Skip to content

Rate limits

Limits are enforced per team, not per key.

That's deliberate. All of a team's keys sit in front of the same sending reputation, so minting a second key can't double what you push at it. It also means the numbers you see are the numbers that matter — a key can be given a tighter limit than its team (handy for one you hand to a partner), but never a looser one.

WindowDefault
Per second10 requests
Per day (UTC)10,000 requests

Both can be raised — ask us once you have a sending history to point at.

Every response tells you where you stand

You don't have to hit a limit to find out about it. Every response carries your remaining budget:

http
RateLimit-Limit: 10
RateLimit-Remaining: 7
RateLimit-Reset: 1
RateLimit-Daily-Limit: 10000
RateLimit-Daily-Remaining: 9863
RateLimit-Daily-Reset: 58685
HeaderMeaning
RateLimit-LimitRequests allowed in the current second.
RateLimit-RemainingHow many are left in it.
RateLimit-ResetSeconds until that window resets.
RateLimit-Daily-LimitRequests allowed today.
RateLimit-Daily-RemainingHow many are left.
RateLimit-Daily-ResetSeconds until 00:00 UTC.

The daily window gets its own headers rather than hiding behind the per-second one. It's the limit that actually ends someone's evening, and a client that can see it coming can slow down instead of falling over.

When you exceed one

429, with the reason in code and a Retry-After in seconds:

json
{
  "message": "Rate limit of 10 requests per second reached for this team.",
  "code": "rate_limit_exceeded",
  "request_id": "8f2c1e64-1b17-4f8c-9f0e-3b6c1e0f5c31"
}
json
{
  "message": "Daily quota of 10000 requests reached for this team. It resets at 00:00 UTC.",
  "code": "daily_quota_exceeded",
  "request_id": "3b6c1e0f-5c31-4f8c-9f0e-8f2c1e641b17"
}

A 429 still carries the full set of RateLimit-* headers, so a backoff loop can read its budget from the very response that rejected it.

Handling it

Wait Retry-After seconds and retry. Don't retry immediately, and don't retry a burst all at once — the second window is one second wide, so a stampede of retries just re-triggers it.

If you regularly hit the per-second limit, the fix is usually a queue between your app and us rather than a higher limit. If you're sending a large run, use batch: 100 messages in one request costs one request against your budget, not 100.

Transactional email on your own domain.