> ## 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.

# Email Deliverability Best Practices

> Understand email deliverability — how receiving providers evaluate your mail, why messages land in spam, and how to interpret Telnyx delivery error codes when things go wrong.

<Callout type="info">
  This page explains **why** deliverability works the way it does — the mental model behind inbox placement, and how to read Telnyx delivery signals when mail fails. For step-by-step setup instructions, see [Deliverability and Domain Warm-up](/docs/messaging/email/deliverability).
</Callout>

Deliverability is not a feature you enable. It is a judgment that thousands of independent receiving systems make about your mail, message by message, using signals you control only indirectly. Understanding *how* that judgment is formed is what lets you diagnose a problem instead of guessing at it.

## How receiving providers evaluate mail

No mailbox provider publishes its filtering algorithm, but the evaluation consistently happens across four layers. A message must satisfy all four — strength in one layer does not compensate for failure in another.

### Authentication: proving the mail is really yours

Authentication answers a single question: *is this sender permitted to use this domain?* Providers check three mechanisms, and each proves something different.

**SPF** authorizes sending IPs. Your domain publishes a DNS record naming which servers may send on its behalf, and the receiver compares the connecting IP against that list. SPF alone is fragile — it breaks when mail is forwarded, because the forwarding server isn't in your record.

**DKIM** proves message integrity. Telnyx signs each outbound message with a private key; the receiver fetches the matching public key from `<selector>._domainkey.<domain>` and verifies the signature. Because the signature travels with the message, DKIM survives forwarding where SPF does not. This is why DKIM is the load-bearing authentication mechanism for most senders.

**DMARC** ties the first two to the domain your recipient actually sees. SPF and DKIM validate technical identifiers that a recipient never reads. DMARC requires that one of them *aligns* with the visible `From:` domain, and tells receivers what to do when alignment fails.

<Callout type="warning">
  DMARC is the only mechanism that constrains the `From:` header your recipients see. A message can pass SPF and DKIM for an attacker-controlled domain while displaying your brand in the `From:` field — alignment is what closes that gap. This is why publishing SPF and DKIM without DMARC leaves the impersonation problem unsolved.
</Callout>

### Reputation: the accumulated record

Authentication proves identity. Reputation determines whether that identity is welcome. Providers maintain scores for both the sending IP and your authenticated domain, built from behavior over time.

The signals that matter most are the ones recipients generate: opening messages, replying, moving mail out of spam — and the negative ones, marking as spam or deleting unread. Bounce rate matters heavily because sending to addresses that don't exist is the clearest available evidence that a list was not built from genuine consent.

Reputation is asymmetric. It accrues slowly through consistent, wanted mail and collapses quickly after a bad send. It is also per-provider — a strong Gmail reputation tells Microsoft nothing.

### Content: what the message itself signals

Content filters look for the statistical fingerprint of unwanted mail: mismatches between subject line and body, link shorteners obscuring destinations, image-only messages carrying no analyzable text, malformed HTML, and missing plain-text alternatives.

The underlying logic is that legitimate senders have no reason to obscure what they're sending. Most content heuristics detect evasion rather than specific words.

### Infrastructure: whether the sending setup looks legitimate

Receivers check operational hygiene: does the sending IP have a reverse DNS (PTR) record that resolves back consistently? Does the domain have valid MX records? Is TLS offered? Is volume steady, or does it spike unpredictably?

Telnyx operates and maintains this layer for you. It matters to your mental model because it explains why a brand-new domain with perfect DNS still doesn't reach the inbox reliably: the infrastructure is trusted, but *your domain's* history on it is empty.

## The delivery lifecycle and where it fails

A Telnyx send passes through distinct stages, and knowing which stage failed determines what — if anything — you should do about it.

| Stage             | What happened                                           | Event              |
| ----------------- | ------------------------------------------------------- | ------------------ |
| Acceptance        | The API validated and persisted the message for sending | `email.queued`     |
| Handoff           | Accepted by the Telnyx outbound MTA                     | `email.sent`       |
| Delivery          | The remote MX returned SMTP success                     | `email.delivered`  |
| Transient failure | The remote MX returned a 4xx — will be retried          | `email.deferred`   |
| Permanent failure | The remote MX returned a 5xx                            | `email.bounced`    |
| Expiration        | Retries exhausted; the MTA gave up                      | `email.bounced`    |
| Complaint         | A feedback report (ARF) arrived from the provider       | `email.complained` |

