Errors and Rate Limits
Sending email at scale means dealing with errors. This page covers what Mailtarget returns, what you should retry, and what should fail fast and surface to a human.
API error response shape
When the API returns a non-2xx status, the body is a JSON object with two fields.
{
"error": "string code",
"message": "human-readable description"
}
The error field is a stable code you can match against in code. The message field is for humans and may change.
HTTP status codes
| Status | Meaning | What to do |
|---|---|---|
200 / 201 | Accepted. Response body contains the transmissionId. | Persist the transmissionId and listen for webhook events. |
400 Bad Request | The request body is malformed or a required field is missing. | Do not retry. Fix the request and resubmit. |
401 Unauthorized | The Authorization header is missing, malformed, or the key is invalid. | Do not retry. Check the header and the key. |
403 Forbidden | The key is valid but the scope does not cover this operation, or the request came from an IP outside the allowlist. | Do not retry. Check the key permission scope and the allowlist. |
404 Not Found | The endpoint path is wrong, or a referenced resource (template ID, contact ID) does not exist. | Do not retry. Check the URL and resource identifiers. |
422 Unprocessable Entity | The request is well-formed but business validation failed (for example, recipient on suppression list, sending domain not verified). | Do not retry. Surface to a human. |
429 Too Many Requests | You exceeded a rate limit. | Retry after the duration in the Retry-After header. |
500 Internal Server Error | Mailtarget had an internal failure. | Retry with exponential backoff. |
502 / 503 / 504 | Transient infrastructure issue (gateway, upstream, timeout). | Retry with exponential backoff. |
Rate limits
The API enforces per-account rate limits. When you exceed one, the response status is 429 and the response includes a Retry-After header indicating the number of seconds to wait before retrying.
HTTP/1.1 429 Too Many Requests
Retry-After: 30
Content-Type: application/json
{ "error": "rate_limited", "message": "Account send rate exceeded." }
Honor Retry-After. Do not retry sooner. Most rate limits resolve in seconds; a few resolve in minutes. The exact ceiling depends on your account package and is visible in the dashboard.
If you hit 429 repeatedly, the throughput your account is provisioned for is below your demand. Either:
- Reduce the send rate at the application layer (queue and drain).
- Raise the package ceiling.
Do not work around 429 by spinning up more keys or sending from more IPs. The limit is per account, not per key.
Retry strategy
A safe retry policy:
- Retry only
429,500,502,503,504. - Use exponential backoff with jitter. A common starting point is
2 ** attempt + random(0, 1)seconds, capped at 60 seconds. - Cap retries at five attempts. After the cap, write the request to a dead-letter queue for human review.
- Persist the request body and an idempotency key (you choose the key) before the first attempt. On retry, send the same idempotency key in the request metadata so duplicate sends can be deduped at reconciliation time.
Do not retry 400, 401, 403, 404, 422. Those are caller errors. Retrying them just amplifies the problem.
SMTP errors
The SMTP relay returns standard SMTP response codes.
| Code class | Meaning | What to do |
|---|---|---|
2xx | Accepted by the relay. | Treat as queued for delivery. Listen for webhook events for final state. |
4xx | Temporary failure. | Retry with backoff. Most SMTP clients do this automatically. |
5xx | Permanent failure. | Do not retry. Surface to a human. The bounce will also fire as a webhook event. |
The relay returns the same kinds of failure modes as the API: bad credentials, unverified domain, suppressed recipient, rate exceeded. The codes follow RFC 5321. The text in the response body tells you which class.
Bounce types
Mailtarget classifies non-deliveries into two categories that affect retry behavior:
- Hard bounce. The recipient is permanently undeliverable (mailbox does not exist, domain does not accept mail). Mailtarget adds the address to the suppression list. Do not retry. Read the Operations section on bounce classification for the policy.
- Soft bounce. The recipient is temporarily undeliverable (mailbox full, server unavailable, greylisting). Mailtarget retries automatically inside the platform for a bounded window. Your application does not need to retry soft bounces.
Treat the suppression list as authoritative. Sending to addresses on the list is a fast way to damage your sending domain reputation.
Reconciling sends to outcomes
The handle is the transmissionId returned at submit time. Every webhook event for that send carries the same transmissionId. Persist the ID at the moment you get the 2xx response, and join against webhook events as they arrive.
If you do not get a transmissionId (because the response was an error), the send did not happen. Treat it as not yet sent.
Next
Wire Webhooks to receive delivery, open, click, bounce, and complaint events. Without webhooks, you cannot tell what happened after submit.