Guides

Errors

Every error has the same shape: a stable code for your program and a sentence for the person reading the log.

The shape

A request that did not succeed is answered with a 4xx or 5xx status and a JSON body. Branch on error, which does not change. Show or log message, which is written for a person and may be reworded.

400 Bad Request
{
  "error": "bad_request",
  "message": "The body is not valid JSON."
}

Status codes

StatuserrorMeansRetry
400bad_requestThe request is not valid: the body is not JSON, a field is missing or wrong. The message says which and why.Fix, do not retry
401unauthorizedNo key, a key in the wrong shape, or a key that is not valid any more.Fix, do not retry
402payment_requiredThe account’s plan does not include this, or a plan limit applies. The message says what is needed.Fix, do not retry
403forbiddenThe key does not hold the scope this operation needs; the message names it. Also a role that may not do this.Fix, do not retry
404not_foundThere is no such endpoint, or no such object in this account.Fix, do not retry
409conflictThe request conflicts with the current state, such as a domain that is not set up for sending yet.Fix, do not retry
409refusedA guard said no: a recipient who bounced, unsubscribed or complained, or sending stopped for the account. Retrying cannot help.Fix, do not retry
413too_largeThe request body is over 2 MB.Fix, do not retry
429rate_limitedToo many requests for this key in a short time. The retry-after header says how many seconds to wait.After retry-after
429too_manyAn account limit that frees up with time, such as the hourly transactional allowance. The message says which.After retry-after
500internalSomething went wrong on our side.With backoff
502upstreamSomething went wrong on our side, or with a provider behind us.With backoff
502ambiguousA provider may or may not have taken a message. It is recorded as not sent; check the log before sending again.Check first
503not_configuredThe API is not switched on for this deployment.With backoff

A refusal is not a failure

The difference matters. A failure (500, 502, 503) means we tried and something broke, so a retry later may work. A refusal (refused, and every other 4xx except 429) means a rule said no, and sending the same request again will be refused again. Fix the request, or tell the person.

Retries that cannot send twice

Give each message an idempotencyKey when you send email. A retry with the same key returns the first result, so a timeout on your side can be retried safely.

Handling errors

Node
const res = await fetch(url, init);
if (!res.ok) {
  const { error, message } = await res.json();
  if (res.status === 429) {
    const wait = Number(res.headers.get('retry-after') ?? 1);
    // wait that many seconds, then try again
  }
  throw new Error(`Rykon Mail ${res.status} ${error}: ${message}`);
}

A response may carry an x-request-id header. Include it when you write to us about a request; it finds the request in our logs.