DEV Community

Cover image for MTA-STS and TLS-RPT: Enforce TLS for Inbound Email
Muhammad Ali Mughal
Muhammad Ali Mughal

Posted on

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 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)
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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:sts-reports@google.com"
_smtp._tls.outlook.com  TXT  "v=TLSRPTv1;rua=https://tlsrpt.azurewebsites.net/report"
Enter fullscreen mode Exit fullscreen mode

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.

A row of old brass mailboxes with small closed doors, representing mail that should only reach verified destinations
AI-generated image.

How a sending server uses the policy

When a sender that supports MTA-STS delivers to your domain:

  1. It looks up _mta-sts.yourdomain TXT. If there's a record and the id differs from its cached copy, it fetches the policy over HTTPS.
  2. It validates the HTTPS certificate on mta-sts.yourdomain. A broken cert there means no policy.
  3. It caches the policy for max_age seconds.
  4. For each MX, it checks the hostname against the mx: patterns, requires STARTTLS, and validates the MX certificate against the hostname.
  5. In enforce, a failure means no delivery to that MX; the sender tries other MX hosts and then queues and retries. In testing, 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";
    }
}
Enter fullscreen mode Exit fullscreen mode

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, id not bumped. Senders keep the cached version until max_age expires.
  • MX migration without a policy update. Moving mail providers changes MX hosts. Update mx: lines first, bump the id, wait out the old max_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

  1. List every MX host and confirm each presents a valid, unexpired certificate matching its hostname.
  2. Publish _smtp._tls with a mailbox or HTTPS endpoint you actually read.
  3. Create mta-sts.yourdomain with a valid certificate and serve the policy as text/plain, no redirects.
  4. Start with mode: testing and max_age: 86400.
  5. Publish _mta-sts TXT with v=STSv1 and a fresh id.
  6. Read TLS-RPT reports for two to four weeks.
  7. Switch to enforce, bump the id, keep a short max_age for a week, then raise it.
  8. 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

Top comments (0)