Skip to main content
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. 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: The username is always the literal string apikey, not an email address or account login. The password carries the credential.

Framework guides

Nodemailer

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 before switching traffic.

Django

settings.py:
  • 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 before switching traffic.

Rails (ActionMailer)

config/environments/production.rb:
  • 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 before switching traffic.

Laravel

config/mail.php:
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 before switching traffic.

WordPress (wp_mail + WP Mail SMTP plugin)

WP Mail SMTP plugin settings (Other SMTP):
  • “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 before switching traffic.

Spring (Java)

application.properties:
  • 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 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 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

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.
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.
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 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. Full tables cover six providers, condensed translations cover seven more, and the quick reference 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.

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.

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”).

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.

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

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.

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

The replies you are most likely to see during cut-over. The SMTP Protocol Contract lists the replies the relay sends.

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: 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

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, 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. 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.
  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.)

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