outreach/lib/rateLimiter.js
2025-08-15 01:03:38 -08:00

108 lines
2.9 KiB
JavaScript

const config = require("../config");
const logger = require("./logger");
class RateLimiter {
constructor() {
this.sentCount = 0;
this.startTime = Date.now();
this.lastSentTime = null;
}
// Record a successful send
recordSuccess() {
this.sentCount++;
this.lastSentTime = Date.now();
}
// Get randomized delay between min and max
getRandomDelay() {
const baseDelay = config.app.delayMinutes * 60 * 1000; // Convert to ms
const minDelay = baseDelay * 0.8; // 20% less than base
const maxDelay = baseDelay * 1.2; // 20% more than base
// Add additional randomization
const randomFactor = Math.random() * (maxDelay - minDelay) + minDelay;
// Add jitter to avoid patterns
const jitter = (Math.random() - 0.5) * 30000; // +/- 30 seconds
return Math.floor(randomFactor + jitter);
}
// Check if we should pause based on sent count
shouldPause() {
const hoursSinceStart = (Date.now() - this.startTime) / (1000 * 60 * 60);
const emailsPerHour = this.sentCount / hoursSinceStart;
// Gmail limits: ~500/day = ~20/hour
// Be conservative: pause if exceeding 15/hour
if (emailsPerHour > 15) {
return true;
}
// Pause every 50 emails for 30 minutes
if (this.sentCount > 0 && this.sentCount % 50 === 0) {
return true;
}
return false;
}
// Get pause duration
getPauseDuration() {
// Standard pause: 30 minutes
const basePause = 30 * 60 * 1000;
// Add randomization to pause duration
const randomPause = basePause + Math.random() * 10 * 60 * 1000; // +0-10 minutes
return randomPause;
}
// Calculate next send time
async getNextSendDelay() {
if (this.shouldPause()) {
const pauseDuration = this.getPauseDuration();
const pauseMinutes = Math.round(pauseDuration / 60000);
logger.rateLimitPause(
pauseDuration,
`Automatic pause after ${this.sentCount} emails`
);
console.log(`Rate limit pause: ${pauseMinutes} minutes`);
return pauseDuration;
}
return this.getRandomDelay();
}
// Get human-readable time
formatDelay(ms) {
const minutes = Math.floor(ms / 60000);
const seconds = Math.floor((ms % 60000) / 1000);
return `${minutes}m ${seconds}s`;
}
// Reset counters (for testing)
reset() {
this.sentCount = 0;
this.startTime = Date.now();
this.lastSentTime = null;
}
// Get current stats
getStats() {
const runtime = (Date.now() - this.startTime) / 1000; // seconds
const avgRate = this.sentCount / (runtime / 3600); // emails per hour
return {
sentCount: this.sentCount,
runtime: Math.floor(runtime / 60), // minutes
averageRate: avgRate.toFixed(1),
nextDelay: this.formatDelay(this.getRandomDelay()),
};
}
}
module.exports = new RateLimiter();