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

# SMTP Protocol Contract

> Connection settings, authentication, session limits, and the exact SMTP replies for sending email through the Telnyx SMTP relay at smtp.telnyx.com.

Send email through Telnyx over SMTP by configuring an SMTP client to use `smtp.telnyx.com`. This page lists the connection settings, the limits the relay enforces, and the exact reply the relay sends for each outcome.

## Connection settings

| Setting | Value |
| - | - |
| Host | `smtp.telnyx.com` |
| Port | `587` |
| Encryption | STARTTLS, required before authentication and before any message is accepted. Implicit TLS (port 465) is not offered. |
| Authentication | `AUTH PLAIN` or `AUTH LOGIN`, advertised only after STARTTLS |
| Username | The literal string `apikey` |
| Password | A Telnyx API v2 key |

The username is always `apikey`; it is not an account name or email address. The API key is the credential.

## Session walkthrough

`S:` lines are relay replies and `C:` lines are client commands. The greeting, the first `EHLO`, and `STARTTLS` are exchanged before encryption starts; the rest of the session runs over TLS. The relay advertises `AUTH` only in the `EHLO` response that follows a completed STARTTLS handshake, so send `EHLO` again after the handshake.

```text theme={null}
S: 220 smtp.telnyx.com ESMTP email_submission
C: EHLO client.example.com
S: 250-smtp.telnyx.com
S: 250-SIZE 25000000
S: 250-8BITMIME
S: 250-PIPELINING
S: 250-SMTPUTF8
S: 250 STARTTLS
C: STARTTLS
S: 220 OK
   (TLS handshake)
C: EHLO client.example.com
S: 250-smtp.telnyx.com
S: 250-SIZE 25000000
S: 250-8BITMIME
S: 250-PIPELINING
S: 250-SMTPUTF8
S: 250 AUTH PLAIN LOGIN
C: AUTH PLAIN <base64 of "\0apikey\0YOUR_API_KEY">
S: 235 2.7.0 Authentication successful
C: MAIL FROM:<sender@your-verified-domain.com>
S: 250 sender Ok
C: RCPT TO:<recipient@example.com>
S: 250 recipient Ok
C: DATA
S: 354 enter mail, end with line containing only '.'
C: (headers and body, then a line containing only ".")
S: 250 2.0.0 Ok: queued as 0f8fad5b-d9cb-469f-a165-70867728950e
C: QUIT
S: 221 Bye
```

## Command order

Send commands in this order: `EHLO`, `STARTTLS`, `EHLO` again, `AUTH`, then `MAIL FROM`, `RCPT TO`, and `DATA` for each message. Use `EHLO`, not `HELO`: the relay answers `HELO` with `250 smtp.telnyx.com` and advertises no extensions, so a client that sends only `HELO` can't start TLS or authenticate.

The table lists the reply to a well-formed command sent out of order by a client that greets with `EHLO`. The TLS handshake resets the session, so "before `EHLO`" also covers the time between the handshake and the next `EHLO`. For each command, the first matching row applies. After `EHLO`, a syntax or parameter error in `MAIL FROM` or `RCPT TO` is reported before authentication is checked.

| Command | Sent | Reply |
| - | - | - |
| `STARTTLS` | Before the first `EHLO` | `500 Command unrecognized` |
| `STARTTLS` | After the TLS handshake | `500 TLS already negotiated` |
| `AUTH` | Before `EHLO` | `503 Error: send EHLO first` |
| `AUTH` | After `EHLO`, before STARTTLS | `538 5.7.11 Encryption required` |
| `AUTH` | During a mail transaction | `503 5.5.1 AUTH not permitted during a mail transaction` |
| `MAIL FROM` | Before `EHLO` | `503 Error: send HELO/EHLO first` |
| `MAIL FROM` | During a mail transaction | `503 Error: Nested MAIL command` |
| `MAIL FROM` | On a connection that is not authenticated | `530 5.7.0 Authentication required` |
| `RCPT TO` | Before `EHLO` | `503 Error: need MAIL command` |
| `RCPT TO` | On a connection that is not authenticated | `530 5.7.0 Authentication required` |
| `DATA` | Before `EHLO` | `503 Error: send HELO/EHLO first` |
| `DATA` | With no accepted `MAIL FROM` | `503 Error: need MAIL command` |
| `DATA` | With no accepted `RCPT TO` | `503 Error: need RCPT command` |

