Recipient rate guards
Caps that stop the same mailbox from being hammered — 5 per 10 minutes, 20 per 24 hours, and bounce cooldowns.
Lumail blocks a single recipient address from receiving too many real emails, even if the sender is allowed to send. Campaigns, workflows, transactional API, and SMTP share this gate. It is not your plan quota and not the HTTP API rate limit.
The address is hashed (SHA-256 of the normalized envelope) before it is stored in Redis. Fixture / test-mode recipients skip the gate entirely — see Test-mode recipients.
Caps
| Rule | Limit | Window |
|---|---|---|
| Frequency | 5 accepted sends | rolling 10 minutes |
| Frequency | 20 accepted sends | rolling 24 hours |
| Bounce cooldown | no further sends | 24 hours after a recipient-rate or mailbox-full bounce |
A send counts only when the SES-boundary grant succeeds. Peeking the windows (API preflight) does not increment them.
The 24-hour frequency cap is the tighter one for a blast to one inbox: at most 20 real emails per address per day.
What trips the 24-hour cooldown
A later bounce in these categories writes a per-recipient cooldown key. Until it expires, every send to that address is refused:
- Gmail / ISP recipient rate throttled
- Mailbox full
Coarse ISP labels (for example Other) never pause unrelated recipients. Guards are recipient-only.
Who it applies to
| Path | Guarded? | When it refuses |
|---|---|---|
Transactional API (POST /api/v1/emails, POST /api/v2/emails, batch) | Yes | Before queue if the window is already full. At send if a burst queued first. |
SMTP (smtp.lumail.io) | Yes | Same transactional lane and the same Redis windows. |
| Campaigns and workflow emails | Yes | At send. Already-enrolled recipients that hit the cap are skipped, not retried. |
| Test-mode / reserved fixture domains | No | Fake SES id, no window increment. |
Transactional traffic is TIME_SENSITIVE: it jumps ahead of campaign bulk for the shared SES limiter. The recipient caps still apply. A 20k burst to one Gmail does not starve the guard, but while those rows drain or skip they can delay bulk campaigns.
What you see
Transactional API — refused at enqueue
The address already sits at 5/10 min or 20/24 h (or is on cooldown). Lumail does not create the email or the journal row.
{
"message": "This recipient is temporarily rate limited",
"code": "RECIPIENT_RATE_LIMITED",
"retryAfter": 86400
}HTTP 429, plus Retry-After in seconds. Honor that header. Do not treat this as the organization API rpm limit — that 429 uses a different body (Too many requests) and is documented under Rate Limits.
Transactional / campaign — refused at send
A burst can pass preflight while the windows are still empty, then lose at the SES fence. Those rows become journal phase SKIPPED with RECIPIENT_RATE_LIMITED. No provider send. Campaigns do not retry them. Transactional placeholders stay unsent (awsEmailId null).
One address, 20 000 transactional emails in two hours
- The first 20 grants increment the 24-hour window and go to SES (subject to org quota and API rpm).
- Call 21 onward: 429
RECIPIENT_RATE_LIMITEDif you send after those 20 have been granted. - If you enqueue all 20 000 before any grant lands, ~20 send and the rest
SKIPPED. - Gmail sees at most 20 messages. The rest never leave Lumail.
- The 429 / skip lasts until the oldest grant ages out of the 24-hour window (up to 24 h).
Without an Idempotency-Key, each call is a distinct dispatch. You still get one subscriber row.
Not this guard
| Symptom | What it actually is |
|---|---|
429 Too many requests | Organization HTTP rpm. Rate Limits. |
| Monthly / daily plan cap | Lumail billing quota. How to fix a monthly email limit. |
| Sending rate exceeded / daily SES quota | Shared provider account cap. How to fix sending rate or daily quota. |
| Slow campaign (for example ~10 emails/s) | Provider RTT and retries after the grant. Recipient guards are not a global emails/s cap. |