Hoppa till innehållet

Reference

Errors

Every error from the API is JSON, with an error code for your program and a message for a person. A request that fails validation also has issues, one per invalid field:

{
  "error": "invalid_request",
  "message": "The request is invalid: to.0: Invalid email address",
  "issues": [{ "path": "to.0", "message": "Invalid email address" }]
}

Some errors add fields, such as the suppressed recipients or when a limit resets. Each operation’s page in the reference lists them.

Two refusals are plain text, not JSON: 429 Too Many Requests and 413 Request Entity Too Large. They come from the proxy in front of the API, as Limits explains. A body over the size limit meets the proxy first, so it gets the plain-text 413.

Retry or fix

  • rate_limited (429): wait the seconds in retryAfter, which the Retry-After header also gives, then send again. The refused message was not stored. If you meet this limit often, ask support to raise it.
  • sending_disabled (503): sending is disabled for everyone for the moment. Retry after the seconds in the Retry-After header.
  • monthly_cap_reached (429): wait until resetsAt, or have an administrator of your account raise the cap under Account › Usage. Limits says how far they can raise it.
  • A 5xx status: retry with a growing delay, under the same idempotency key so the message cannot go twice.
  • Any other code: read its meaning in the table below. Most need a fix to the request or the account, and fail the same way if sent again unchanged.

On rate_limited and monthly_cap_reached, scope says whose limit it is: the account’s (account), the server’s own (server) or the test servers’ (test). Test mail never counts toward the account’s monthly cap. A webhook can warn you before the cap is used up.

The SDK throws SendoraError for every failed call. It has the same code, the HTTP status and the body’s extra fields, such as issues, retryAfter, scope, limit, cap, used, resetsAt, suppressed and existingId. retryable says whether trying again could succeed. A lost connection or a timeout is the same error, with connection_failed or timeout and no status.

The SDK retries rate limits, sending_disabled and server errors itself for a few seconds. If retryAfter is longer than that, it throws at once so you can schedule the retry.

The SDK raises a SendoraError for every failed call. Its class depends on the status, such as RateLimitError for 429. It has the same code, the HTTP status and the body’s extra fields, such as issues, retry_after, scope, limit, cap, used, resets_at, suppressed and existing_id. retryable says whether trying again could succeed. A lost connection is an APIConnectionError with connection_failed, and a timeout an APITimeoutError with timeout, both without a status.

The SDK retries rate limits, sending_disabled and server errors itself for a few seconds. If retry_after is longer than that, it raises at once so you can schedule the retry.

When an account cannot send

  • payment_required (402): the account has no active payment, for instance before a card is saved. Activate payment under Account › Billing in the dashboard. Every send from a live server fails until then.
  • account_not_active (403): Sendora has not approved the account yet. Every account is reviewed by hand before a live server can send. A test server works before that.
  • account_paused (403): Sendora has paused the account, for instance after a failed payment. Support tells you why. A pause for an unpaid subscription lifts once you save a working card under Account › Billing.

Over SMTP, the same refusals arrive as SMTP replies, which SMTP submission lists.

Error codes

CodeStatusMeaning
account_not_active403The account is closed, or not yet approved for a live server's sends or an inbound stream.
account_paused403The account is paused by Sendora.
broadcast_not_open409The broadcast is completed or cancelled already; nothing is left to cancel.
content_expired410The content of this received message is gone; the stream’s content window has passed. The message itself stays 13 months.
content_unreadable409The stored content of this received message cannot be opened. Sendora has been told.
default_stream409The default stream of a server cannot be archived.
domain_exists409The account already has that domain.
domain_reserved400The name is Sendora’s own, or a return-path host; no account can receive on it.
from_domain_not_verified422The From domain is not a verified sending domain of this server.
idempotency_key_mismatch422The Idempotency-Key was already used for a different request.
idempotency_key_required400Batch sends need an Idempotency-Key header.
inbound_domain_exists409The stream already has a domain, or another account holds the name verified.
inbound_stream_exists409A server has one inbound stream at a time; archive it to make another.
invalid_request400The body, the query or a header does not match what the route takes.
last_secret409The only live secret of a webhook cannot be deleted; create another first.
last_token409This is the only live key of the server; create another before revoking it.
list_unsubscribe_reserved422On a broadcast stream Sendora writes the List-Unsubscribe pair itself, so a message may not carry one.
mode_immutable400A server’s mode is fixed when it is created; create a new server for the other mode.
monthly_cap_reached429The monthly cap of the account, the server or the test servers is used up.
not_found404No such broadcast of this server.
payment_required402The account has no active subscription.
rate_limited429More was sent within a minute than the limit allows.
recipient_suppressed422One or more recipients are on the suppression list of the stream.
request_too_large413The body exceeds 10 MB.
secret_limit409The webhook already holds two live secrets: one in use and one to roll to. Delete one before creating another.
sending_disabled503Sending is disabled for everyone for the moment; retry after the seconds in Retry-After.
server_exists409The account already has a server with that name.
server_in_flight409Messages of the server are still being delivered; try again once they have left.
server_limit409The account has as many servers as it may; Sendora raises the cap on request.
server_reserved409This server sends Sendora’s own sign-in mail and cannot be renamed or removed.
spam_complaint_locked403The recipient reported a message as spam; only Sendora support lifts that, at the recipient’s own request.
stream_archived422The stream is archived and takes no new messages.
stream_exists409The server already has a stream with this name, archived or not.
stream_kind_not_allowed422A test server has no inbound stream; receive on a live server.
stream_not_broadcast422A broadcast goes on a broadcast stream; the streamId names a transactional one.
stream_not_found422The streamId names no stream of this server.
stream_not_sendable422An inbound stream receives mail; it takes no messages and has no suppression list. Name a transactional or broadcast stream.
stream_paused422Sendora has paused the stream after complaints; it takes no messages until support has resumed it.
substitution_missing422The subject, text or HTML names a {{ key }} that a message does not give.
test_address_on_live_server422Addresses at simulator.sendora.se act out an outcome on a test server; a live server never sends to them.
token_limit409The server or the account already holds two live keys: one in use and one to rotate to. Revoke one before creating another.
unauthorized401The key is missing, malformed or revoked.
unsubscribe_locked403The recipient unsubscribed themselves; only Sendora support lifts that, at the recipient’s own request.
unsubscribe_placeholder_missing422A message on a broadcast stream must carry {{ unsubscribe_url }} in every part it has.
webhook_exists409The server already has a webhook for that URL.
wrong_token_kind403The key is of the other kind: a server key (sk_) where an account key (ak_) is needed, or the reverse. The message names the kind the operation takes.