<Callout type="info">
  **`email.queued` does not mean the message reached the MTA.** It fires as soon as the API has validated the request and persisted the message — before the message is produced to the internal queue and before the outbound MTA has seen it. **`email.sent` does not mean the recipient received the message** either: it means the MTA accepted the message for delivery. Remote acceptance is reported separately by `email.delivered`. Treating `queued` or `sent` as success is the most common source of inflated delivery numbers.
</Callout>

Two boundaries are worth internalizing:

**Deferred is not failure.** A 4xx response means "not now" — the remote server is throttling, greylisting, or temporarily unavailable. Telnyx retries automatically. Resubmitting a deferred message creates duplicates and worsens the throttling that caused the deferral.

**Delivered is not "in the inbox."** A 250 response means the receiving server accepted custody. Placement — inbox, promotions, or spam — happens after acceptance and is never reported back over SMTP. This is why provider-side tools like Google Postmaster Tools show you something your delivery events structurally cannot.

### Suppression: the send that never happens

When a recipient is on your suppression list, the message is not attempted at all — and the recipient is removed from the send *before* any per-recipient record exists. Telnyx suppresses automatically after a qualifying hard bounce or a spam complaint, so the platform enforces the list hygiene that protects your reputation.

Suppressed addresses are stripped from `to`, `cc`, and `bcc` during request handling, so they never get a recipient row and never produce a delivery attempt. If *every* recipient on the request is suppressed, the API rejects the request with `recipient_suppressed` and no message, recipient row, or detail record is created at all. The response includes a top-level `suppressed` array naming the blocked addresses.

## Understanding delivery error codes

Telnyx reports failures at two distinct levels. Confusing them sends you looking in the wrong place.

### API-level errors (10xxx)

These occur **before** a message exists — your request was rejected. They are about your API call, not about deliverability.

| Code                   | Meaning                         | Deliverability relevance                                                                                                                                                                                                                                                                |
| ---------------------- | ------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `10015`                | Validation failed / bad request | Malformed fields; nothing was sent                                                                                                                                                                                                                                                      |
| `10007`                | Forbidden                       | The Email API's generic Forbidden code. Covers domain not verified, suspended, degraded, missing an active DKIM key, a `from` address outside the domain's sending profile, trial-account recipient restrictions, and insufficient authorization scope. Read `detail` to find out which |
| `10008`                | Forbidden (shared domain)       | Returned by the email **domains** service when you try to modify, verify, or delete a Telnyx-managed shared domain. Shared domains are usable for sending by every account but read-only for non-owners                                                                                 |
| `10027`                | Idempotency conflict            | Same `Idempotency-Key` reused with a different body. Enforced at the API gateway, not by the Email service                                                                                                                                                                              |
| `10036`                | Still processing                | Same key, same body — the original request is in flight. Also gateway-enforced                                                                                                                                                                                                          |
| `reputation_suspended` | Sending suspended               | The domain's reputation band dropped to `poor`                                                                                                                                                                                                                                          |

<Callout type="info">
  `10007` is a service-wide code, not a domain-authentication signal. Several unrelated policy failures share it, so branching on the code alone will mislead you — always inspect the `detail` string (and, where present, `source`) to identify the actual cause before acting on it.
</Callout>

`10007` and `reputation_suspended` are the two that most often carry genuine deliverability meaning: the first frequently indicates your domain isn't verified or your `from` address isn't authorized, the second says your reputation has already degraded far enough that Telnyx stopped sending. See [Error Codes](/docs/messaging/email/error-codes) for the complete reference.

### Delivery-level errors

These occur **after** the message was accepted — the send happened and delivery failed.

**`bounce_category`** appears on `email.bounced` events and identifies *why* the bounce occurred:

| Value       | Origin                  | Meaning                                                                                                                      |
| ----------- | ----------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
| `permanent` | KumoMTA `Bounce` record | The MTA classified the failure as a permanent bounce. This is the record category, **not** proof that the address is invalid |
| `transient` | Retries exhausted       | Repeated 4xx responses; the MTA gave up                                                                                      |
| `admin`     | Operator-initiated      | Cancelled in flight — not a recipient-side signal                                                                            |
| `oob`       | Out-of-band report      | The remote MTA accepted, then asynchronously rejected                                                                        |

<Callout type="warning">
  `permanent` is the default category assigned to any `Bounce` record — it is derived from the record type, not from the SMTP code. A `permanent` bounce is therefore *not* the same thing as a confirmed invalid address, and it does not by itself mean the recipient was suppressed. Suppression is decided separately, by narrower enhanced-code rules, and it happens asynchronously.

  `transient` as a bounce category is likewise not the same as an `email.deferred` event. A deferral is still being retried. A `transient` bounce means retrying already happened and failed — the message is permanently undelivered even though every individual failure was temporary.
