Skip to content
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.

CodeRetryMeaning
validation_errorNoRequest body or query parameters did not match the endpoint schema.
unauthorizedAfter auth fixThe API key is missing, malformed, expired, or revoked.
forbiddenAfter key changeThe key is valid but does not include the required scope.
rate_limitedYesThe workspace or key exceeded its current rate limit.
domain_quota_exceededAfter upgrade/resetThe active plan has no remaining domain slots. Similar quota errors exist for sends, webhooks, API keys, and templates.
domain_auth_brokenAfter DNS propagationSPF, DKIM, DMARC, or return-path checks are failing for the sending domain.
attachment_type_blockedAfter file changeThe attachment filename or MIME type is blocked for safety and deliverability.
queue_backpressureYesThe 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.

ReasonActionMeaning
no_mxFix recipient domainThe recipient domain has no usable MX record.
mailbox_not_foundDo not retryThe remote server rejected the recipient mailbox as unknown.
rate_limitedRetry laterSendix or the recipient provider applied a temporary rate limit.
tls_failedRetry or investigateTLS negotiation failed during SMTP delivery.
spf_failedFix sender DNSSPF did not authorize the sending path.
dkim_failedFix DKIM DNSThe DKIM signature did not validate at the recipient side.
blocked_by_remoteEscalate with evidenceThe remote provider blocked the message or sender policy.
ip_reputation_lowReview reputationDelivery was affected by sending IP reputation.

Status codes

What each HTTP status means in practice.

StatusNameWhen you see it
200OKSuccessful request; response body present.
201CreatedResource created — `id` returned in the body.
202AcceptedAsync work queued for broadcasts or webhook delivery checks.
204No ContentSuccessful request, no response body.
400Bad RequestValidation failed — see `error.code` and `error.details`.
401UnauthorizedMissing, malformed, or revoked credentials.
402Payment RequiredPlan limit hit — upgrade or wait for the period reset.
403ForbiddenAuthenticated but the key lacks the required scope.
404Not FoundThe resource does not exist in this workspace.
409ConflictState mismatch (e.g. publishing an archived template).
422Unprocessable EntityDomain verification failed, audience policy rejected, etc.
429Too Many RequestsRate limit hit — back off using the `Retry-After` header.
500Server ErrorUnhandled exception. Safe to retry idempotent calls.
503Service UnavailableA 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
}