MTA-STS and TLS-RPT: Enforce TLS for Inbound Email
SMTP between mail servers was designed in an era when nobody encrypted anything, and it still shows. STARTTLS upgrades a connection to TLS only if both sides offer it, which means anyone on the network path can strip tha
SMTP between mail servers was designed in an era when nobody encrypted anything, and it still shows. STARTTLS upgrades a connection to TLS only if both sides offer it, which means anyone on the network path can strip that offer and force your inbound mail into plaintext. MTA-STS is the fix: a published policy that tells sending servers "always use TLS to my MX hosts, with a valid certificate, or don't deliver at all."
I run PixelValut, a set of browser-side developer and security tools, including an MTA-STS checker. I wrote this with help from AI tools and checked every command and claim myself. Every DNS answer and policy file below was fetched today from the live domains named.
What is MTA-STS and what does it protect?
Direct answer: MTA-STS (RFC 8461) lets a domain publish a TXT record and an HTTPS-hosted policy file that tell sending mail servers to deliver only over TLS, to listed MX hosts, with a valid certificate. It blocks STARTTLS downgrade and MX spoofing attacks on inbound email. TLS-RPT (RFC 8460) adds daily reports on failed TLS connections.
The threat: opportunistic TLS fails open
Without MTA-STS, a sending server connecting to your MX does this: connect on port 25, send EHLO, look for 250-STARTTLS in the reply, and upgrade if it's there. If it isn't, most servers deliver in plaintext rather than bounce the mail. They also usually don't validate the MX certificate, because there was historically no way to know what certificate to expect.
That gives an attacker on the path two easy moves:
| Attack | How it works | Without MTA-STS | With MTA-STS enforce
|
|---|---|---|---|
| STARTTLS stripping | Remove STARTTLS from the EHLO response |
Sender falls back to plaintext | Sender refuses to deliver, retries later |
| Certificate swap | Present a self-signed or wrong-host cert | Usually accepted | Rejected: cert must be valid for the MX host |
| MX spoofing | Poison DNS so MX points to attacker host | Mail goes to attacker | Host not in policy mx: list, rejected |
| Passive eavesdropping | Read mail on a plaintext hop | Readable | Hop must be TLS |
SPF, DKIM and DMARC don't help here. They authenticate who sent a message. MTA-STS protects the transport into your domain. I covered the sender side in how to check SPF, DKIM and DMARC records. This is the other half.
The three moving parts, from real domains
MTA-STS has two required pieces and one optional but essential one. My box doesn't have dig, so I queried Cloudflare's DNS-over-HTTPS JSON endpoint with curl. The answers are the same records dig TXT returns.
1. The _mta-sts TXT record signals that a policy exists and carries an id that must change whenever the policy changes:
_mta-sts.gmail.com TXT "v=STSv1; id=20190429T010101;"
_mta-sts.outlook.com TXT "v=STSv1; id=20190225000000Z;"
_mta-sts.yahoo.com TXT "v=STSv1; id=20161109010200Z;"
_mta-sts.github.com (no record)
2. The policy file, always at https://mta-sts.<domain>/.well-known/mta-sts.txt, served over HTTPS with a valid certificate. I fetched each one with curl:
curl -s https://mta-sts.gmail.com/.well-known/mta-sts.txt
version: STSv1
mode: enforce
mx: smtp.google.com
mx: gmail-smtp-in.l.google.com
mx: *.gmail-smtp-in.l.google.com
max_age: 86400
Compare with Yahoo's, fetched the same way:
version: STSv1
mode: testing
mx: *.am0.yahoodns.net
mx: *.mail.gm0.yahoodns.net
mx: *.mail.am0.yahoodns.net
max_age: 86400
3. The TLS-RPT record at _smtp._tls, which tells senders where to report TLS failures:
_smtp._tls.gmail.com TXT "v=TLSRPTv1;rua=mailto:[email protected]"
_smtp._tls.outlook.com TXT "v=TLSRPTv1;rua=https://tlsrpt.azurewebsites.net/report"
What the policy fields tell you:
| Field | Gmail | Outlook.com | Yahoo | What it means |
|---|---|---|---|---|
mode |
enforce |
enforce |
testing |
enforce blocks bad delivery; testing only reports |
mx |
3 patterns incl. wildcard | *.olc.protection.outlook.com |
3 wildcards | Allowed MX hostnames |
max_age |
86400 (1 day) | 604800 (7 days) | 86400 (1 day) | How long senders cache the policy |
| TLS-RPT | mailto | HTTPS endpoint | mailto | Where failure reports go |
Two details from those outputs are worth checking on your own domain. First, the mx: patterns must cover every real MX. Gmail's MX set is gmail-smtp-in.l.google.com plus alt1 to alt4.gmail-smtp-in.l.google.com, and the wildcard *.gmail-smtp-in.l.google.com covers the alternates. A wildcard matches exactly one label, so *.example.com covers mx1.example.com but not a.mx1.example.com. Second, Yahoo publishes in testing, a reminder that even large providers don't always run enforce.
How a sending server uses the policy
When a sender that supports MTA-STS delivers to your domain:
- It looks up
_mta-sts.yourdomainTXT. If there's a record and theiddiffers from its cached copy, it fetches the policy over HTTPS. - It validates the HTTPS certificate on
mta-sts.yourdomain. A broken cert there means no policy. - It caches the policy for
max_ageseconds. - For each MX, it checks the hostname against the
mx:patterns, requires STARTTLS, and validates the MX certificate against the hostname. - In
enforce, a failure means no delivery to that MX; the sender tries other MX hosts and then queues and retries. Intesting, it delivers anyway and reports the failure through TLS-RPT.
The policy is fetched over HTTPS, not DNS alone, on purpose: an attacker who can tamper with DNS would also be able to remove a DNS-only policy. Caching with max_age protects later connections even if DNS is attacked after the first fetch. The first-ever connection is the weak spot, which is the trust-on-first-use trade-off MTA-STS accepts.
Rolling it out without losing mail
I roll MTA-STS out in stages, because enforce with a wrong mx: line silently queues inbound mail at senders.
| Stage | mode |
max_age |
Duration | Exit criterion |
|---|---|---|---|---|
| 1. Reporting only | none yet; publish TLS-RPT first | n/a | 1Γ’β¬β2 weeks | Reports arrive, no unknown failures |
| 2. Testing | testing |
86400 | 2Γ’β¬β4 weeks | No sts-policy or certificate failures in reports |
| 3. Enforce, short cache | enforce |
86400 | 1Γ’β¬β2 weeks | Still clean |
| 4. Enforce, long cache | enforce |
604800 or more | Ongoing | Monitor reports weekly |
The policy file can be static on any HTTPS host. A minimal Nginx block:
server {
listen 443 ssl;
server_name mta-sts.example.com;
ssl_certificate /etc/letsencrypt/live/mta-sts.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/mta-sts.example.com/privkey.pem;
location = /.well-known/mta-sts.txt {
default_type text/plain;
return 200 "version: STSv1\nmode: testing\nmx: mx1.example.com\nmx: mx2.example.com\nmax_age: 86400\n";
}
}
Failure modes I check first
-
Expired certificate on
mta-sts.host. Senders can't fetch the policy. Automate renewal and monitor it like any other site; my SSL certificate check guide covers what to inspect. -
Policy changed,
idnot bumped. Senders keep the cached version untilmax_ageexpires. -
MX migration without a policy update. Moving mail providers changes MX hosts. Update
mx:lines first, bump theid, wait out the oldmax_age, then switch MX. The DNS migration audit sequence applies here too. -
Wrong content type or redirects. Serve the policy directly as
text/plain. RFC 8461 says senders must not follow HTTP redirects when fetching it. -
Forwarding-only domains. My own sites use registrar mail forwarding with MX hosts I don't control, so I can't publish an honest
mx:list that I can keep correct. If you don't control the MX or its certificates, coordinate with the provider instead of guessing.
Verify it in your browser
Paste your domain into the PixelValut MTA-STS checker. It reads the _mta-sts TXT record, fetches the policy file, compares the mx: lines with your real MX hosts and shows the TLS-RPT record, the same checks I ran with curl above.
MTA-STS deployment checklist
- List every MX host and confirm each presents a valid, unexpired certificate matching its hostname.
- Publish
_smtp._tlswith a mailbox or HTTPS endpoint you actually read. - Create
mta-sts.yourdomainwith a valid certificate and serve the policy astext/plain, no redirects. - Start with
mode: testingandmax_age: 86400. - Publish
_mta-stsTXT withv=STSv1and a freshid. - Read TLS-RPT reports for two to four weeks.
- Switch to
enforce, bump theid, keep a shortmax_agefor a week, then raise it. - Add the policy to your MX change runbook.
Frequently asked questions
Does MTA-STS encrypt outbound email from my domain?
No. It protects mail sent to your domain by senders that support it. Your outbound security depends on the receiving domains' policies and your server honoring them.
Is MTA-STS the same as DANE?
No. DANE pins certificates in DNS with TLSA records and requires DNSSEC. MTA-STS uses HTTPS and the normal certificate system, so it works without DNSSEC.
What happens if my MTA-STS policy is wrong in enforce mode?
Senders that honor the policy won't deliver to MX hosts that fail it. They queue and retry, and mail may bounce after the retry period. That is why the testing stage matters.
Do I need TLS-RPT?
It's optional in the spec, but rolling out without it is flying blind. The reports show which senders failed TLS and why.
How often do senders refresh the policy?
When the id in the TXT record changes, or when the cached copy reaches max_age. Bumping the id is how you announce an update.
Does Gmail use MTA-STS?
Gmail publishes an enforce policy for gmail.com, shown above, with a one-day max_age and a TLS-RPT address. Whether a given sender honors your policy is up to that sender, which is what TLS-RPT reports reveal.
Follow & Connect
- YouTube: PixelValut (@pixel-valut)
- TikTok: PixelValut (@pixelvalut007)
- GitHub: Muhammad Ali Mughal (@ali111887)
- LinkedIn: Muhammad Ali Mughal
Originally published by Dev.to Security. Aggregated on AIWithGhost for educational purposes β full credit and traffic to the original publisher.