Mailbox providers impose strict per-IP connection and message limits that differ significantly: Gmail accepts roughly 250 SMTP connections per hour from a new IP, Yahoo and AOL limit to around 20 connections per hour per IP, and Outlook uses session-based hourly quotas. RFC5321-s4.5.3 makes clear that exceeding these limits results in 421 temporary deferrals. When all recipients are throttled identically — ignoring which ISP is receiving — the system will inevitably over-deliver to restrictive providers, accumulating 421 responses that reduce effective throughput and signal poor sending behavior, eroding IP reputation at the exact providers where limits are tightest.
High because ISP-agnostic throttling causes 421 deferrals and throughput reduction at Yahoo and Outlook specifically, where per-IP limits are tightest, with repeated deferrals contributing to IP reputation degradation.
Map receiving domain to ISP and enforce per-ISP limits before dispatching. This configuration covers the three mailbox providers responsible for the majority of consumer email:
const ISP_LIMITS: Record<string, { maxPerHour: number; maxConcurrent: number }> = {
'gmail.com': { maxPerHour: 250, maxConcurrent: 10 },
'yahoo.com': { maxPerHour: 100, maxConcurrent: 5 },
'aol.com': { maxPerHour: 100, maxConcurrent: 5 },
'outlook.com': { maxPerHour: 150, maxConcurrent: 8 },
'hotmail.com': { maxPerHour: 150, maxConcurrent: 8 },
'live.com': { maxPerHour: 150, maxConcurrent: 8 },
'default': { maxPerHour: 200, maxConcurrent: 10 },
}
export function getIspLimit(recipientDomain: string) {
return ISP_LIMITS[recipientDomain.toLowerCase()] ?? ISP_LIMITS['default']
}
Bucket your queue workers by recipient ISP and apply a Bottleneck or similar rate limiter per bucket. Cross-reference deliverability-engineering.sending-throttling.adaptive-throttling — per-ISP limits are the static floor; adaptive throttling handles dynamic 4xx signals on top.
ID: deliverability-engineering.sending-throttling.per-isp-limits
Severity: high
What to look for: Enumerate all ISP-specific rate limit configurations. Search for code that identifies the receiving ISP of a recipient and applies ISP-specific connection or message rate limits. Key limits vary widely: Gmail accepts ~250 connections/hour from a new IP, Yahoo/AOL limits to ~20 connections/hour per IP, Outlook.com uses hourly SMTP session limits. Look for a configuration object that maps ISP domains or IP ranges to their respective limits, and for code that selects the correct limit before dispatching.
Pass criteria: A per-ISP limit configuration exists covering at least 3 major ISPs (Gmail, Yahoo/AOL, Microsoft) with distinct limits. Report the count of ISPs configured even on pass.
Fail criteria: All recipients are throttled identically regardless of their mailbox provider, or no throttling is applied at the per-ISP level.
Skip (N/A) when: The project sends fewer than 1,000 emails per day where per-ISP limits are unlikely to be hit, or uses an ESP's managed sending infrastructure that handles ISP throttling transparently.
Cross-reference: Check deliverability-engineering.sending-throttling.adaptive-throttling — per-ISP limits are static; adaptive throttling responds dynamically to 4xx deferrals.
Detail on fail: "No per-ISP throttling found — all recipients subject to the same rate, risking 421 deferrals from Yahoo and Microsoft when their per-IP limits are exceeded" or "Throttling configured globally but not differentiated by receiving mailbox provider"
Remediation: Map receiving domains to ISP policies and throttle accordingly:
const ISP_LIMITS: Record<string, { maxPerHour: number; maxConcurrent: number }> = {
'gmail.com': { maxPerHour: 250, maxConcurrent: 10 },
'googlemail.com': { maxPerHour: 250, maxConcurrent: 10 },
'yahoo.com': { maxPerHour: 100, maxConcurrent: 5 },
'ymail.com': { maxPerHour: 100, maxConcurrent: 5 },
'aol.com': { maxPerHour: 100, maxConcurrent: 5 },
'outlook.com': { maxPerHour: 150, maxConcurrent: 8 },
'hotmail.com': { maxPerHour: 150, maxConcurrent: 8 },
'live.com': { maxPerHour: 150, maxConcurrent: 8 },
'default': { maxPerHour: 200, maxConcurrent: 10 },
}
export function getIspLimit(recipientDomain: string) {
return ISP_LIMITS[recipientDomain.toLowerCase()] ?? ISP_LIMITS['default']
}
// In your queue worker, bucket sends by ISP and apply the relevant rate limiter