rate-limiter.js
1 import Bottleneck from 'bottleneck'; 2 import './load-env.js'; 3 4 /** 5 * Rate limiter for OpenRouter API 6 * 7 * Vendor limits: 200 RPM for gpt-4o-mini (paid plan); 20 RPM (free tier) 8 * Regulatory: None 9 * 10 * Default: OPENROUTER_REQUESTS_PER_MINUTE=194 (3% below 200 RPM paid plan) 11 * OPENROUTER_MAX_CONCURRENT=5 12 * Free tier override: set OPENROUTER_REQUESTS_PER_MINUTE=19 13 */ 14 export const openRouterLimiter = new Bottleneck({ 15 minTime: Math.round(60000 / parseInt(process.env.OPENROUTER_REQUESTS_PER_MINUTE || '194', 10)), 16 maxConcurrent: parseInt(process.env.OPENROUTER_MAX_CONCURRENT || '5', 10), 17 }); 18 19 /** 20 * Rate limiter for Twilio SMS API 21 * 22 * Vendor limits: Long code = 1 SMS/sec per number 23 * Toll-free = 3 SMS/sec 24 * Short code = 100 SMS/sec 25 * Regulatory: TCPA/A2P 10DLC — 10 MPS cap for registered campaigns; 26 * business hours (8am–9pm local) enforced in compliance.js 27 * 28 * Default: TWILIO_REQUESTS_PER_SECOND=0.7 (30% below 1 SMS/sec long code limit) 29 * TWILIO_MAX_CONCURRENT=1 30 * Short code override: set TWILIO_REQUESTS_PER_SECOND=70 31 */ 32 export const twilioLimiter = new Bottleneck({ 33 minTime: Math.round(1000 / parseFloat(process.env.TWILIO_REQUESTS_PER_SECOND || '0.7')), 34 maxConcurrent: parseInt(process.env.TWILIO_MAX_CONCURRENT || '1', 10), 35 }); 36 37 /** 38 * Rate limiter for ZenRows API 39 * 40 * Vendor limits: Monthly subscription — no daily limit (confirmed with ZenRows support 2026-03-06) 41 * Concurrency: 20 simultaneous requests max 42 * Regulatory: None 43 * 44 * Default: ZENROWS_CONCURRENCY=19 (5% below 20 concurrent limit) 45 * No daily reservoir — monthly plan has no daily cap. 46 * 47 * Reference: https://docs.zenrows.com/universal-scraper-api/features/concurrency 48 */ 49 export const zenrowsLimiter = new Bottleneck({ 50 maxConcurrent: parseInt(process.env.ZENROWS_CONCURRENCY || '19', 10), 51 }); 52 53 /** 54 * Rate limiter for Resend email API 55 * 56 * Vendor limits: 10 req/sec (Pro plan); 50,000 emails/month, NO daily cap 57 * Regulatory: CAN-SPAM/CASL — no per-second limit; campaign-level frequency 58 * restrictions are enforced via outreach cooldown logic, not here 59 * 60 * Default: RESEND_REQUESTS_PER_SECOND=1.5 (25% below 2 req/sec actual limit) 61 * NOTE: Resend docs say 10 req/sec but actual plan enforces 2 req/sec 62 * RESEND_MAX_CONCURRENT=1 63 * 64 * Plan: Pro — 50,000 emails/month, no daily limit (upgraded 2026-03-06) 65 * Reference: https://resend.com/docs/api-reference/introduction#rate-limit 66 */ 67 export const resendLimiter = new Bottleneck({ 68 minTime: Math.round(1000 / parseFloat(process.env.RESEND_REQUESTS_PER_SECOND || '1.5')), 69 maxConcurrent: parseInt(process.env.RESEND_MAX_CONCURRENT || '1', 10), 70 }); 71 72 /** 73 * Rate limiter for ZeroBounce Email Validation API 74 * 75 * Vendor limits: ~100 req/sec (documented upper bound) 76 * Note: batch endpoint sends 200 emails per API call, so effective throughput 77 * is 200 × 40 = 8,000 emails/sec — far more than needed. 78 * 79 * Default: ZEROBOUNCE_REQUESTS_PER_SECOND=40 (60% below vendor limit) 80 * ZEROBOUNCE_MAX_CONCURRENT=5 81 */ 82 export const zeroBounceLimiter = new Bottleneck({ 83 minTime: Math.round(1000 / parseFloat(process.env.ZEROBOUNCE_REQUESTS_PER_SECOND || '40')), 84 maxConcurrent: parseInt(process.env.ZEROBOUNCE_MAX_CONCURRENT || '5', 10), 85 }); 86 87 /** 88 * Rate limiter for DataForSEO API 89 * 90 * Vendor limits: 2,000 req/min; 100 concurrent connections max 91 * Regulatory: None 92 * 93 * Default: DATAFORSEO_REQUESTS_PER_MINUTE=1940 (3% below 2,000 RPM vendor limit) 94 * DATAFORSEO_MAX_CONCURRENT=97 (3% below 100 concurrent limit) 95 * 96 * Reference: https://docs.dataforseo.com/v3/appendix/rate_limits/ 97 */ 98 export const dataForSEOLimiter = new Bottleneck({ 99 minTime: Math.round(60000 / parseInt(process.env.DATAFORSEO_REQUESTS_PER_MINUTE || '1940', 10)), 100 maxConcurrent: parseInt(process.env.DATAFORSEO_MAX_CONCURRENT || '97', 10), 101 });