Reference
Errors & limits
Every failure has the same shape, and the code is the part to write logic against. The message is written for a human and may change.
{
"error": {
"code": "DOMAIN_NOT_VERIFIED",
"message": "example.com has not passed DNS verification. SPF and DKIM must both verify before it can send.",
"details": {}
}
}Codes
| Code | HTTP | What it means |
|---|---|---|
| INVALID_API_KEY | 401 | Key is unknown or revoked. |
| UNAUTHORIZED | 401 | Missing or malformed Authorization header. |
| FORBIDDEN | 403 | Authenticated, but not allowed to do this. An API key cannot reach dashboard-only endpoints. |
| ACCOUNT_SUSPENDED | 403 | Sending is blocked pending review. |
| DOMAIN_NOT_FOUND | 404 | That domain is not on this account. |
| DOMAIN_NOT_VERIFIED | 403 | The domain exists but SPF and DKIM have not both passed. |
| INVALID_FROM_ADDRESS | 422 | Malformed, or the domain restricts senders and this address is not on its list. |
| INVALID_RECIPIENT | 422 | A recipient address was rejected. details.recipients says which. |
| SUPPRESSED_RECIPIENT | 403 | One or more recipients are suppressed and will not be mailed. |
| TOO_MANY_RECIPIENTS | 422 | Over 50 across to, cc and bcc. |
| EMAIL_TOO_LARGE | 413 | Bodies plus decoded attachments exceed the size limit. |
| VALIDATION_ERROR | 422 | The body failed validation. details lists each field and what was wrong with it. |
| RATE_LIMIT_EXCEEDED | 429 | Too many HTTP requests. Nothing was created — safe to retry. |
| SENDING_LIMIT_EXCEEDED | 429 | Hourly or daily allowance spent. Safe to retry later. |
| EMAIL_QUEUE_FAILED | 503 | Accepted but could not be queued; the record was rolled back. Safe to retry. |
| INTERNAL_ERROR | 500 | Unexpected, and logged on our side. Do not retry a send — see below. |
Which failures are safe to retry
Sending is not idempotent, so a blind retry can send the message twice. Retry only where nothing can have been created: no response at all, 429, or 503 EMAIL_QUEUE_FAILED.
A 500 is the one that catches people. The message may have been queued before the failure, so retrying can duplicate it — and a duplicate email is worse than a failed one. Surface it instead.
Reading a validation error
details names the field, so you can point a user at the right input:
{
"error": {
"code": "VALIDATION_ERROR",
"message": "The request body failed validation.",
"details": [
{ "path": "email", "message": "Must be a valid email address" },
{ "path": "password", "message": "Password must be at least 12 characters" }
]
}
}Limits
Two independent layers, and they fail with different codes so you can tell them apart.
HTTP requests
Per IP, per minute. Exceeding it returns RATE_LIMIT_EXCEEDED. Credential endpoints allow far fewer attempts, per IP, per fifteen minutes — that limit exists to make password guessing impractical.
Every response carries the current state, so you can back off before being refused:
X-RateLimit-Limit: 600
X-RateLimit-Remaining: 597
X-RateLimit-Reset: 47Messages
Counted per recipient, not per call
A message to fifty people spends fifty of your allowance, not one. This is the thing that surprises people when a bulk send stops halfway.
The allowance is enforced against your account, the sending domain and the API key simultaneously — whichever runs out first refuses the request with SENDING_LIMIT_EXCEEDED. A rejected request consumes nothing.
New accounts start lower for the first day. Beyond that the allowance rises as your sending stays clean, because a sending IP with no history gets filtered if it suddenly sends thousands of messages — see what to watch while you ramp.
Your current usage against both windows is on the Deliverability page.
NextSecurityHow your keys and data are kept separate from everyone else's.