Mail · Guides
Errors and limits
How the API reports problems, how much you can send, and how to retry without sending an email twice.
Error responses
Successful requests return 200 OK. When something goes wrong, the status code says what kind of problem it is, and the body says exactly what happened:
{
"error": {
"type": "validation_error",
"message": "The to field must contain at least one email address."
}
}Build your error handling on type: it's one of the values below and won't change. The message is written for people. Log it or show it to a developer, but don't parse it, as the wording may change.
Error types
These are all the error types the API returns. The API reference lists which ones each endpoint can return.
| Status | Type | What it means |
|---|---|---|
| 400 | invalid_request | We couldn't read the request: the body isn't valid JSON, Content-Type isn't application/json, or the Idempotency-Key header is longer than 255 characters. |
| 401 | missing_api_key | There's no Authorization: Bearer header with an API key. |
| 401 | invalid_api_key | The API key doesn't exist or has been revoked. |
| 403 | plan_inactive | The Email API plan the key belongs to isn't active, for example because it has ended. |
| 403 | domain_not_verified | The from address isn't at one of your verified sending domains. |
| 403 | domain_limit_reached | All of your plan's sending domains are in use. Delete one, or upgrade your plan in your account. |
| 403 | webhook_limit_reached | You already have 5 webhooks. |
| 403 | test_key | The key is a test inbox's (mb_test_…). A test key sends to its inbox and reads what it sent; domains, webhooks and suppressions take a live key. See Test keys. |
| 404 | not_found | There's no endpoint with this method and path, or the ID isn't on your plan. Emails older than your plan's log retention return this too. |
| 409 | domain_exists | This domain is already on your plan. |
| 409 | idempotency_conflict | The Idempotency-Key was already used with a different request body, or the first request with it is still being processed. |
| 422 | validation_error | A field or query parameter is missing, unknown or invalid. The message says which one and why. |
| 429 | rate_limit_exceeded | More than 10 requests in one second with this key. Wait for the number of seconds in the Retry-After header. |
| 429 | monthly_limit_reached | Sending this email would go over your plan's monthly allowance. Nothing was sent. |
| 500 | server_error | Something went wrong on our side. Try again; for POST /emails, use the same Idempotency-Key. |
Retrying
429: wait as long as theRetry-Afterheader says, then try again. The exception ismonthly_limit_reached, which lasts until next month.500and network errors: try again after a short pause, and wait longer after each failure. ForPOST /emails, send the sameIdempotency-Keyso the email can't go out twice.- Other errors: the same request will fail again. Fix the cause first.
Rate limits
Each API key can make up to 10 requests a second. Above that, requests fail with 429 and the type rate_limit_exceeded. The Retry-After header says how many seconds to wait before trying again.
Monthly allowance
Your plan includes a number of emails a month (see Limits). Each recipient counts as one email: an email to two addresses in to and one in cc uses three. Recipients that are skipped because they're on your suppression list don't count; recipients whose email bounces or fails later do.
The allowance starts again on the 1st of every month at 00:00 UTC. Unused emails don't carry over.
If an email's recipients don't fit in what's left this month, the request fails with 429 and the type monthly_limit_reached, and nothing is sent. This response has no Retry-After header. To send more, upgrade your plan in your account.
Idempotency
If a request to send an email times out, you can't tell whether the email was accepted. An idempotency key makes it safe to try again: send a unique value, such as a UUID, in the Idempotency-Key header of POST /emails, and use the same value when you retry.
curl -X POST https://services.my-beast.com/mail/api/email/v1/emails \
-H "Authorization: Bearer $MYBEAST_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 4f1c2b7e-9d3a-4e8b-a6f0-2c5d8e1b7a93" \
-d '{ ... }'- Keys can be up to 255 characters. We remember them for 24 hours, for all API keys on your plan.
- Retrying with the same key and the same body returns the first response, and no second email is sent. The response has the header
Idempotent-Replayed: true. - Using the same key with a different body fails with
409and the typeidempotency_conflict. So does a retry while the first request is still being processed: wait a moment and try again. - Only successful requests are remembered. If the first request failed, a retry with the same key is handled as a new request.
Pagination
Endpoints that list things return them newest first, one page at a time, in the same shape:
{
"data": [ ... ],
"has_more": true
}data holds up to limit items (20 by default, at most 100). If has_more is true, ask for the next page by passing the ID of the last item as after:
curl "https://services.my-beast.com/mail/api/email/v1/emails?limit=100&after=em_01JA2Q8ZK4T9W6" \
-H "Authorization: Bearer $MYBEAST_API_KEY"Repeat until has_more is false. The suppression list has no IDs, so there after is the last item's email.
Limits
By plan:
Loading the plans…
On every plan:
| What | Limit |
|---|---|
| Requests | 10 a second per API key |
| Recipients per email | 50, counting to, cc and bcc together |
| Reply-to addresses | 5 per email |
| Email size | 10 MB, counting the bodies and decoded attachments |
| Subject | 500 characters |
| Custom headers | 50 per email |
| Tags | 10 per email; names and values up to 256 characters |
| Idempotency keys | 255 characters, remembered for 24 hours |
| List page size | 20 by default, up to 100 |
| Webhooks | 5 per plan |
| Webhook response time | 10 seconds |
Need more?