Rate limits & quotas
Per-minute burst limits, plan send quotas, response headers, and 429 semantics.
Limits are enforced per tenant at the edge by a Durable Object, so they are
consistent across every region. Only POST /v1/email and
POST /v1/email/batch consume quota — one unit per email, so a batch of N
consumes N.
Per-minute burst limit
A flat limit shared by every tenant and plan (RATE_LIMIT_PER_MIN, currently
100 requests/minute) — not tiered by plan. Every response from a
rate-checked route includes:
| Header | Meaning |
|---|---|
X-RateLimit-Limit | The per-minute limit |
X-RateLimit-Remaining | Requests left in the current minute |
X-RateLimit-Reset | Seconds until the current minute window resets |
A 429 additionally carries Retry-After (seconds).
Daily and monthly send quotas
Tiered by plan, tracked as rolling windows (not fixed-window resets):
| Plan | Daily quota | Monthly quota | Overage / 1,000 |
|---|---|---|---|
| Free | 150 | 5,000 | none (hard stop) |
| Pay as you go | 250,000 | 5,000 included | $0.35 |
| Credits | 1,000,000 | 0 (prepaid balance) | $0.28 from balance |
No plan has a base price. On the metered plans (payg, credits) the monthly
window does not gate sending at all — once Pay as you go's included 5,000 are
consumed every send is billable, and Credits draws down prepaid balance.
Their daily quota is the only ceiling: a burst guard against a runaway job. A
quota_exceeded on those plans always means the daily ceiling was hit.
- Free hard-stops at its caps — sending pauses, nothing overage-bills.
- Pay as you go includes the first 5,000 emails each month, then bills $0.35 per 1,000 automatically.
- Credits has no included volume: prepaid packs cost $0.28 per 1,000 and are valid for 365 days.
Rejections never burn quota
A send rejected after a quota unit was consumed — validation failure, unverified domain, suppression — has that unit refunded. A rejected send never costs you money.
HTTP/2 429
Retry-After: 12
{ "error": "rate_limit_exceeded" }HTTP/2 429
Retry-After: 30
{ "error": "quota_exceeded" }Back off, don't hammer
Honour Retry-After on a 429. Tight retry loops waste your own requests and
get you nothing — the limit is a rolling per-minute window, so waiting is
always the fastest way back in.
See also: Errors and Idempotency for safe retries.