A connection is authenticated from the `235` reply until a later `AUTH` fails with `535`, or until the relay replies `421 4.4.2 Session expired, close and retry`. A mail transaction starts when `MAIL FROM` is accepted and ends with the final reply to `DATA` or with `RSET`. If `DATA` ended with `552 Message too large`, send `RSET` before `AUTH`.

## Authentication

* Authenticate with `AUTH PLAIN` or `AUTH LOGIN` after STARTTLS and the second `EHLO`. Other mechanisms, such as `CRAM-MD5`, are refused with `504 Unrecognized authentication type`.
* For `AUTH PLAIN`, send the base64 encoding of `\0apikey\0YOUR_API_KEY`, either on the `AUTH` line or after the `334` prompt. For `AUTH LOGIN`, send the base64-encoded username `apikey` and then the base64-encoded API key, one after each `334` prompt.
* A response that is not valid base64, or an `AUTH PLAIN` response that does not decode to NUL-separated username and password fields, is refused with `501 Authentication line too long / invalid`.
* Before STARTTLS, `EHLO` does not advertise `AUTH`, and an `AUTH` command sent after that `EHLO` is refused with `538 5.7.11 Encryption required`. The connection stays open: issue `STARTTLS`, send `EHLO`, and authenticate.
* Any username other than `apikey`, or an invalid key, is refused with `535 5.7.8 Authentication credentials invalid`. A valid key that is not permitted to send email is refused with `535 5.7.1 Authorization failed: email send capability required`. After a `535` reply, the connection stays open and is not authenticated.
* Repeated failed `AUTH` attempts can be temporarily refused with `421 4.7.0 Temporary authentication failure, retry later`. The relay sends the same reply when authentication is temporarily unavailable, and closes the connection after it. Wait, then reconnect and authenticate with a valid key.

## Sender and recipients

