# Per-ISP rate limits enforced

- **Pattern:** `ab-000935` (`deliverability-engineering.sending-throttling.per-isp-limits`)
- **Severity:** high
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-000935
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-000935)

## Why it matters

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.

## Severity rationale

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.

## Remediation

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:

```ts
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.

## Detection

- **ID:** `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:

  ```ts
  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
  ```

## External references

- external RFC5321-s4.5.3 — https://www.rfc-editor.org/rfc/rfc5321#section-4.5.3
- iso-25010 performance-efficiency

Taxons: cost-efficiency

HTML version: https://auditbuffet.com/patterns/ab-000935