</Callout>

**`error_evidence`** is the structured error field on the Email Detail Record (EDR). It is populated for every error status — `bounced`, `failed`, `deferred`, `expired`, `suppressed`, and `gw_reject` — and is null for successful ones:

| Field           | Contents                                                                                                                                                 |
| --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `code`          | The **normalized Telnyx delivery error code** (`30001`–`30099`), as a string                                                                             |
| `message`       | The callback/MTA delivery detail text, when available. Usually the remote server's response, but operator-initiated failures carry Telnyx-generated text |
| `enhanced_code` | The enhanced SMTP status code in `X.Y.Z` form, when the remote server supplied one                                                                       |
| `source`        | Which layer produced the error: `smtp`, `mta`, `api`, `dns`, `dkim`, or `spam`                                                                           |
| `smtp_status`   | The **raw SMTP response code** as an integer (for example `550`, `421`), when the failure reached SMTP                                                   |
| `retryable`     | Whether Telnyx will retry this failure automatically                                                                                                     |

<Callout type="warning">
  `error_evidence.code` is always a normalized `30xxx` code — never a raw SMTP code. The raw SMTP response lives in `smtp_status`. If your integration matches on `code == "550"` it will never fire; match on `code == "30001"` and read `smtp_status` when you need the remote server's exact response.
</Callout>

#### Normalized delivery error codes (30xxx)

Raw SMTP codes are a poor integration surface: they vary per remote MX, 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 into a small product-level taxonomy.

| Code    | Title                | Source | Retryable | What it means                                                                            |
| ------- | -------------------- | ------ | --------- | ---------------------------------------------------------------------------------------- |
| `30001` | Hard bounce          | `smtp` | No        | The remote MX permanently rejected the recipient (5xx), or bounced without a usable code |
| `30002` | Deferred             | `smtp` | Yes       | A transient rejection (4xx). Telnyx is still retrying                                    |
| `30003` | Injection failure    | `mta`  | No        | The outbound MTA refused the injection                                                   |
| `30004` | Suppressed recipient | `api`  | No        | The recipient was on a suppression list                                                  |
| `30005` | Queue expiry         | `mta`  | No        | Retries were exhausted and the MTA gave up                                               |
| `30006` | Gateway rejection    | `api`  | No        | The message was refused before entering the queue                                        |
| `30099` | Internal error       | `api`  | No        | The send failed before anything reached the MTA                                          |

The status is the primary axis of classification; the SMTP code only disambiguates where one status covers both a permanent and a transient failure. A `bounced` recipient carrying a 4xx response is classified `30002` (retryable) rather than `30001`, because the remote server's own code says the rejection was temporary.

The `message` field is the most diagnostically useful and the least structured. It carries whatever delivery detail the callback supplied, and its provenance depends on who generated the failure: an SMTP rejection carries the receiving provider's own free text — frequently a URL explaining a block, or a specific reason the numeric code cannot convey — while an operator-initiated failure such as an admin bounce carries Telnyx-generated text instead. Do not assume the string came from the remote host. Failures that never produced a callback at all — suppression and gateway rejection — have no delivery detail to carry, so `message` is null there rather than fabricated.

**Enhanced codes drive suppression.** Telnyx uses the `X.Y.Z` class — not the `30xxx` code — to decide whether a bounce reflects a bad address or a policy decision. Codes in the `5.1.x` (bad destination address) and `5.2.x` (mailbox status — disabled, full) ranges indicate a recipient-side problem and qualify for auto-suppression. Codes in the `5.7.x` range are security and policy rejections and are *not* suppressed, because the address may be perfectly valid while your sending configuration is not. A bounce with no enhanced code at all falls back to the raw SMTP code: 5xx qualifies for suppression, anything else does not.

That distinction matters when you are diagnosing a spike in bounces. A wave of `5.1.1` responses means your list has decayed. A wave of `5.7.x` responses means the receiver is rejecting your mail on security or policy grounds and the recipients themselves are probably fine.

### Record status values

The EDR carries a `status` field describing the recipient's delivery outcome. For outbound mail the possible values are `queued`, `sending`, `sent`, `deferred`, `delivered`, `bounced`, `failed`, `expired`, `suppressed`, `cancelled`, and `gw_reject`. Inbound records use `received` and `delivered`.

