Limits — send caps, payload sizes, and rate limits
Sending is capped per plan, on three windows — monthly, hourly, and daily. Caps are counted per recipient: a message with three to addresses plus one cc counts as four against your caps.
Send caps by tier
| Tier | Monthly | Hourly | Daily | Price |
|---|---|---|---|---|
| Free | 3,000 | 30 | 300 | $0 |
| Pro | 50,000 | 500 | 5,000 | $20/mo |
| Scale | 250,000 | 2,000 | 20,000 | $99/mo |
See pricing for what each tier includes. Hitting a cap returns a 429 with a specific code:
- Hourly cap →
hourly_limit_exceeded, counted over any 60 minutes. - Daily cap →
daily_limit_exceeded, counted over any 24 hours. - Monthly cap →
monthly_limit_exceeded(orfree_tier_exceededon Free).
The body says when the send will go through: resets_at (UTC) and retry_after_seconds, with the same time in the message. Both are null when waiting will not help, because the send alone is over the cap. A Retry-After header is sent only when the wait is 60 seconds or less, so retry at resets_at rather than waiting for a header.
An agent can also carry caps of its own, hourly_send_limit and daily_send_limit, set when you create or update it and counted on top of your plan’s. Past one, that agent’s sends get the same codes. Test-mode sends are neither counted nor refused by them.
{
"error": {
"type": "rate_limit_error",
"code": "hourly_limit_exceeded",
"message": "Hourly cap (500) reached. Retry at 2026-10-05T14:03:12Z or later.",
"param": null,
"request_id": "req_01KVF4...",
"resets_at": "2026-10-05T14:03:12.000Z",
"retry_after_seconds": 1874
}
}You can also give one agent a lower cap of its own: set hourly_send_limit or daily_send_limit when you create or update the agent. Past it, that agent’s live sends get the same 429 codes and Retry-After values, the message names the agent, and test-mode sends never count.
Burst and reach limits
Each live API key may make 100 send requests per 10 seconds, across POST /v1/messages, the batch, reply, forward, appeal and draft-send routes. Past it a request gets 429 rate_limit_exceeded; a test key is never refused. Responses from those routes carry RateLimit-Policy, RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset headers. One batch of up to 100 messages is one request.
A workspace can also reach only so many distinct recipients in any 24 hours. The number starts low for a new workspace and grows with its age, never above the plan’s daily cap. A live send that would pass it gets 429 new_workspace_fanout_exceeded: its message gives the number, and resets_at says when the send would go through. A send only to people the workspace already mailed in that window always passes, and test-mode sends do not count.
Plus addresses a day
Mail to any plus address of an agent’s receiving address reaches the agent, such as qa.acme+run-1842@in.mails.ai for qa.acme@in.mails.ai (see email testing). A workspace can receive mail at this many different plus addresses in any 24 hours:
| Tier | Plus addresses in 24 hours |
|---|---|
| Free | 20 |
| Pro | 1,000 |
| Scale | 10,000 |
An address that already received mail in that time keeps receiving it, so a test’s second email always arrives. Past the limit, mail to a new plus address is not kept. A record of who sent it, to whom and when takes its place, with over_limit set to plus_address_limit, and raises message.received.over_limit. A wait with that address as to answers 402 plan_limit_exceeded at once. Mail made with POST /v1/test/inbound is not counted.
A sign-in code, or a verify, sign-in or password-reset link, from a service such as Google, Microsoft, Apple, a payment app or a bank never reaches a plus address: such mail keeps only a record, with over_limit set to held_code, and no wait finds it. For Microsoft that is only its account team’s mail, from accountprotection.microsoft.com: the codes Azure AD B2C sends for your own app, from microsoftonline.com, arrive. Sign up for those services at the agent’s own address. Both kinds of record count toward one cap, shared with mail past the hourly and daily inbound rates: at most 20 records an hour for each workspace.
Payload limits on a single message
The hard limits on one POST /v1/messages body:
- Recipients — up to 50 each in
to,cc,bcc. - Subject — up to 998 characters.
- Body — up to 1,000,000 characters each for
body_textandbody_html. - Attachments — up to 10 files, 25 MB total decoded.
- Metadata — string key/value pairs (keys ≤64, values ≤500 chars).
- Tags — up to 10
{ name, value }pairs. - Batch — up to 100 messages per
POST /v1/messages/batch.
Exceeding any of these returns 400 invalid_param_value, or 400 invalid_field for the attachments’ total size, with the offending field in param — see errors.
Staying under the caps
- Back off on 429. Wait until the body’s
resets_atrather than retrying immediately. - Batch where you can. One
POST /v1/messages/batchof 100 is a single round-trip, though it still counts each recipient against your caps. - Scheduling does not smooth bursts. A send with a future
scheduled_atcounts against the hourly and daily caps from the moment it is accepted, and is checked against them again when it goes out, so spread a large run by sending it over time. - Use idempotency keys. Pair retries with an
Idempotency-Keyso a retried send never double-charges a cap or a recipient.
Was this page helpful?