DocsErrors
Errors
On failure the body is a JSON envelope you can switch on. Validation details, when available, are surfaced under error.details.
Error envelope
Same shape across every endpoint.
{
"error": {
"code": "validation_error",
"message": "Invalid request",
"details": {
"fieldErrors": {
"to": ["Required"]
},
"formErrors": []
},
"request_id": "req_..."
}
}The error.code is a stable machine-readable identifier — safe to switch on. error.message is human-readable and may change without notice.
Machine codes
Common identifiers your application can branch on safely.
| Code | Retry | Meaning |
|---|---|---|
| validation_error | No | Request body or query parameters did not match the endpoint schema. |
| unauthorized | After auth fix | The API key is missing, malformed, expired, or revoked. |
| forbidden | After key change | The key is valid but does not include the required scope. |
| rate_limited | Yes | The workspace or key exceeded its current rate limit. |
| domain_quota_exceeded | After upgrade/reset | The active plan has no remaining domain slots. Similar quota errors exist for sends, webhooks, API keys, and templates. |
| domain_auth_broken | After DNS propagation | SPF, DKIM, DMARC, or return-path checks are failing for the sending domain. |
| attachment_type_blocked | After file change | The attachment filename or MIME type is blocked for safety and deliverability. |
| queue_backpressure | Yes | The selected delivery queue is temporarily saturated and returned a Retry-After hint. |
Delivery reasons
Delivery failures also include reason codes that map to a customer action or operator escalation path.
| Reason | Action | Meaning |
|---|---|---|
| no_mx | Fix recipient domain | The recipient domain has no usable MX record. |
| mailbox_not_found | Do not retry | The remote server rejected the recipient mailbox as unknown. |
| rate_limited | Retry later | Sendix or the recipient provider applied a temporary rate limit. |
| tls_failed | Retry or investigate | TLS negotiation failed during SMTP delivery. |
| spf_failed | Fix sender DNS | SPF did not authorize the sending path. |
| dkim_failed | Fix DKIM DNS | The DKIM signature did not validate at the recipient side. |
| blocked_by_remote | Escalate with evidence | The remote provider blocked the message or sender policy. |
| ip_reputation_low | Review reputation | Delivery was affected by sending IP reputation. |
Status codes
What each HTTP status means in practice.
| Status | Name | When you see it |
|---|---|---|
| 200 | OK | Successful request; response body present. |
| 201 | Created | Resource created — `id` returned in the body. |
| 202 | Accepted | Async work queued for broadcasts or webhook delivery checks. |
| 204 | No Content | Successful request, no response body. |
| 400 | Bad Request | Validation failed — see `error.code` and `error.details`. |
| 401 | Unauthorized | Missing, malformed, or revoked credentials. |
| 402 | Payment Required | Plan limit hit — upgrade or wait for the period reset. |
| 403 | Forbidden | Authenticated but the key lacks the required scope. |
| 404 | Not Found | The resource does not exist in this workspace. |
| 409 | Conflict | State mismatch (e.g. publishing an archived template). |
| 422 | Unprocessable Entity | Domain verification failed, audience policy rejected, etc. |
| 429 | Too Many Requests | Rate limit hit — back off using the `Retry-After` header. |
| 500 | Server Error | Unhandled exception. Safe to retry idempotent calls. |
| 503 | Service Unavailable | A dependency is degraded; retry with backoff. |
Retry strategy
Retry 429, 500, and 503 with exponential backoff. Honor Retry-After when it is present. Do not retry validation, auth, scope, suppression, or plan-limit errors until the underlying input or account state changes.
if (res.status === 429 || res.status >= 500) {
const retryAfter = Number(res.headers.get('Retry-After') ?? '0');
const waitMs = retryAfter > 0 ? retryAfter * 1000 : 1000;
// enqueue for retry instead of blocking the request path
}