Three values are easy to misread. `failed` means a sender-side or operator-side non-delivery — the remote MX never rejected the recipient — which is why an admin bounce maps to `failed` rather than `bounced`. `expired` is its own terminal status, not a flavor of `failed`: it means the MTA exhausted its retries and gave up (`30005`). `gw_reject` means the message was refused before entering the queue, and is never billable.

#### `injection_timeout`: a recipient state with no detail record

`injection_timeout` is a recipient and webhook state, **not** an EDR status. It occurs when the injection request to the MTA times out ambiguously — the MTA may or may not have accepted the recipient, and Telnyx cannot tell which. Retrying would risk a duplicate send, so the recipient is parked in this terminal state instead.

Because the outcome is genuinely unknown, **no detail record is emitted**. The recipient enters a terminal `injection_timeout` state. Note: `injection_timeout` is not currently available as a subscribable webhook event type. If the MTA did in fact accept the message, its later Reception callback reconciles the recipient to `sent` and a detail record is published then. If no callback ever arrives, the recipient stays in `injection_timeout` and no EDR is ever produced.

<Callout type="warning">
  Reconcile on the webhook, not on the absence of a record. An `injection_timeout` webhook with no corresponding EDR does not mean the message failed — it means the outcome is not yet known. Treat the delivery outcome as unknown and do not retry — a later `sent` transition may reconcile the state, but if no callback arrives, `injection_timeout` remains terminal and no definitive delivery outcome is available.
</Callout>

## Best practices, and the reasoning behind them

**Authentication** — Publish SPF, DKIM, and DMARC, and verify your domain. DMARC is what makes the other two meaningful to a receiver evaluating the `From:` header your recipient reads.

**Reputation** — Increase volume gradually and keep bounce and complaint rates low. Providers evaluate rate, not count: a hundred bounces out of a hundred sends is catastrophic, while a hundred out of a million is unremarkable.

**Content** — Always send a plain-text alternative alongside HTML, keep subject lines honest, and avoid obscuring links. Filters are detecting evasion; give them nothing to detect.

**List hygiene** — Remove hard bounces, honor unsubscribes including RFC 8058 one-click, and let suppression lists do their job. Every send to a dead address is evidence that your consent process is weak.

**Monitoring** — Subscribe to bounce and complaint webhooks *before* your first production send. Deliverability problems compound: by the time you notice degraded delivery without instrumentation, the reputation damage is already done.

## What to do when delivery fails

| Signal                                        | What it means                                        | Response                                                                                                                                                                                                                                               |
| --------------------------------------------- | ---------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `email.deferred`                              | Temporary 4xx; retries in progress                   | Nothing — do not resend. Persistent deferrals against one provider indicate throttling; reduce volume to that provider                                                                                                                                 |
| `bounce_category: permanent`                  | The MTA classified the failure as a permanent bounce | Check `error_evidence.enhanced_code` before concluding the address is bad. Qualifying bounces (`5.1.x`, `5.2.x`, or a 5xx with no enhanced code) are auto-suppressed asynchronously — a suppression may not be visible the instant the webhook arrives |
| `bounce_category: transient`                  | Retries exhausted                                    | Check whether the provider was throttling you; this is a volume or reputation signal                                                                                                                                                                   |
| `bounce_category: oob`                        | Accepted, then asynchronously rejected               | Treat as a hard bounce; the same enhanced-code suppression rules apply                                                                                                                                                                                 |
| `error_evidence.enhanced_code` starting `5.7` | Security or policy rejection                         | Not auto-suppressed. Read `error_evidence.message` to tell authentication failure apart from reputation or content policy, then fix the indicated cause                                                                                                |
| `email.complained`                            | A recipient reported the message as spam             | Auto-suppressed (asynchronously). Review consent, targeting, and frequency                                                                                                                                                                             |
| `reputation_suspended`                        | Reputation band dropped to `poor`                    | Sending is halted. Contact support after correcting list quality                                                                                                                                                                                       |

The general rule: **4xx means wait, 5xx means stop.** Telnyx handles the waiting for you, and handles the stopping by suppressing recipients whose bounces qualify under the enhanced-code rules above. Your job is the layer neither of those can address — understanding why the address was bad or the content unwanted in the first place.

## Related documentation

* [Deliverability and Domain Warm-up](/docs/messaging/email/deliverability) — setup and warm-up procedures
* [Error Codes](/docs/messaging/email/error-codes) — complete error reference
* [Webhooks & Events](/docs/messaging/email/webhooks-events) — event types and payloads
* [Domains & DKIM](/docs/messaging/email/domains) — domain registration and DNS records
* [Suppressions](/docs/messaging/email/suppressions) — suppression list behavior