* The domain of the `MAIL FROM` address must be a verified sending domain on the account that owns the API key. The relay applies the same sender-domain rules as the [Send Email API](https://developers.telnyx.com/docs/messaging/email/send-email.md) and refuses other senders with a `550 5.7.1` reply that states the reason. See [Domains](https://developers.telnyx.com/docs/messaging/email/domains.md).
* The message must contain exactly one `From:` header, and its address must pass the same check. A missing or repeated `From:` header is refused with `550 5.7.1 Invalid sender address` after the message data is sent.
* An empty sender (`MAIL FROM:<>`) is refused with `550 5.7.1 Invalid sender address`.
* An address in angle brackets is refused with `501 Bad sender address syntax` or `501 Bad recipient address syntax` when it is longer than 320 characters after any source route, quoting, and escaping are removed.
* Delivery status notification (DSN) parameters such as `NOTIFY` and `RET` are not supported and are refused with `555 Unsupported option: <parameters>`.

## Message content

Content is delivered unchanged; Telnyx may re-encode it and add a DKIM signature. Don't send mail you've already signed.

* **`Message-ID` and `Date`:** The relay keeps the `Message-ID` header the message carries. If `Message-ID` or `Date` is missing, the relay does not add it, so set both headers on every message.
* **Tracking:** No open or click tracking is applied to mail sent over SMTP. To track opens and clicks, send through the [Send Email API](https://developers.telnyx.com/docs/messaging/email/send-email.md).
* **Line endings:** End every line in the message data with CRLF. Message data with bare LF or bare CR line endings can be refused with `451 Bare newline detected`.
* **Character encoding:** Message data must be valid UTF-8. Send parts in other character sets with `Content-Transfer-Encoding: quoted-printable` or `base64`. Message data that is not valid UTF-8 is refused with `554 5.6.0 Message content must be UTF-8; send other character sets as quoted-printable or base64`.
* **Telnyx message ID:** The UUID in the final `250 2.0.0 Ok: queued as <uuid>` reply is the Telnyx message ID. Use it with `GET /v2/email_messages/{id}`, and to match [webhooks and events](https://developers.telnyx.com/docs/messaging/email/webhooks-events.md) and Email Detail Records. A `250` reply means Telnyx accepted the message; delivery results arrive as events.
* **Retries after `451 4.3.0`:** In rare cases, a message that gets `451 4.3.0 Temporary submission failure, try again` at the end of `DATA` has still been accepted, so retrying it can deliver a duplicate.

## Session limits

| Limit | Value | Reply when exceeded |
| - | - | - |
| Message size | `25,000,000` bytes, advertised as `SIZE 25000000` | `552 Estimated message length <n> exceeds limit of 25000000` at `MAIL FROM` when the client declares `SIZE=`; `552 Message too large` during `DATA` |
| Recipients per connection | `50` accepted `RCPT TO` commands, counted across every message on the connection | `452 4.5.3 Too many recipients` for the 51st and each later `RCPT TO` |
| Idle time between commands | `30` seconds | `421 Error: timeout exceeded`; the relay closes the connection |
| Message data transfer | `300` seconds from the `354` reply to the final `.` | `421 Error: timeout exceeded`; the relay closes the connection |
| Session lifetime | `5` minutes after successful `AUTH` | `421 4.4.2 Session expired, close and retry` on the next `MAIL FROM`, `RCPT TO`, or end of `DATA` |
| Address length | `320` characters per address in angle brackets, after any source route, quoting, and escaping are removed | `501 Bad sender address syntax` or `501 Bad recipient address syntax` |

The recipient limit applies to the connection, not to a single message. A `452 4.5.3 Too many recipients` reply refuses only that recipient; recipients already accepted in the transaction stay accepted. Open a new connection for each batch of up to 50 recipients, and close each connection with `QUIT`.

After `421 4.4.2 Session expired, close and retry`, the connection is not authenticated. Close it and open a new connection.

Messages sent over SMTP count against the same [rate limits and quotas](https://developers.telnyx.com/docs/messaging/email/rate-limits.md) as messages sent through the API, including sending-domain limits. When any of these limits is reached, the end of `DATA` is answered with `452 4.5.3 Mail quota exceeded, try again later`. Retry later with backoff; a daily quota resets at midnight UTC.

Suspensions get different replies depending on their cause:

* **Suspended sending domain:** `MAIL FROM`, and the `From:` header at the end of `DATA`, are refused with `550 5.7.1 Domain is suspended.`
* **Sending suspended because of the sending domain's reputation:** the end of `DATA` is answered with `452 4.5.3 Mail quota exceeded, try again later`. Retries get the same reply until the suspension is lifted; contact Telnyx support.

## Reply codes

Each table lists the exact reply line. `<domain>`, `<n>`, `<uuid>`, `<parameters>`, and `<COMMAND>` stand for values from the session.

### Connection and TLS replies

| Code | Reply | Meaning | What to do |
| - | - | - | - |
| `220` | `220 smtp.telnyx.com ESMTP email_submission` | Connection greeting | Send `EHLO` |
| `250` | `250 smtp.telnyx.com` | Reply to `HELO`; no extensions are advertised | Send `EHLO` instead |
| `220` | `220 OK` | Ready to start TLS after `STARTTLS` | Start the TLS handshake, then send `EHLO` again |
| `454` | `454 TLS negotiation failed` | The TLS handshake failed | Check the client's TLS configuration and reconnect |
| `500` | `500 Command unrecognized` | `STARTTLS` was sent before `EHLO` | Send `EHLO`, then `STARTTLS` |
| `500` | `500 TLS already negotiated` | `STARTTLS` was sent on a connection that already uses TLS | Continue with `EHLO` |
| `501` | `501 Syntax error (no parameters allowed)` | `STARTTLS` was sent with arguments | Send `STARTTLS` on its own |
| `501` | `501 Syntax: EHLO hostname` | `EHLO` was sent without a hostname | Send `EHLO` with the client's hostname |
| `501` | `501 Syntax: HELO hostname` | `HELO` was sent without a hostname | Send `EHLO` with the client's hostname |
| `500` | `500 Error: SMTP should send HELO or EHLO instead of LHLO` | `LHLO`, an LMTP command, was sent with a hostname | Send `EHLO` |
| `501` | `501 Syntax: LHLO hostname` | `LHLO` was sent without a hostname | Send `EHLO` with the client's hostname |
| `503` | `503 Error: send HELO/EHLO first` | `MAIL FROM` or `DATA` was sent before `EHLO` or `HELO`, including after the TLS handshake | Send `EHLO` first |

### Authentication replies

| Code | Reply | Meaning | What to do |
| - | - | - | - |
| `235 2.7.0` | `235 2.7.0 Authentication successful` | The API key was accepted | Continue with `MAIL FROM` |
| `334` | `334 VXNlcm5hbWU6` | `AUTH LOGIN` asks for the username (base64 of `Username:`) | Send `apikey`, base64-encoded |
| `334` | `334 UGFzc3dvcmQ6` | `AUTH LOGIN` asks for the password (base64 of `Password:`) | Send the API key, base64-encoded |
| `334` | `334` | `AUTH PLAIN` was sent without a credential | Send the base64 encoding of `\0apikey\0YOUR_API_KEY` |
| `535 5.7.8` | `535 5.7.8 Authentication credentials invalid` | The username is not `apikey`, or the API key is invalid | Use the literal username `apikey` and a valid API v2 key |
| `535 5.7.1` | `535 5.7.1 Authorization failed: email send capability required` | The API key is valid but is not permitted to send email | Use an API key that can send email |
| `538 5.7.11` | `538 5.7.11 Encryption required` | `AUTH` was sent after `EHLO` but before STARTTLS | Issue `STARTTLS`, send `EHLO`, then authenticate |
| `421 4.7.0` | `421 4.7.0 Temporary authentication failure, retry later` | Authentication is temporarily unavailable, or `AUTH` was temporarily refused after repeated failed attempts; the relay closes the connection | Wait, then reconnect and authenticate with a valid key |
| `503 5.5.1` | `503 5.5.1 AUTH not permitted during a mail transaction` | `AUTH` was sent during a mail transaction; the reply follows the credential | Send `RSET`, then `AUTH` |
| `503` | `503 Error: send EHLO first` | `AUTH` was sent before `EHLO` or `HELO`, including after the TLS handshake | Send `EHLO` first |
| `504` | `504 Unrecognized authentication type` | The mechanism is missing or is not `PLAIN` or `LOGIN` | Use `AUTH PLAIN` or `AUTH LOGIN` |
| `501` | `501 Authentication aborted` | The client cancelled `AUTH` with `*` | Start `AUTH` again |
| `501` | `501 Authentication line too long / invalid` | The `AUTH` response is not valid base64, or the decoded `AUTH PLAIN` response is not NUL-separated username and password fields | For `AUTH PLAIN`, send the base64 encoding of `\0apikey\0YOUR_API_KEY`, or use `AUTH LOGIN` |

### Envelope replies

| Code | Reply | Meaning | What to do |
| - | - | - | - |
| `250` | `250 sender Ok` | `MAIL FROM` accepted | Send `RCPT TO` |
| `250` | `250 recipient Ok` | `RCPT TO` accepted | Add recipients or send `DATA` |
| `530 5.7.0` | `530 5.7.0 Authentication required` | `MAIL FROM` or `RCPT TO` was sent after `EHLO` on a connection that is not authenticated | Issue `STARTTLS`, send `EHLO`, and authenticate first |
| `550 5.7.1` | `550 5.7.1 Domain is not verified. Complete DNS setup before sending email.` | The sender domain is on the account but not verified | Finish domain verification |
| `550 5.7.1` | `550 5.7.1 Sending domain <domain> is not available for this account` | The sender domain is not on the account that owns the API key | Send from a domain on this account, or use that account's API key |
| `550 5.7.1` | `550 5.7.1 Domain is suspended.` | The sender domain is suspended | Resolve the suspension before sending |
| `550 5.7.1` | `550 5.7.1 Domain is in a degraded state.` | The sender domain is degraded | Fix the domain's configuration before sending |
| `550 5.7.1` | `550 5.7.1 Domain does not have an active DKIM key configured.` | The sender domain has no active DKIM key | Configure DKIM for the domain |
| `550 5.7.1` | `550 5.7.1 This domain is receive-only and cannot be used for sending. Verify a custom domain to send email.` | The sender domain is receive-only | Verify a custom domain and send from it |
| `550 5.7.1` | `550 5.7.1 Sender address does not match the domain's sending profile.` | The sender address does not match the domain's sending profile | Send from an address the domain allows |
| `550 5.7.1` | `550 5.7.1 Shared domain from-address must be onboarding@<domain>. To use a custom from-address, verify your own domain.` | A shared domain allows only the `onboarding@` sender | Send from `onboarding@<domain>`, or verify a custom domain |
| `550 5.7.1` | `550 5.7.1 Invalid sender address` | The sender address cannot be parsed, has no domain, or is empty | Send from a valid address on a verified domain |
| `451 4.3.0` | `451 4.3.0 Temporary submission failure, try again` | The sender check is temporarily unavailable | Retry later |
| `452 4.5.3` | `452 4.5.3 Too many recipients` | The connection already has 50 recipients | Send the remaining recipients on a new connection |
| `552` | `552 Estimated message length <n> exceeds limit of 25000000` | The `SIZE=` value on `MAIL FROM` is above the limit | Reduce the message size |
| `501` | `501 Syntax error` | The `SIZE=` value on `MAIL FROM` is not a whole number | Send `SIZE=` with the message size in bytes, or omit it |
| `555` | `555 Unsupported option BODY` | The `BODY=` value on `MAIL FROM` is not `7BIT` or `8BITMIME`, or `EHLO` was not sent on the connection | Send `BODY=7BIT` or `BODY=8BITMIME`, or omit it |
| `555` | `555 Unsupported option SMTPUTF8` | `MAIL FROM` carried `SMTPUTF8`, and `EHLO` was not sent on the connection | Send `EHLO` before `MAIL FROM` |
| `555` | `555 Unsupported option: <parameters>` | `MAIL FROM` or `RCPT TO` carried an unsupported parameter, such as a DSN parameter; `<parameters>` is everything after the address | Remove the parameter |
| `501` | `501 Bad sender address syntax` | The `MAIL FROM` address is malformed or exceeds the 320-character address limit | Correct the address |
| `501` | `501 Bad recipient address syntax` | The `RCPT TO` address is malformed, empty, or exceeds the 320-character address limit | Correct the address |
| `501` | `501 Syntax: MAIL FROM:<address>` | `MAIL` was sent without `FROM:` | Use `MAIL FROM:<address>` |
| `501` | `501 Syntax: RCPT TO:<address>` | `RCPT` was sent without `TO:` | Use `RCPT TO:<address>` |
| `503` | `503 Error: Nested MAIL command` | `MAIL FROM` was sent during a mail transaction | Finish the transaction or send `RSET` |
| `503` | `503 Error: need MAIL command` | `DATA` was sent after `EHLO` but before a `MAIL FROM` was accepted, or `RCPT TO` was sent before `EHLO` or `HELO`, including after the TLS handshake | Send `EHLO` if needed, then `MAIL FROM` |

### Message data replies

| Code | Reply | Meaning | What to do |
| - | - | - | - |
| `354` | `354 enter mail, end with line containing only '.'` | Ready for the message | Send headers and body, then a line containing only `.` |
| `250 2.0.0` | `250 2.0.0 Ok: queued as <uuid>` | Telnyx accepted the message; `<uuid>` is the Telnyx message ID | Store the ID to match events and webhooks |
| `503` | `503 Error: need RCPT command` | `DATA` was sent with no accepted recipients | Send at least one `RCPT TO` |
| `452 4.5.3` | `452 4.5.3 Mail quota exceeded, try again later` | A rate limit, daily quota, or sending-domain limit was reached, or sending is suspended because of the sending domain's reputation | Retry later with backoff; see [Rate Limits](https://developers.telnyx.com/docs/messaging/email/rate-limits.md). For a reputation suspension, contact Telnyx support |
| `451 4.3.0` | `451 4.3.0 Temporary submission failure, try again` | The message could not be submitted, or Telnyx could not confirm that it was accepted. In rare cases the message has still been accepted | Retry later; a retry can deliver a duplicate |
| `451` | `451 Bare newline detected` | The message data contains a bare LF or bare CR | Use CRLF line endings |
| `552` | `552 Message too large` | The message data exceeded `25,000,000` bytes | Reduce the message size |
| `552 5.3.4` | `552 5.3.4 Message size exceeds fixed maximum message size` | The message data was received in full, but the message is too large to submit | Reduce the message size |
| `554 5.6.0` | `554 5.6.0 Message content must be UTF-8; send other character sets as quoted-printable or base64` | The message data is not valid UTF-8, such as 8-bit content in another character set | Send valid UTF-8, or send parts in other character sets as quoted-printable or base64; don't retry it unchanged |
| `554 5.6.0` | `554 5.6.0 Message content rejected` | Telnyx rejected the message for a reason other than its character encoding | Fix the message; don't retry it unchanged |
| `550 5.7.1` | `550 5.7.1 Shared domain sends are restricted to your account's verified email address (sandbox). To send to other recipients, verify your own domain.` | A shared-domain message was addressed to someone other than the account's verified email address | Verify a custom domain to send to other recipients |
| `550 5.7.1` | `550 5.7.1 Trial sends are restricted to your account owner's verified email address. Verify your own domain to send to other recipients.` | A trial account sent to someone other than the account owner's verified email address | Verify a custom domain to send to other recipients |
| `535 5.7.1` | `535 5.7.1 Authorization failed on message submission` | The API key was not authorized when the message was submitted | Check the API key, then authenticate again |

The sender replies in the [envelope table](#envelope-replies) also apply to the `From:` header at the end of `DATA`.

### Session and timeout replies

| Code | Reply | Meaning | What to do |
| - | - | - | - |
| `421` | `421 Error: timeout exceeded` | No command arrived for 30 seconds, or the message data was not complete within 300 seconds; the relay closes the connection | Reconnect; don't hold idle connections open |
| `421 4.4.2` | `421 4.4.2 Session expired, close and retry` | 5 minutes have passed since successful `AUTH`; the connection is not authenticated after this reply | Close the connection and open a new one |
| `221` | `221 Bye` | Reply to `QUIT`; the relay closes the connection | None |

### Other command replies

| Code | Reply | Meaning | What to do |
| - | - | - | - |
| `250` | `250 Ok` | Reply to `NOOP` and `RSET` | None |
| `252 2.0.0` | `252 2.0.0 Cannot VRFY user, but will accept message and attempt delivery` | Reply to `VRFY` with an address; addresses are not verified | None |
| `501` | `501 Syntax: VRFY username/address` | `VRFY` was sent without an address, or with one that cannot be parsed | None; `VRFY` does not verify addresses |
| `214 2.0.0` | `214 2.0.0 See https://developers.telnyx.com/docs/email/smtp` | Reply to `HELP`; the link opens this page | None |
| `500` | `500 Error: command not recognized: <COMMAND>` | The command is not supported, or `DATA` was sent with arguments; `<COMMAND>` is the command name in uppercase | Use a supported command |
| `500` | `500 Error: bad syntax` | An empty command line was sent | Send a command |

## Test the connection

### openssl s\_client

Build the `AUTH PLAIN` credential:

```bash theme={null}
printf '\0apikey\0%s' "$TELNYX_API_KEY" | openssl base64 -A
```

Open a TLS session. `-starttls smtp` sends `EHLO` and `STARTTLS` for you, `-crlf` sends CRLF line endings, and `-ign_eof` stops `openssl` from treating lines that start with `Q` or `R` as its own commands:

```bash theme={null}
openssl s_client -connect smtp.telnyx.com:587 -starttls smtp -crlf -ign_eof
```

After the handshake, enter the session:

```text theme={null}
EHLO client.example.com
AUTH PLAIN <credential from the printf command>
MAIL FROM:<sender@your-verified-domain.com>
RCPT TO:<recipient@example.com>
DATA
From: sender@your-verified-domain.com
To: recipient@example.com
Subject: SMTP test
Date: Thu, 22 Oct 2026 12:00:00 +0000
Message-ID: <smtp-test-1@your-verified-domain.com>

Hello from the Telnyx SMTP relay.
.
QUIT
```

### swaks

`--tls` makes `swaks` require STARTTLS. Without it, `swaks` doesn't start TLS, and the relay doesn't offer `AUTH` on an unencrypted connection.

```bash theme={null}
swaks --server smtp.telnyx.com:587 --tls \
  --auth PLAIN --auth-user apikey --auth-password "$TELNYX_API_KEY" \
  --from sender@your-verified-domain.com --to recipient@example.com
```

Both tests send a real message. A successful run ends with `250 2.0.0 Ok: queued as <uuid>`.
