> ## 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 Migration Guides

> Move SMTP sending to Telnyx from 13 providers with settings translation tables, framework configurations, and a cut-over checklist.

Move SMTP sending from another provider to the Telnyx SMTP relay. This guide translates each provider's settings into Telnyx equivalents, shows the configuration for common frameworks, and covers the differences that matter during cut-over.

For connection details, enforced limits, and the replies the relay sends, see the [SMTP Protocol Contract](https://developers.telnyx.com/docs/messaging/email/smtp-protocol-contract.md).

The SMTP protocol contract page (linked throughout) is the authoritative list of replies and limits.

## Telnyx SMTP settings

Every framework and provider below translates to these 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. |
| Auth mechanism | `AUTH PLAIN` or `AUTH LOGIN` |
| Username | `apikey` (the literal string, not an account name or key ID) |
| Password | A Telnyx API v2 key |

The username is always the literal string `apikey`, not an email address or account login. The password carries the credential.

## Framework guides

### Nodemailer

```javascript theme={null}
const nodemailer = require("nodemailer");

const transport = nodemailer.createTransport({
  host: "smtp.telnyx.com",
  port: 587,
  secure: false,           // 587 = STARTTLS ("secure: true" means implicit-TLS 465, which Telnyx does not offer)
  requireTLS: true,        // refuse to continue unless STARTTLS succeeds
  auth: {
    user: "apikey",        // literal string
    pass: process.env.TELNYX_API_KEY,
  },
});
```

`secure: false` + port 587 is Nodemailer's STARTTLS mode, but on its own it only upgrades **if the server advertises STARTTLS**. If something on the path strips that advertisement, Nodemailer carries on in plaintext. `requireTLS: true` makes it upgrade or abort, so the API key is never offered over an unencrypted connection. Do not set `secure: true`: that selects implicit-TLS port 465, which Telnyx does not offer.

**Nodemailer-specific pitfalls:**

* The block above opens a fresh connection per `sendMail()`, so the per-connection recipient limit only affects a single message with more than 50 To+Cc+Bcc recipients (`452 4.5.3 Too many recipients` on the 51st). That refusal is per recipient, so it doesn't fail the send: the message is still submitted for the first 50, and the rest show up in `info.rejected`. Check it, or chunk before sending.
* If you turn on `pool: true`, pooled connections are long-lived. Telnyx sessions expire 5 minutes after AUTH, and the 50-recipient limit counts across **every** message on the connection, so keep `maxMessages` × recipients-per-message at 50 or below.
* Pass the Telnyx API key as-is; Nodemailer handles the AUTH encoding.

Verify the settings with [swaks or openssl](#verify-the-connection) before switching traffic.

### Django

`settings.py`:

```python theme={null}
EMAIL_HOST = "smtp.telnyx.com"
EMAIL_PORT = 587
EMAIL_USE_TLS = True       # STARTTLS. EMAIL_USE_SSL = False — 465 is not offered.
EMAIL_HOST_USER = "apikey"  # literal string
EMAIL_HOST_PASSWORD = "<TELNYX_V2_API_KEY>"
```

* `EMAIL_USE_TLS = True` is Django's STARTTLS mode. Never set `EMAIL_USE_SSL = True` for Telnyx.
* `EMAIL_BACKEND` stays as-is (`django.core.mail.backends.smtp.EmailBackend`). `send_mail()` opens one connection per call, but the connection limits still apply in two common cases:
  * one message whose To+Cc+Bcc exceeds 50 addresses gets `452 4.5.3 Too many recipients` on the 51st `RCPT`. Django won't raise: the message is still submitted for the first 50, and Django discards the list of refused recipients, so the rest are dropped silently;
  * `send_mass_mail()`, or one explicitly opened connection (`with get_connection() as conn:`) reused across `send_messages()`, puts **all** messages on one connection. The 50-recipient limit counts across that whole connection, and the session expires 5 minutes after AUTH.
* Chunk recipient lists **before** sending so each connection carries at most 50 recipients in total, and open a new connection per chunk. A connection left idle for more than 30 seconds between messages is closed with `421 Error: timeout exceeded`.

Verify the settings with [swaks or openssl](#verify-the-connection) before switching traffic.

### Rails (ActionMailer)

`config/environments/production.rb`:

```ruby theme={null}
config.action_mailer.delivery_method = :smtp
config.action_mailer.smtp_settings = {
  address:              "smtp.telnyx.com",
  port:                 587,
  user_name:            "apikey",   # literal string
  password:             ENV["TELNYX_API_KEY"],
  authentication:       :plain,     # :login also works; :cram_md5 is not offered
  enable_starttls:      true,       # Rails 7+: STARTTLS or fail
}
```

* `enable_starttls: true` (Rails 7+) requires STARTTLS and fails the delivery if the server doesn't offer it. Don't set `enable_starttls_auto` alongside it. On older Rails, `enable_starttls_auto: true` (the default) upgrades **only when the server advertises STARTTLS**, so it will not stop a downgrade.
* `authentication: :plain` and `:login` are both accepted (after STARTTLS). `:cram_md5` is not offered.
* `openssl_verify_mode` default (peer verification) is correct — do not set `:none`.

Verify the settings with [swaks or openssl](#verify-the-connection) before switching traffic.

### Laravel

`config/mail.php`:

```php theme={null}
'smtp' => [
    'transport'     => 'smtp',
    'host'          => 'smtp.telnyx.com',
    'port'          => 587,
    'encryption'    => 'tls',      // Laravel 9/10; port 587 is what selects STARTTLS
    'username'      => 'apikey',   // literal string
    'password'      => env('TELNYX_API_KEY'),
    'timeout'       => 30,
    'require_tls'   => true,       // symfony/mailer >= 7.3: STARTTLS or fail
],
```

Under Symfony Mailer (Laravel 9+), the **port** decides the TLS mode, not the `encryption` value. Port 465 means implicit TLS, which Telnyx does not offer. Any other port means plaintext connect followed by STARTTLS when the server offers it. So the setting that matters is `'port' => 587`. Newer skeletons replace `encryption` with `'scheme'`: leave it `smtp`, never `smtps`. On its own, that STARTTLS is opportunistic: it happens only if the server advertises it. `'require_tls' => true` makes it upgrade-or-fail, like the other guides. It needs symfony/mailer 7.3 or later, which means Laravel 11+ (which passes mailer options through to Symfony Mailer). Laravel 9/10 have no equivalent setting.

Verify the settings with [swaks or openssl](#verify-the-connection) before switching traffic.

### WordPress (wp\_mail + WP Mail SMTP plugin)

WP Mail SMTP plugin settings (Other SMTP):

| Plugin field | Value |
| - | - |
| SMTP Host | `smtp.telnyx.com` |
| Encryption | TLS |
| SMTP Port | 587 |
| Authentication | ON |
| SMTP Username | `apikey` |
| SMTP Password | Your Telnyx v2 API key. Prefer WP Mail SMTP's `wp-config.php` constants (`define('WPMS_ON', true);` + `define('WPMS_SMTP_PASS', '…');`), which keep the key out of the database. That is the plugin's own recommendation. |

* "Encryption: TLS" in WP Mail SMTP = STARTTLS (matches port 587). "SSL" would expect 465 — not offered.
* **From address must be on a verified domain:** wp\_mail's From address must be on a verified Telnyx sending domain on the same account. The relay checks the envelope sender at `MAIL FROM` and the `From:` header at the end of `DATA`, with the same sender-domain rules as the Send Email API. An unverified domain gets `550 5.7.1 Domain is not verified. Complete DNS setup before sending email.`; a domain that isn't on the authenticated account gets `550 5.7.1 Sending domain <domain> is not available for this account`. Set "From Email" to an address on your verified domain. The default `wordpress@yourdomain` passes only if the site domain is verified in Telnyx.
* Plain `wp_mail()` without a plugin uses PHP `mail()` (local MTA) — that path never speaks to Telnyx; use the plugin.

Verify the settings with [swaks or openssl](#verify-the-connection) before switching traffic.

### Spring (Java)

`application.properties`:

```properties theme={null}
spring.mail.host=smtp.telnyx.com
spring.mail.port=587
spring.mail.username=apikey
spring.mail.password=${TELNYX_API_KEY}
spring.mail.properties.mail.smtp.auth=true
spring.mail.properties.mail.smtp.starttls.enable=true
spring.mail.properties.mail.smtp.starttls.required=true
```

* `starttls.enable=true` upgrades via STARTTLS; `starttls.required=true` fails the connection if STARTTLS isn't negotiated, instead of continuing unencrypted. Telnyx refuses `AUTH` before STARTTLS with `538 5.7.11 Encryption required`.
* JavaMail's default `mail.smtp.auth` mechanisms include PLAIN/LOGIN; no CRAM/DIGEST needed (not offered).

Verify the settings with [swaks or openssl](#verify-the-connection) before switching traffic.

### Client notes that apply to every framework

* **Command order:** connect → EHLO → STARTTLS → EHLO again → AUTH PLAIN or AUTH LOGIN (`apikey` + API v2 key) → MAIL FROM → RCPT TO → DATA. Before STARTTLS, AUTH is refused with `538 5.7.11 Encryption required`, and the connection stays usable: issue STARTTLS, send EHLO, and continue.
* **Connection limits:** 50 recipients per connection, counted across every message on it; messages up to 25,000,000 bytes (`SIZE 25000000` in EHLO); 30 seconds of idle time between commands; 300 seconds to transfer the message data; sessions expire 5 minutes after AUTH. The 51st recipient on one connection gets `452 4.5.3 Too many recipients`. Most clients treat that as a partial success, not an error, so batch your recipient list across connections up front. A connection idle for more than 30 seconds gets `421 Error: timeout exceeded` and is closed. After 5 minutes, the next `MAIL`, `RCPT`, or end of `DATA` is refused; close the connection and open a new one. The [SMTP Protocol Contract](https://developers.telnyx.com/docs/messaging/email/smtp-protocol-contract.md) lists the exact replies.
* **Rate limits and quotas are shared with the API:** there is no separate SMTP limit. When a rate limit or quota is reached, the end of `DATA` is answered with `452 4.5.3 Mail quota exceeded, try again later`. Retry later with backoff.
* **Content handling:** Message data must be valid UTF-8. Send parts in other character sets with `Content-Transfer-Encoding: quoted-printable` or `base64`; 8-bit content in another character set is refused at the end of `DATA` with `554 5.6.0 Message content must be UTF-8; send other character sets as quoted-printable or base64`. This is a check on the raw message when it is submitted, not a character-set conversion. Accepted content is delivered unchanged; Telnyx may re-encode it and add a DKIM signature. Don't send mail you've already signed.
* **Set `Message-ID` and `Date`:** The relay keeps the `Message-ID` header your message carries. If `Message-ID` or `Date` is missing, the relay does not add it, so set both on every message.
* **The `250` reply carries the message ID:** `250 2.0.0 Ok: queued as <uuid>`. The UUID is the Telnyx message ID used by the Email API, webhooks, and Email Detail Records. Log it on accept; it is your correlation handle for support.

### Python smtplib

```python theme={null}
import smtplib
import ssl
from email.message import EmailMessage
from email.utils import formatdate, make_msgid

msg = EmailMessage()
msg["From"] = "you@verified-domain.com"
msg["To"] = "recipient@example.com"
msg["Subject"] = "Hello from Telnyx SMTP"
msg["Date"] = formatdate()                                  # the relay does not add Date
msg["Message-ID"] = make_msgid(domain="verified-domain.com")  # nor Message-ID
msg.set_content("Sent via smtp.telnyx.com:587")

with smtplib.SMTP("smtp.telnyx.com", 587, timeout=30) as smtp:
    smtp.starttls(context=ssl.create_default_context())   # mandatory; verifies the certificate
    smtp.login("apikey", "<TELNYX_V2_API_KEY>")   # literal username
    smtp.send_message(msg)   # ≤50 recipients per connection; open a new connection per batch
```

If you skip `starttls()`, smtplib never reaches the server's `538 5.7.11`. Telnyx doesn't advertise AUTH before TLS, so `login()` fails locally with `SMTPNotSupportedError: SMTP AUTH extension not supported by server.` Both mean the same thing: call `starttls()` first. Pass `ssl.create_default_context()`: without a context, smtplib's `starttls()` does **not** verify the server certificate or hostname.

`send_message()` raises only if **every** recipient is refused. If some are (for example the 51st and later, `452 4.5.3`), it returns them in a dict and still sends to the rest, so check the return value.

## Verify the connection

Test the new settings before switching traffic. Both checks send a real message from a verified sending domain; a successful run ends with `250 2.0.0 Ok: queued as <uuid>`.

**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 you@verified-domain.com --to recipient@example.com
```

**openssl.** Build the `AUTH PLAIN` credential, then 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. `-verify_return_error` and `-verify_hostname smtp.telnyx.com` make the connection fail if the relay's certificate doesn't verify — don't send `AUTH` credentials over an unverified session.

```bash theme={null}
printf '\0apikey\0%s' "$TELNYX_API_KEY" | openssl base64 -A
openssl s_client -connect smtp.telnyx.com:587 -starttls smtp -crlf -ign_eof -verify_return_error -verify_hostname smtp.telnyx.com
```

After the handshake, send `EHLO` again, then `AUTH PLAIN <credential>`, `MAIL FROM`, `RCPT TO`, and `DATA`. Include `From`, `Date`, and `Message-ID` headers in the message. The [SMTP Protocol Contract](https://developers.telnyx.com/docs/messaging/email/smtp-protocol-contract.md#test-the-connection) has a complete session to paste.

## Migration tables — from 13 providers

Settings names in each provider column are **quoted from that provider's own docs**. Telnyx-side values are the ones the relay enforces; see the [SMTP Protocol Contract](https://developers.telnyx.com/docs/messaging/email/smtp-protocol-contract.md). Full tables cover six providers, condensed translations cover seven more, and the [quick reference](#translation-quick-reference-all-providers) has one row per provider.

### Migrating from SendGrid

SendGrid SMTP: host `smtp.sendgrid.net`, "Set the port to 587"; username = literal `apikey`, password = Mail-Send-scoped API key.

| SendGrid setting | Telnyx setting | Notes |
| - | - | - |
| Host `smtp.sendgrid.net` | `smtp.telnyx.com` | |
| Port 587 | **587 unchanged** | SendGrid also accepts 25/2525/465 — Telnyx offers 587 only |
| Username: literal string `apikey` | **`apikey` — identical** | No change |
| Password: SendGrid API key (Mail Send scope) | Telnyx API v2 key | Both are "API key as password"; swap the value, keep the shape |
| AUTH PLAIN/LOGIN, STARTTLS optional (plaintext accepted on 25/2525/587) | STARTTLS required before AUTH | Telnyx refuses AUTH before STARTTLS with `538 5.7.11`; set STARTTLS explicitly |
| X-SMTPAPI header (categories, templates, personalizations) | **Not processed** — use the API's own fields | Telnyx delivers the header unchanged and doesn't act on it; remove it and use the API for categories, templates, and personalization |
| 30 MB message size (Mail Send v3) | 25,000,000 bytes (`SIZE 25000000`) | If you relied on SendGrid's 30 MB, audit attachments before switching |
| 5k messages per SMTP connection, 10k concurrent connections | 5-minute session lifetime after AUTH; 50 recipients per connection | Different shape: Telnyx limits each connection by time and recipient count, not message count — reconnect per batch |

### Migrating from Mailgun

Mailgun SMTP: regional hosts `smtp.mailgun.org` (US) and `smtp.eu.mailgun.org` (EU), one per Mailgun region; ports 25/465/587/2525; per-domain SMTP credentials, default login `postmaster@yourdomain`.

| Mailgun setting | Telnyx setting | Notes |
| - | - | - |
| Host `smtp.mailgun.org` (US) / `smtp.eu.mailgun.org` (EU) | `smtp.telnyx.com` | One hostname; no region selection |
| Ports 25, 465, 587, 2525 | **587 only** | The biggest mechanical change from Mailgun — firewalls/exit policies must allow 587 |
| Per-domain SMTP credentials (`postmaster@yourdomain` + domain password) | `apikey` + Telnyx API v2 key | **Credential model change:** Mailgun credentials are per-domain and separate from API keys; Telnyx uses the same API v2 key for the API and SMTP. Delete the per-domain credential bookkeeping |
| AUTH, STARTTLS optional | STARTTLS required before AUTH | Mailgun allows non-TLS on 25/587/2525; Telnyx requires STARTTLS before AUTH and before accepting any message |
| `X-Mailgun-Recipient-Variables` (batch personalization) | **Not processed over SMTP** — use the API's batch send | Telnyx delivers the header unchanged and doesn't act on it |
| `X-Mailgun-Drop-Message` test mode | No SMTP test mode | Every message the relay accepts is submitted for delivery. Test with [swaks](#verify-the-connection) against a mailbox you control |
| 25 MB message size | 25,000,000 bytes (`SIZE 25000000`) | About the same |

### Migrating from Amazon SES

SES SMTP: region-vended endpoints `email-smtp.<region>.amazonaws.com`; STARTTLS 25/587/2587, TLS Wrapper 465/2465; **dedicated SMTP credentials** (IAM-derived, per-region, "Your SMTP credentials aren't identical to your AWS access keys").

| SES setting | Telnyx setting | Notes |
| - | - | - |
| Region-vended host `email-smtp.<region>.amazonaws.com` | `smtp.telnyx.com` | One hostname; no region selection |
| Ports 25/587/2587 (STARTTLS), 465/2465 (TLS Wrapper) | **587 only** | SES's port matrix is the broadest; Telnyx publishes one port |
| Dedicated SMTP credentials (IAM user, access key ID + derived secret, per-region) | `apikey` + Telnyx API v2 key | **The biggest change:** no SMTP credential derivation, no per-region sets, no IAM policy to manage. The same API v2 key works for the API and SMTP |
| TLS mandatory ("The Amazon SES SMTP endpoint requires that all connections be encrypted using TLS") | identical | Telnyx: STARTTLS mandatory |
| 40 MB message size (v2 API & SMTP) | 25,000,000 bytes (`SIZE 25000000`) | **Downsize alert:** SES's 40 MB becomes 25 MB — audit large senders |
| 50 recipients per message (To/CC/BCC) | 50 recipients per connection | Same number, different unit: SES caps per message, Telnyx caps per connection — one RCPT list > 50 must be split across connections, not just across messages |
| 454 Throttling (`Maximum sending rate exceeded`) | `452 4.5.3 Mail quota exceeded, try again later` at the end of `DATA` | Both are 4xx retry-later replies. SMTP sends share your Telnyx API rate limits and quotas |
| `X-SES-CONFIGURATION-SET` header | **Not processed** | Telnyx delivers the header unchanged and doesn't act on it; remove it |

### Migrating from Postmark

Postmark SMTP: host `smtp.postmarkapp.com` (`smtp-broadcasts.postmarkapp.com` for bulk); ports 25/2525/587, no 465 ("Port 465 is not supported with Postmark SMTP"); Server API Token as both username and password, or per-stream SMTP Token pairs.

| Postmark setting | Telnyx setting | Notes |
| - | - | - |
| Host `smtp.postmarkapp.com` | `smtp.telnyx.com` | Broadcasts and transactional share one host at Telnyx — no separate broadcasts endpoint |
| Ports 25/2525/587 | **587 only** | |
| Server API Token as **both username and password**, or SMTP Token Access Key + Secret Key pair | `apikey` (username) + API v2 key (password) | **Credential shape change:** Postmark's same-token-both-fields model becomes two-field. Replace Postmark's credentials: username `apikey`, password = your Telnyx API v2 key |
| `X-PM-MESSAGE-STREAM` (stream routing) | **Not processed** | Message Streams are Postmark-specific; Telnyx delivers the header unchanged and doesn't act on it |
| `X-PM-Tag` / `X-PM-Metadata-*` | **Not processed** — use the API's `tags` / `metadata` fields | Telnyx delivers the headers unchanged and doesn't act on them |
| `X-PM-TrackOpens` / `X-PM-TrackLinks` | **Not offered over SMTP** | No open or click tracking is applied to SMTP mail; send through the API with `tracking_settings` for tracked sends |
| 10 MB message size | 25,000,000 bytes (`SIZE 25000000`) | Telnyx is 2.5× Postmark's cap |
| "somewhat arbitrary, and informal" SMTP limits (self-policed 10 concurrent connections/IP) | Published, enforced connection limits | See the [SMTP Protocol Contract](https://developers.telnyx.com/docs/messaging/email/smtp-protocol-contract.md) |
| SMTPApiError synthetic bounce (failures logged as bounce with raw source) | `4xx`/`5xx` replies during the SMTP session, and the message ID in the `250` | Submission errors arrive as SMTP replies during the session |

### Migrating from Brevo (ex-Sendinblue)

Brevo SMTP: host `smtp-relay.brevo.com`; ports 587 (STARTTLS, default), 2525 (STARTTLS fallback), 465 (SSL); SMTP login = account email or `[ID]@smtp-brevo.com`, password = **SMTP key** (distinct from API keys; named keys, expiry, IP allowlists).

| Brevo setting | Telnyx setting | Notes |
| - | - | - |
| Host `smtp-relay.brevo.com` | `smtp.telnyx.com` | |
| Ports 587 / 2525 / 465 | **587 only** | Brevo's 2525 fallback exists for hosts blocking 587; Telnyx has no alternate port, so the network must allow outbound 587 |
| SMTP login: account email or `[ID]@smtp-brevo.com` | `apikey` (literal) | **Credential change:** Brevo's email-or-ID login becomes the literal-string username |
| SMTP key (named, standard 64-char or short 15-char variants, expiry, IP allow/block lists) | Telnyx API v2 key | **Credential model change:** Brevo's SMTP keys are separate from its API keys; Telnyx uses the same API v2 key for the API and SMTP |
| 20 MB total message (legacy documented cap; Brevo's current SMTP pages don't restate it) | 25,000,000 bytes (`SIZE 25000000`) | Telnyx is larger |
| `535` top cause: "using the relay hostname as the username" | `535 5.7.8 Authentication credentials invalid` | Same failure class, same fix — username is `apikey`, never the hostname |
| Account suspension blocking SMTP (Brevo: bot attacks on unprotected forms, compromised account) | Telnyx account and domain status applies | A suspended sending domain is refused with `550 5.7.1 Domain is suspended.` |

### Migrating from Resend

Resend SMTP: host `smtp.resend.com`; ports 25/465/587/2465/2587 (recommended default **465**); username = literal `resend`, password = full API key incl. `re_` prefix; team-wide rate limit 10 req/s across all API keys; Resend does not provide SMTP server logs for debugging.

| Resend setting | Telnyx setting | Notes |
| - | - | - |
| Host `smtp.resend.com` | `smtp.telnyx.com` | |
| Ports 25/465/587 + 2465/2587 aliases (recommended default 465 implicit TLS) | **587 only** | **Port change:** Resend recommends 465; Telnyx offers 587 STARTTLS only. Clients configured for Resend's default MUST change port and TLS mode |
| Username: literal string `resend` | `apikey` (literal) | Same literal-string model — swap the constant |
| Password: full API key including `re_` prefix | Telnyx API v2 key | Both are "full key as password" |
| Implicit TLS on 465 (client `secure: true` in Nodemailer terms) | STARTTLS on 587 | **Client config change:** Nodemailer `secure: false` + port 587; do not carry over `secure: true` |
| Team-wide rate limit (10 req/s shared across all keys) | Account's API rate limits and quotas | SMTP and API sends share them; a limit reached at the end of `DATA` returns `452 4.5.3 Mail quota exceeded, try again later`. See [Rate Limits](https://developers.telnyx.com/docs/messaging/email/rate-limits.md) |
| No SMTP server logs | SMTP replies and the message ID | Every SMTP reply states the outcome. The `250` reply carries the Telnyx message ID; look the message up with `GET /v2/email_messages/{id}` |
| `Resend-Idempotency-Key` header | **Not supported over SMTP** | Telnyx delivers the header unchanged and doesn't act on it. For deduplicated sends, use the API's `Idempotency-Key` header |

### Other providers (Mailjet, MailPace, Loops, AgentMail, Cloudflare, SMTP.com, MailerSend)

* **Mailjet** (`in-v3.mailjet.com`, API keypair as SMTP creds): host → `smtp.telnyx.com`; port → 587; username `apikey`; password = Telnyx key. Mailjet's port-80/588 alternates have no equivalent.
* **MailPace** (`smtp.mailpace.com`, per-domain token as username+password): username → literal `apikey`; password → Telnyx key. Unlike MailPace, Telnyx keeps your custom headers and your Message-ID: content is delivered unchanged.
* **Loops** (`smtp.loops.so`, JSON-payload-in-DATA, template-only): there is no direct SMTP translation — Loops' body is not MIME. Rebuild as normal MIME submissions against `smtp.telnyx.com:587`, or move to the API with templates.
* **AgentMail** (`smtp.agentmail.to`, inbox address as username, API key as password): username → literal `apikey`. The username does not select the sender: any `MAIL FROM` address that passes the API's sender-domain rules can send.
* **Cloudflare** (`smtp.mx.cloudflare.net`, **465 implicit-TLS only**): the port must change to 587 + STARTTLS. A client hard-wired to TLS-on-connect cannot be translated by settings alone, so configure STARTTLS explicitly. `api_token` username → `apikey`; Cloudflare API token → Telnyx key. Size goes up: Cloudflare's 5 MiB default SIZE becomes 25,000,000 bytes. Telnyx allows 50 recipients per connection.
* **SMTP.com** (per-account hosts, per-sender logins): host → `smtp.telnyx.com`; per-sender "channel" credentials → a Telnyx API v2 key. Telnyx accepts messages up to 25,000,000 bytes, so check your largest messages against that limit before switching.
* **MailerSend** (`smtp.mailersend.net`, per-domain SMTP users): use `smtp.telnyx.com:587` with STARTTLS; username/password → `apikey` + Telnyx key. MailerSend's hard 5-message-per-connection cap and desktop-client refusal do not apply. Telnyx sessions still expire 5 minutes after AUTH, and the 50-recipient limit counts across the whole connection, so keep connections short-lived. MailerSend supports custom headers over SMTP on its Professional and Enterprise plans and strips them on lower plans (only `X-MailerSend-Tags` is preserved on all plans); Telnyx delivers your custom headers unchanged.

### What never changes (every provider)

* **Accepted content is delivered unchanged; Telnyx may re-encode it and add a DKIM signature.** Don't send mail you've already signed. No open or click tracking is applied to SMTP mail.
* **Message data must be valid UTF-8.** Send parts in other character sets as quoted-printable or base64. 8-bit content in another character set is refused at the end of `DATA` with `554 5.6.0 Message content must be UTF-8; send other character sets as quoted-printable or base64`; Telnyx checks the raw message and does not convert character sets.
* **Your headers pass through.** Custom headers are delivered unchanged; provider-specific *processing* headers (X-SMTPAPI, X-Mailgun-*, X-PM-*, X-SES-\*) do nothing at Telnyx. The relay keeps your `Message-ID`, but it does not add `Message-ID` or `Date` when they are missing, so set both.
* **DKIM and domains:** SMTP uses the same verified sending domains and DKIM setup as the API. There are no SMTP-specific DNS records.
* **From-domain authorization** works identically to the API. The envelope sender (checked at `MAIL FROM`) and the `From:` header (checked at the end of `DATA`) must be on a verified sending domain on the authenticated account. Otherwise you get a `550 5.7.1` reply with the same wording as the API error (see the [failure-mode quick reference](#failure-mode-quick-reference)).

## Failure-mode quick reference

The replies you are most likely to see during cut-over. The [SMTP Protocol Contract](https://developers.telnyx.com/docs/messaging/email/smtp-protocol-contract.md#reply-codes) lists the replies the relay sends.

| Stage | Reply | Meaning | What to do |
| - | - | - | - |
| Connect | `220 smtp.telnyx.com ESMTP email_submission` | Banner | — |
| AUTH | `538 5.7.11 Encryption required` | AUTH attempted before STARTTLS | Issue STARTTLS first; the connection stays usable |
| AUTH | `535 5.7.8 Authentication credentials invalid` | Wrong username or invalid API key | Username is the literal `apikey`; check the key is valid |
| AUTH | `535 5.7.1 Authorization failed: email send capability required` | The key is valid but is not permitted to send email | Use an API key that can send email |
| AUTH | `421 4.7.0 Temporary authentication failure, retry later` | Authentication temporarily unavailable, or AUTH temporarily refused after repeated failed attempts; the connection closes | Wait, then reconnect and authenticate with a valid key |
| MAIL / RCPT | `530 5.7.0 Authentication required` | `MAIL FROM` or `RCPT TO` on a connection that is not authenticated | STARTTLS, EHLO, and AUTH first |
| DATA | `503 Error: need MAIL command` | `DATA` without an accepted `MAIL FROM`, including on a connection that is not authenticated | Authenticate, then send `MAIL FROM` and `RCPT TO` before `DATA` |
| MAIL / DATA | `550 5.7.1 Domain is not verified. Complete DNS setup before sending email.` | Sender domain exists on the account but isn't verified | Finish domain verification (same as the API) |
| MAIL / DATA | `550 5.7.1 Sending domain <domain> is not available for this account` | Sender domain isn't on the authenticated account | Use a domain on this account, or the right account's key |
| MAIL / DATA | `550 5.7.1 Domain is suspended.` / `550 5.7.1 Domain is in a degraded state.` / `550 5.7.1 Domain does not have an active DKIM key configured.` / `550 5.7.1 This domain is receive-only and cannot be used for sending. Verify a custom domain to send email.` / `550 5.7.1 Sender address does not match the domain's sending profile.` | Domain state denies sending — same wording as the API errors | Fix the domain state |
| MAIL / DATA | `550 5.7.1 Invalid sender address` | Envelope or `From:` address unparseable or domain-less, an empty `MAIL FROM:<>`, or a missing or duplicated `From:` header | Send from a valid address on your verified domain |
| MAIL / DATA | `451 4.3.0 Temporary submission failure, try again` | Sender check or submission temporarily unavailable. In rare cases, a message that gets this reply at the end of `DATA` has still been accepted | Retry later; a retry after `DATA` can deliver a duplicate |
| RCPT | `452 4.5.3 Too many recipients` | 51st recipient on this connection | Split recipients across connections |
| DATA | `452 4.5.3 Mail quota exceeded, try again later` | A rate limit or quota was reached, or sending is suspended because of the sending domain's reputation (the API's `429`) | Retry later with backoff; for a reputation suspension, contact Telnyx support |
| MAIL | `552 Estimated message length <n> exceeds limit of 25000000` | Your client declared `SIZE=` above the limit | Shrink below 25,000,000 bytes |
| DATA | `552 Message too large` | Message exceeds `SIZE 25000000` | Shrink below 25,000,000 bytes |
| DATA | `552 5.3.4 Message size exceeds fixed maximum message size` | The message is too large to submit | Shrink the message |
| DATA | `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 |
| DATA | `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 |
| DATA | `535 5.7.1 Authorization failed on message submission` | Send authorization failed when the message was submitted | Check the API key; authenticate again |
| DATA | `250 2.0.0 Ok: queued as <uuid>` | Accepted; the UUID is the Telnyx message ID | Log it; the same ID appears in the Email API, webhooks, and Email Detail Records |
| Any | `421 Error: timeout exceeded` | No command within 30 seconds, or the message data not finished within 300 seconds of the `354`; the server closes the connection | Don't hold idle connections; reconnect |
| QUIT | `221 Bye` | Session closed | — |

## What does not translate: the SMTP feature-header gap

Every provider ships some control headers over SMTP. Telnyx delivers custom headers unchanged but doesn't act on any of them. If your integration relies on these, move that behavior to the API before cut-over:

| Behavior you may have via headers today | Where it goes on Telnyx |
| - | - |
| Categories / tags (X-SMTPAPI, X-Mailgun-Tag, X-PM-Tag, X-Mailjet-Campaign, X-MailerSend-Tags) | `tags` field on `POST /v2/email_messages` |
| Custom metadata / unique args | `metadata` field on `POST /v2/email_messages` |
| Per-message open/click tracking toggles | `tracking_settings` on `POST /v2/email_messages`; SMTP mail is never tracked |
| Template rendering over SMTP (X-SMTPAPI template\_id, X-Mailgun-Template-Name, X-MJ-TemplateID) | `template_id` + `template_variables` on `POST /v2/email_messages` |
| Scheduling over SMTP (X-SMTPAPI send\_at, Deliver-By/Within) | `scheduled_at` on `POST /v2/email_messages` (the deprecated alias `send_at` is still accepted) |
| Recipient variables / personalization fan-out over one submission | `POST /v2/email_messages/batch`; see [Batch Sending](https://developers.telnyx.com/docs/messaging/email/batch-sending.md) |
| Idempotency (Resend-Idempotency-Key) | `Idempotency-Key` header on the API |
| Sending-IP / IP-pool selection at SMTP time | Dedicated IP routing is set per domain, not per message |

The SMTP-native behavior that carries over unchanged: **the relay keeps your own Message-ID**, and the final `250` reply carries the Telnyx message ID for log correlation: `250 2.0.0 Ok: queued as <uuid>`. It is the same ID the Email API, webhooks, and Email Detail Records use. Capture it if your client can read the final reply line.

## Behavioral differences to plan for

| Behavior | What changes on Telnyx |
| - | - |
| TLS posture | STARTTLS is required before AUTH and before any message is accepted. SendGrid/Mailgun/Postmark/SMTP.com/Mailjet all accept plaintext submission somewhere; Telnyx rejects AUTH before STARTTLS with `538 5.7.11`. Clients configured for no encryption fail here — set STARTTLS explicitly. |
| Auth mechanisms | `AUTH PLAIN` and `AUTH LOGIN` only. CRAM-MD5 / DIGEST-MD5 (Postmark keeps these "for compatibility with older SMTP clients") are not offered — use STARTTLS + PLAIN/LOGIN. |
| Credential semantics | Unknown or invalid key → `535 5.7.8 Authentication credentials invalid`. When authentication is temporarily unavailable, AUTH is refused with `421 4.7.0 Temporary authentication failure, retry later`, never accepted, and the connection closes. Repeated failed AUTH attempts can also be temporarily refused with that reply; wait and retry with a valid key. |
| From-domain gate | The envelope sender is checked at `MAIL FROM` and the `From:` header at the end of `DATA`. Both must be on a verified sending domain on the authenticated account, else `550 5.7.1` with the API's wording. Same rules as the API. |
| Message size | 25,000,000 bytes (`SIZE 25000000` in EHLO). Oversize → `552` at `MAIL` if your client declares `SIZE=`, otherwise during `DATA`. SES (40 MB) and Resend (40 MB) senders must check their largest messages. |
| Recipients | 50 per connection (`452 4.5.3` on the 51st and later — a per-recipient refusal, so most clients still send to the first 50 without raising). SendGrid's 1,000-recipient personalization blocks and Mailgun's 1,000-recipient batches do not fit — chunk, or use the batch API. |
| Session limits | Sessions expire 5 minutes after AUTH; 30 seconds of idle time between commands and 300 seconds to transfer message data (`421 Error: timeout exceeded`). Long-lived pooled connections (PHPMailer keepalive, Nodemailer `pool: true`) must expect the relay to end them, and the 50-recipient limit counts across every message on the connection. |
| Suppressed recipients | Same handling as the API: suppressed recipients are not sent to. A `250` reply means Telnyx accepted the message, not that it was delivered. |
| Content and tracking | No open or click tracking on SMTP mail. Message data must be valid UTF-8; send other character sets as quoted-printable or base64, or the message is refused at the end of `DATA` with `554 5.6.0 Message content must be UTF-8; send other character sets as quoted-printable or base64`. Accepted content is delivered unchanged; Telnyx may re-encode it and add a DKIM signature. Use the API for tracked sends. |
| Rate limits and quotas | SMTP and API sends share the same rate limits and quotas. A limit or quota reached at `DATA` is `452 4.5.3 Mail quota exceeded, try again later`. |

## Cut-over playbook

1. **Verify the sending domain first** (`POST /v2/email_domains` → publish DNS records → `verify`). Telnyx generates its own DKIM keypair — the new DKIM record coexists with your old ESP's; keep both live during parallel running.
2. **Import suppressions before the first send** (`POST /v2/email_blocks/import`; auto-detects SendGrid/Mailgun/SES CSV exports). Sending to addresses that bounced or complained at your old ESP is the fastest way to damage a new domain's reputation.
3. **Configure the SMTP client** with the [Telnyx SMTP settings](#telnyx-smtp-settings), and set `Message-ID` and `Date` on every message.
4. **Point a canary at `smtp.telnyx.com:587`** with STARTTLS and the literal username `apikey`. Verify with the [swaks or openssl check](#verify-the-connection). Confirm the `250 2.0.0 Ok: queued as <uuid>` line and look the message up with `GET /v2/email_messages/{id}`.
5. **Split traffic by percentage**, keep the old ESP as fallback, compare delivery/bounce via `GET /v2/email_events/stats`, then ramp — the same parallel-running guidance as the [API migration guide](https://developers.telnyx.com/docs/messaging/email/migrate-to-telnyx.md).
6. **Watch the connection limits on day one:** 50 recipients per connection and 5-minute sessions affect bulk-shaped SMTP jobs that "worked" against permissive providers. Chunk the RCPT list; move true bulk to the batch API.

## Translation quick-reference (all providers)

One row per provider. **Every row ends up with the same four settings:** host `smtp.telnyx.com`, port **587** with STARTTLS, username the literal `apikey`, password your Telnyx API v2 key. The last column says what that means coming from each provider. (Loops has no row: its JSON-in-DATA submissions need a rebuild; see Other providers above.)

| From | Old host | Old ports | Old username | What you change |
| - | - | - | - | - |
| SendGrid | `smtp.sendgrid.net` | 25/2525/587/465 | `apikey` | host → `smtp.telnyx.com`; port → 587 STARTTLS; username stays `apikey`; password → Telnyx key |
| Mailgun | `smtp.mailgun.org` / `smtp.eu.mailgun.org` | 25/587/2525/465 | `postmaster@domain` | host → `smtp.telnyx.com`; port → 587 STARTTLS; username → `apikey`; password → Telnyx key |
| SES | `email-smtp.<region>.amazonaws.com` | 25/587/2587/465/2465 | per-region SMTP creds | host → `smtp.telnyx.com`; port → 587 STARTTLS; username → `apikey`; password → Telnyx key; delete per-region credential maps |
| Postmark | `smtp.postmarkapp.com` / `smtp-broadcasts.postmarkapp.com` | 25/2525/587 | Server token / stream SMTP token | host → `smtp.telnyx.com`; port → 587 STARTTLS; username → `apikey`; password → Telnyx key |
| Resend | `smtp.resend.com` | 25/465/587/2465/2587 | `resend` | host → `smtp.telnyx.com`; port → 587 **and** TLS mode → STARTTLS (Resend's default is 465 implicit TLS); username → `apikey`; password → Telnyx key |
| Brevo | `smtp-relay.brevo.com` | 587/2525/465 | account email / `[ID]@smtp-brevo.com` | host → `smtp.telnyx.com`; port → 587 STARTTLS; username → `apikey`; password → Telnyx key |
| Mailjet | `in-v3.mailjet.com` | 25/80/587/588/2525/465 | API key | host → `smtp.telnyx.com`; port → 587 STARTTLS; username → `apikey`; password → Telnyx key |
| MailPace | `smtp.mailpace.com` | 25/587/2525/465 | per-domain token | host → `smtp.telnyx.com`; port → 587 STARTTLS; username → `apikey`; password → Telnyx key |
| AgentMail | `smtp.agentmail.to` | 465/587 | inbox address | host → `smtp.telnyx.com`; port → 587 STARTTLS; username → `apikey`; password → Telnyx key |
| Cloudflare | `smtp.mx.cloudflare.net` | **465 only** | `api_token` | host → `smtp.telnyx.com`; port **must** change 465 → 587 **and** TLS mode → STARTTLS; username → `apikey`; password → Telnyx key |
| SMTP.com | per-account host | 25/2525/2082/465 | per-sender login | host → `smtp.telnyx.com`; port → 587 STARTTLS; username → `apikey`; password → Telnyx key |
| MailerSend | `smtp.mailersend.net` | see MailerSend's docs | generated SMTP user | host → `smtp.telnyx.com`; port → 587 STARTTLS; username → `apikey`; password → Telnyx key |

## What you get that your old provider didn't

* **STARTTLS before credentials and mail.** STARTTLS is required before AUTH and before any message is accepted. Among the classic ESPs only SES and MailerSend match this posture. Of the providers in this guide, Cloudflare (TLS-on-connect only) and AgentMail (encryption required on both ports) do too. SendGrid, Mailgun, Postmark and SMTP.com all accept plaintext submission.
* **Published, enforced limits** — Postmark describes its SMTP limits as "somewhat arbitrary, and informal." Telnyx's connection limits and replies are on the [SMTP Protocol Contract](https://developers.telnyx.com/docs/messaging/email/smtp-protocol-contract.md). Rate limits and quotas are shared with the API; a limit reached at `DATA` returns `452 4.5.3`.
* **Message-ID kept, never dropped** — MailPace explicitly drops unsupported headers/Message-ID; Loops can't accept arbitrary MIME at all. Telnyx keeps your headers and your Message-ID.
* **One pipeline:** SMTP mail uses the same domains, suppressions, DKIM setup, events, and webhooks as API sends, and the message is traceable end to end from the message ID in the `250` reply.
