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:

HeaderMeaning
X-RateLimit-LimitThe per-minute limit
X-RateLimit-RemainingRequests left in the current minute
X-RateLimit-ResetSeconds 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):

PlanDaily quotaMonthly quotaOverage / 1,000
Free1505,000none (hard stop)
Pay as you go250,0005,000 included$0.35
Credits1,000,0000 (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.

On this page