> ## Documentation Index
> Fetch the complete documentation index at: https://developers.telnyx.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Error Codes

> Reference for all Telnyx Email API error codes, HTTP status codes, and troubleshooting guidance.

<Callout type="info">
  This page is a reference for error codes returned by the Telnyx Email API. It covers two distinct families: **synchronous HTTP errors** returned on the API request itself, and **asynchronous delivery errors** (the 30xxx taxonomy) reported later via webhooks and detail records. They are not interchangeable — a 30xxx code never appears in an HTTP response body, and an HTTP code never appears in `error_evidence`. For endpoint-specific errors, see the response examples in each endpoint's API reference.
</Callout>

## Error response format

Most errors follow the standard Telnyx v2 error shape (exceptions: batch errors use `{index, code, message}` — see [Batch-specific errors](#batch-specific-errors) — and some template render errors use `{code, message}`):

```json theme={null}
{
  "errors": [
    {
      "code": "10015",
      "title": "Validation Failed",
      "detail": "subject can't be blank"
    }
  ]
}
```

| Field    | Description                                    |
| -------- | ---------------------------------------------- |
| `code`   | Machine-readable error code (see table below). |
| `title`  | Short human-readable summary.                  |
| `detail` | Specific error message with context.           |

## Request and message size limits

Size failures are a common source of confusion because three different ceilings apply at three different layers. They are not the same number.

| Limit                                              | Value  | Enforced by               | Failure mode                                                                                                                                                      |
| -------------------------------------------------- | ------ | ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Message body (`html_body` + `text_body`, decoded)  | 1 MB   | Email API                 | `422` with code `10015`, title "Bad Request", detail "body exceeds size limit (maximum 1 MB)"                                                                     |
| Total message (decoded body + decoded attachments) | 25 MB  | Email API                 | `422` with code `10015`, title "Bad Request", detail "message exceeds size limit (maximum 25 MB)"                                                                 |
| HTTP request body                                  | 150 MB | Phoenix request parser    | `413` with code `10015`                                                                                                                                           |
| Idempotency-keyed replay capture                   | 8 MB   | Telnyx Edge (API gateway) | `413 Payload Too Large` — an idempotency-keyed request over 8 MB is rejected at Edge before reaching the Email API. Unkeyed requests are not subject to this cap. |

<Callout type="warning">
  **8 MB is not a request-body limit.** It is the Edge gateway's `request_body_cap_bytes` for **idempotency-keyed replay** only. A keyed request over 8 MB is rejected at the Edge with `413 Payload Too Large` — it never reaches the Email API. Unkeyed requests bypass this cap entirely. The limits that actually reject a message body are the 1 MB body and 25 MB total enforced by the Email API, which return `422`.
</Callout>

Attachments are base64-encoded in the request, so a 25 MB message occupies roughly 33 MB on the wire. The Email API measures **decoded** bytes.

## HTTP status codes

| Status | Meaning               | When                                                                                                                                                      |
| ------ | --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 200    | OK                    | Successful request or idempotent replay.                                                                                                                  |
| 201    | Created               | Resource created (e.g. template creation).                                                                                                                |
| 202    | Accepted              | Email accepted for sending (queued).                                                                                                                      |
| 207    | Multi-Status          | One or more batch items failed, including an all-failed batch.                                                                                            |
| 400    | Bad Request           | Validation error, malformed body, or invalid `Idempotency-Key` header.                                                                                    |
| 401    | Unauthorized          | Missing or invalid API key.                                                                                                                               |
| 403    | Forbidden             | Domain not verified/suspended/degraded, sender address not allowed, shared domain is read-only, or the account is inactive.                               |
| 404    | Not Found             | Resource (email, template, domain) not found.                                                                                                             |
| 409    | Conflict              | A request with the same `Idempotency-Key` is still being processed, or a resource conflict (e.g. deleting an unsubscribe group with active suppressions). |
| 413    | Payload Too Large     | Request body exceeds the 150 MB Phoenix parser ceiling. Responses use code `10015` with detail "The request payload exceeds the maximum allowed size".    |
| 422    | Unprocessable Entity  | Changeset validation error, message/body size limit exceeded, idempotency fingerprint conflict, or all recipients suppressed.                             |
| 429    | Too Many Requests     | Daily send limit exceeded, or sending suspended due to poor domain reputation.                                                                            |
| 500    | Internal Server Error | Unexpected server error.                                                                                                                                  |
| 503    | Service Unavailable   | Upstream dependency or Edge idempotency protection unavailable.                                                                                           |

## Error code reference

<Callout type="info">
  Every code in this section is a **synchronous** error — returned in the HTTP response to your API request. Delivery failures that happen after a `202 Accepted` use the separate [30xxx taxonomy](#asynchronous-delivery-errors-30xxx).
</Callout>

### 400 — Bad Request

| Code    | Title                           | Cause                                                                                                                                                           |
| ------- | ------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `10015` | Bad Request / Validation Failed | Missing or invalid fields in the request body (e.g. `email is required`, `subject must be a non-empty string`).                                                 |
| `10015` | Invalid Idempotency-Key         | The `Idempotency-Key` header is empty, duplicated, malformed, or exceeds the length limit. Generate a UUID v4 and pass it as a single header, not a body field. |

### 401 — Unauthorized

| Code    | Title          | Cause                                                                                 |
| ------- | -------------- | ------------------------------------------------------------------------------------- |
| `10006` | Not authorized | The `Authorization` header is missing or the API key is invalid, expired, or revoked. |

### 403 — Forbidden

| Code    | Title            | Cause                                                                                                                                                                                                                                                            |
| ------- | ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `10007` | Forbidden        | Domain is not verified, suspended, degraded, or missing DKIM — or the `from` address is not authorized on the sending domain.                                                                                                                                    |
| `10007` | Forbidden        | The request requires the `email:override` scope (used to send to a suppressed recipient) and your API key does not have it.                                                                                                                                      |
| `20012` | Account inactive | The account has been deactivated — commonly out of funds. This is a billing state, not a domain or permission problem. Resolve the account balance before retrying.                                                                                              |
| `10008` | Forbidden        | Attempted to `PATCH`, `DELETE`, or `POST …/verify` on a shared domain you don't own. Shared domains are read-only for non-owners. **Owned by the email-domains service** — see the caveat under [Codes owned by other services](#codes-owned-by-other-services). |

### 404 — Not Found

| Code    | Title     | Cause                                                                                                                                                                                                         |
| ------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `10001` | Not Found | Email message, template, or domain not found. Check the ID and your account scope. Both explicit application 404s and framework-level routing 404s return `10001` — there is no string `"404"` fallback code. |

### 409 — Conflict

| Code    | Title                       | Cause                                                                                                                                                                                                                                                |
| ------- | --------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `10036` | Resource is being processed | A request with the same `Idempotency-Key` is **still being processed**. Retry later with the same key and the same request body.                                                                                                                     |
| `40901` | Conflict                    | Deleting an unsubscribe group that has active suppressions (without `?force=true`). Remove the suppressions first or pass `force=true`. **Owned by the email-blocks service** — see [Codes owned by other services](#codes-owned-by-other-services). |

### 422 — Unprocessable Entity

| Code                   | Title                | Cause                                                                                                                                                                                                 |
| ---------------------- | -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `10015`                | Validation Failed    | Changeset validation error (e.g. `subject can't be blank`, invalid `webhook_url`, bad address format).                                                                                                |
| `10015`                | Bad Request          | Message size limit exceeded — 1 MB decoded body or 25 MB total message. See [Request and message size limits](#request-and-message-size-limits).                                                      |
| `10027`                | Idempotency Conflict | The `Idempotency-Key` was already used for a **different** request body. Use a new key for a new request; reuse keys only for exact retries.                                                          |
| `recipient_suppressed` | Recipient Suppressed | All recipients are suppressed — the email was not sent. The response includes a top-level `suppressed` array alongside `errors`, each entry carrying `to`, `reason`, `scope`, and `override_allowed`. |

### 429 — Too Many Requests

| Code                   | Title             | Cause                                                                                                                                                                                                                  | Retry?                                                              |
| ---------------------- | ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------- |
| `10011`                | Too Many Requests | The account's **daily send limit** was exceeded. The detail names the limit and states that it resets at midnight UTC. The send is rejected before message creation — no message record, no billing, no MTA injection. | Yes, after the daily reset.                                         |
| `reputation_suspended` | Sending Suspended | The sending domain's reputation band is `poor` — sending is suspended until reputation recovers. Rejected before message creation.                                                                                     | **No.** See [Deliverability](/docs/messaging/email/deliverability). |

<Callout type="warning">
  **Do not treat every `429` as a transient rate limit.** Branch on the error code, not the status. `10011` clears on its own at midnight UTC. `reputation_suspended` does **not** clear by waiting — retrying it in a backoff loop will never succeed and worsens the reputation signal. Stop sending on that domain and remediate the underlying bounce/complaint rates first.
</Callout>

### 500 — Internal Server Error

| Code    | Title                 | Cause                                                                                                                                           |
| ------- | --------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| `10019` | Internal Server Error | Unexpected server-side error (e.g. `Failed to create batch validation`). Retry with the same `Idempotency-Key`. If persistent, contact support. |
| `10000` | Internal Server Error | Returned by specific internal-failure paths, including a failed GDPR email-data deletion and email trace retrieval. Same handling as `10019`.   |
| `500`   | Internal Server Error | Framework fallback error response. Same handling as `10019`.                                                                                    |

### 503 — Service Unavailable

| Code    | Title               | Cause                                                                                                                                                    |
| ------- | ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `10016` | Service Unavailable | An upstream dependency (e.g. the email domain service) or Edge idempotency protection is temporarily unavailable. Retry with the same `Idempotency-Key`. |

### WebSocket upgrade errors

`GET /email_events/ws` is an upgrade endpoint, so it returns HTTP errors before the socket is established:

| Code    | Title             | Cause                                                                                                           |
| ------- | ----------------- | --------------------------------------------------------------------------------------------------------------- |
| `10002` | Invalid parameter | The `inbox_id` query parameter is not a valid UUID.                                                             |
| `10002` | Upgrade Required  | The request did not present a valid WebSocket upgrade. Connect with a WebSocket client, not a plain HTTP `GET`. |

## Asynchronous delivery errors (30xxx)

<Callout type="info">
  A `202 Accepted` means Telnyx accepted the message for sending — **not** that it was delivered. Delivery failures are reported asynchronously through [webhooks](/docs/messaging/email/webhooks-events) and detail records, using this separate 30xxx taxonomy. These codes never appear in an HTTP response body.
</Callout>

Raw SMTP codes are gateway implementation detail: they vary per remote mail server, they collide across unrelated failure modes, and they are absent entirely for failures that never reached SMTP (queue expiry, suppression, gateway rejection). Telnyx therefore normalizes every delivery failure to a product-level 30xxx code, and preserves the raw SMTP status alongside it for triage.

| Code    | Title                | Source | Retryable | Meaning                                                                                                                                                                                                                                                                                                                                                      |
| ------- | -------------------- | ------ | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `30001` | Hard bounce          | `smtp` | No        | The receiving server permanently rejected the recipient (5xx, or a bounce with no usable code). Remove the address.                                                                                                                                                                                                                                          |
| `30002` | Deferred             | `smtp` | **Yes**   | Temporary failure (4xx). The Telnyx MTA retries automatically — do **not** resubmit the message.                                                                                                                                                                                                                                                             |
| `30003` | Injection failure    | `mta`  | No        | The outbound MTA refused the injection and returned an SMTP code.                                                                                                                                                                                                                                                                                            |
| `30004` | Suppressed recipient | `api`  | No        | The recipient was on a suppression list. **Note:** In the current send-time suppression path, suppressed recipients are removed before persistence and never receive an `email_recipients` row or an EDR. `30004` is defined in the classifier but not currently emitted by the public send-time path. It may appear in future suppression-lifecycle events. |
| `30005` | Queue expiry         | `mta`  | No        | The message exhausted its retry window without being delivered. The recipient's status becomes `expired`.                                                                                                                                                                                                                                                    |
| `30006` | Gateway rejection    | `api`  | No        | The send was rejected before reaching the outbound queue. Not billable.                                                                                                                                                                                                                                                                                      |
| `30099` | Internal error       | `api`  | No        | An internal failure occurred before anything reached the MTA.                                                                                                                                                                                                                                                                                                |

### `error_evidence` structure

Failure events carry the normalized contract as `error_evidence`:

```json theme={null}
{
  "error_evidence": {
    "code": "30001",
    "message": "550 5.1.1 <user@example.com>: Recipient address rejected",
    "enhanced_code": "5.1.1",
    "source": "smtp",
    "smtp_status": 550,
    "retryable": false
  }
}
```

| Field           | Type            | Description                                                                                                                        |
| --------------- | --------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| `code`          | string          | The normalized 30xxx code from the table above. Never a raw SMTP status.                                                           |
| `message`       | string \| null  | The raw SMTP response text, when the failure reached SMTP.                                                                         |
| `enhanced_code` | string \| null  | DSN enhanced status code (e.g. `5.1.1`), when the remote supplied one.                                                             |
| `source`        | string          | Where the failure was observed: `smtp`, `mta`, or `api`.                                                                           |
| `smtp_status`   | integer \| null | The raw SMTP status as an integer. `null` for failures that never reached SMTP — queue expiry, suppression, and gateway rejection. |
| `retryable`     | boolean         | Whether resubmitting could succeed. Only `30002` is `true`.                                                                        |

The same normalized error is also published in array form as `errors[]`, where each entry adds a human-readable `title` and renames `message` to `detail`. See [Error evidence on failure events](/docs/messaging/email/webhooks-events#error-evidence-on-failure-events).

<Callout type="warning">
  The webhook event type does not uniquely identify the failure. An ordinary bounce, a queue expiration, an administrative bounce, and an out-of-band bounce all publish `email.bounced`. Branch on `error_evidence.code` — for example `30001` versus `30005` — and read the recipient `status` for the authoritative outcome. `bounce_category` is an internal field: it is not part of normal recipient-scoped webhook payloads and is not written to normal recipient-scoped stored events. A legacy message-scoped fallback path may persist it in stored events, and the Events API sanitizer does not explicitly strip it. Do not build consumer logic that reads `bounce_category` from any public surface.
</Callout>

## Idempotency-specific errors

When using the `Idempotency-Key` header, idempotency is enforced at the Telnyx Edge (API gateway) before the request reaches the Email API. The same key can produce **three distinct outcomes**, and they must be handled differently:

| Outcome                    | HTTP           | Code            | What happened                                                                                                                                                                        | Action                                                                                                                                        |
| -------------------------- | -------------- | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------- |
| Original still in flight   | 409            | `10036`         | The first request with this key is **still being processed**. No second send occurred.                                                                                               | Wait and retry with the **same** key and the **same** body. Do not generate a new key — that would send twice.                                |
| Same key, different body   | 422            | `10027`         | The key was already used with a **different request fingerprint**. The new request was rejected without sending.                                                                     | Generate a new key. Reuse a key only for byte-identical retries of the same logical request.                                                  |
| Original already completed | *original 2xx* | *original body* | The first request finished. The gateway **replays its stored response verbatim** — including the original status and body — and adds an `idempotent-replayed: true` response header. | Nothing. The message was sent exactly once. Check for the `idempotent-replayed` header if you need to distinguish a replay from a fresh send. |

Two further failure modes are specific to the idempotency layer itself:

| Code    | HTTP | Cause                                             | Action                                                                                                   |
| ------- | ---- | ------------------------------------------------- | -------------------------------------------------------------------------------------------------------- |
| `10015` | 400  | Malformed key (empty, duplicate header, overlong) | Generate a valid UUID v4 and pass it as a single `Idempotency-Key` header.                               |
| `10016` | 503  | Idempotency protection unavailable (fail-closed)  | Retry the same request with the same key. The gateway fails closed rather than risking a duplicate send. |

<Callout type="warning">
  A replay only works while the gateway holds the stored response. Requests whose body exceeds the Edge 8 MB replay cap are rejected at the Edge with `413 Payload Too Large` before reaching the Email API — see [Request and message size limits](#request-and-message-size-limits).
</Callout>

## Batch-specific errors

Batch requests (`POST /email_messages/batch`, up to 50 messages) return `207 Multi-Status` when one or more messages fail — including an all-failed batch — with per-message errors:

```json theme={null}
{
  "data": [/* successful messages */],
  "errors": [
    {
      "index": 1,
      "code": "forbidden",
      "message": "Domain is not verified"
    }
  ],
  "meta": {
    "total": 2,
    "failed": 1,
    "succeeded": 1
  }
}
```

Each batch error entry has `index` (position in your `messages` array), `code`, and `message`:

| Error code             | Cause                                                                                                                                                                                                                                                         |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `bad_request`          | Malformed message entry, missing required fields, a template render error, or a field validation failure for that message.                                                                                                                                    |
| `validation_error`     | Field validation failed for that message.                                                                                                                                                                                                                     |
| `unprocessable_entity` | That message exceeded a size limit — either the 1 MB decoded body or the 25 MB total message ceiling. Note this is a **per-message** failure: the rest of the batch still processes. See [Request and message size limits](#request-and-message-size-limits). |
| `forbidden`            | Domain not verified or from-address not allowed.                                                                                                                                                                                                              |
| `not_found`            | Template ID or domain not found (for template sends).                                                                                                                                                                                                         |
| `recipient_suppressed` | All recipients of that message are suppressed.                                                                                                                                                                                                                |
| `reputation_suspended` | Sending suspended for the domain's reputation band.                                                                                                                                                                                                           |
| `service_unavailable`  | Upstream dependency unavailable for that message.                                                                                                                                                                                                             |

## Codes owned by other services

The Telnyx email product spans several services. A few codes documented here are returned by services **other than** the Email API, which means their exact status, code, and detail can change independently of this page. Verify these against the owning service's API reference before depending on the precise shape:

| Area                                                               | Owning service       | Notes                                                                                                           |
| ------------------------------------------------------------------ | -------------------- | --------------------------------------------------------------------------------------------------------------- |
| Shared-domain mutation (`10008`)                                   | telnyx-email-domains | Returned as `403 Forbidden` when mutating a shared domain you don't own.                                        |
| Unsubscribe-group deletion conflict (`40901`)                      | email-blocks         | Returned by the suppressions/unsubscribe-group surface.                                                         |
| `/v2/email_domains` troubleshooting and domain verification errors | telnyx-email-domains | Domain registration, DNS record generation, verification, and drift monitoring all live in the domains service. |
| Webhook CRUD (`/email_domains/{domain_id}/webhooks`)               | telnyx-email-domains | The Email API only resolves webhook targets at publish time; it does not serve these routes.                    |

<Callout type="warning">
  **Error codes are not globally unique across Telnyx email services.** `10008` is the clearest example: the email-domains service returns it as a `403 Forbidden` for shared-domain mutation, while the Email API returns the same code as a `503 Service Unavailable` when diagnostics authentication is unavailable. Always interpret a code together with **both** the HTTP status and the endpoint that returned it — never on the code alone.
</Callout>

## Troubleshooting

### "Domain is not verified"

1. Check `GET /v2/email_domains` to see the domain status.
2. Ensure all required DNS records — ownership and DKIM, plus MX if `inbound_enabled` is true — are published and match the records returned by `GET /v2/email_domains/{domain_id}/dns_records`.
3. Call `POST /v2/email_domains/{domain_id}/verify` after DNS propagates.
4. For a controlled zero-setup test, use `onboarding@<shared-domain>` and send only to the account owner's verified email address.

### "sender address is not allowed"

The `from` address must be on a verified domain you own. Shared-domain sends must use `onboarding@<shared-domain>` and can target only the account owner's verified email address.

### "Idempotency-Key header is invalid"

* Generate a UUID v4 (`uuidgen` or `crypto.randomUUID()`).
* Pass it as an HTTP header: `Idempotency-Key: 8e03978e-40d5-43e8-bc93-6894a57f9326`.
* Do **not** include it in the JSON body — it's a header only.
* One key per logical request. Reuse only for exact retries.

### Sending suspended (`reputation_suspended`)

Your domain's reputation band dropped to `poor` — usually from high bounce or complaint rates. See [Deliverability and Domain Warm-up](/docs/messaging/email/deliverability) for recovery guidance.

### Choosing a retry strategy

Retry decisions belong on the **error code**, not the HTTP status. Two errors that share a status can need opposite handling.

| Error                                    | Status        | Retry? | How                                                                                                                         |
| ---------------------------------------- | ------------- | ------ | --------------------------------------------------------------------------------------------------------------------------- |
| `10011` (daily send limit)               | 429           | Yes    | The limit resets at midnight UTC. Queue the work locally and resume after the reset — backing off in seconds will not help. |
| `reputation_suspended`                   | 429           | **No** | Waiting does not clear it. Stop sending on that domain, reduce bounce and complaint rates, then confirm the band recovered. |
| `10036` (idempotency in flight)          | 409           | Yes    | Retry with the **same** key and body. A new key would send a duplicate.                                                     |
| `10027` (idempotency fingerprint)        | 422           | No     | The body differs from the original. Fix the body or use a new key.                                                          |
| `10016` (service unavailable)            | 503           | Yes    | Retry the same request with the same `Idempotency-Key`, with exponential backoff.                                           |
| `10019` / `10000` / `500`                | 500           | Yes    | Retry with the same `Idempotency-Key`. Contact support if persistent.                                                       |
| `10015` (validation / size)              | 400, 413, 422 | No     | The request is malformed or too large. Retrying it unchanged always fails.                                                  |
| `10007` / `20012` (forbidden / inactive) | 403           | No     | Fix domain verification, sender authorization, scope, or account balance first.                                             |
| `recipient_suppressed`                   | 422           | No     | Every recipient is suppressed. Remove them or send to different addresses.                                                  |

<Callout type="warning">
  Never retry a `4xx` other than `409` and `429` without changing the request — the outcome is deterministic. And when you do retry a send, always reuse the original `Idempotency-Key` so a retry that races a slow success cannot deliver the message twice.
</Callout>

### Delivery failed after a `202 Accepted`

A `202` only confirms acceptance for sending. If the message never arrived, the failure is asynchronous — look at the [30xxx delivery error](#asynchronous-delivery-errors-30xxx) on the recipient's webhook or event, not at the HTTP response. Start with `error_evidence.code`:

* `30001` — permanent rejection. Remove the address.
* `30002` — temporary; Telnyx is already retrying. Do not resubmit.
* `30004` — the recipient was suppressed before any attempt.
* `30005` — retries were exhausted; the recipient status is `expired`.
