DEV Community

Regő Botond Ronyecz
Regő Botond Ronyecz

Posted on • Originally published at zerohook.hashnode.dev

Developer's Guide to MTA-STS and TLS Reporting (2026)

Most developers have heard of SPF, DKIM, and DMARC. Far fewer have heard of MTA-STS, and almost nobody has set up TLS Reporting. Which is a shame, because MTA-STS closes a real gap that the other three don't touch — and TLS Reporting is the only way to know when your email delivery is silently failing.

This guide covers both: what they do, how to set them up, and how to verify they're working.


The problem MTA-STS solves

When two mail servers exchange email, they're supposed to negotiate TLS so the message travels encrypted. But "supposed to" is doing a lot of work in that sentence.

SMTP's STARTTLS mechanism is opportunistic by default. That means:

  1. Sending server connects to your MX host
  2. Sending server says "do you support TLS?"
  3. If yes, they upgrade to TLS
  4. If no (or if an attacker strips the STARTTLS offer), they fall back to plaintext

Step 4 is the problem. An attacker who can intercept traffic between two mail servers — a network-level attacker, a rogue DNS resolver, anyone doing BGP hijacking — can silently strip the STARTTLS advertisement. The sending server sees "no TLS available" and delivers in plaintext. No error. No bounce. No indication anything went wrong.

This is called a STARTTLS downgrade attack, and it's been documented in the wild against real mail providers.

MTA-STS fixes this by publishing a policy that tells sending servers: "our MX hosts support TLS, you should require it, and here's a certificate you can validate against." A sending server that respects MTA-STS will refuse to deliver if TLS negotiation fails or if the certificate doesn't match, rather than falling back to plaintext.

The short version: SPF, DKIM, and DMARC protect against spoofing and phishing. MTA-STS protects the transport layer — the connection between mail servers — from being downgraded or intercepted.


How MTA-STS works

The mechanism involves two components: a policy file served over HTTPS, and a DNS TXT record that tells sending servers where to find it.

  1. Sending server looks up _mta-sts.yourapp.com in DNS to check if a policy exists
  2. If found, it fetches https://mta-sts.yourapp.com/.well-known/mta-sts.txt over HTTPS
  3. The policy tells it which MX hosts to expect and what mode to operate in
  4. It connects to your MX host and requires TLS with a valid, matching certificate
  5. If TLS fails or the cert doesn't match, it refuses delivery instead of falling back to plaintext

The policy file is served over HTTPS from a subdomain you control. The HTTPS requirement is what makes this work: the policy itself can't be tampered with in transit.

The policy file is served over HTTPS from a subdomain you control. The HTTPS requirement matters: it means the policy itself can't be tampered with in transit.


Setting up MTA-STS

Step 1 — Create the policy file

The policy file is a plain text file served at exactly this path:

https://mta-sts.yourapp.com/.well-known/mta-sts.txt
Enter fullscreen mode Exit fullscreen mode

It looks like this:

version: STSv1
mode: enforce
mx: mail.yourapp.com
mx: *.yourapp.com
max_age: 86400
Enter fullscreen mode Exit fullscreen mode
Field Description
version Always STSv1
mode enforce, testing, or none (see below)
mx One entry per MX host you accept mail on. Wildcards allowed.
max_age How long (seconds) sending servers should cache this policy

Mode choices explained:

Mode What happens when TLS fails
testing Delivery proceeds, failure reported via TLS-RPT
enforce Delivery refused if TLS fails or cert doesn't match
none Policy effectively disabled

Start with testing. Run it for a week or two while you monitor TLS-RPT reports (covered below). Once you're confident your MX hosts are properly configured, switch to enforce.

Don't start with enforce if you haven't verified your MX hosts present valid, trusted certificates with matching hostnames. You will break inbound email delivery.


Step 2 — Serve the policy file over HTTPS

You need a subdomain mta-sts.yourapp.com with a valid TLS certificate serving the policy file. The response must:

  • Return HTTP 200
  • Have Content-Type: text/plain
  • Be accessible over HTTPS (not HTTP)

If you're on Nginx:

server {
    listen 443 ssl;
    server_name mta-sts.yourapp.com;

    ssl_certificate     /etc/letsencrypt/live/mta-sts.yourapp.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/mta-sts.yourapp.com/privkey.pem;

    location /.well-known/mta-sts.txt {
        alias /var/www/mta-sts/mta-sts.txt;
        default_type text/plain;
    }
}
Enter fullscreen mode Exit fullscreen mode

If you're on a static hosting platform (Netlify, Vercel, Cloudflare Pages), put the file at /.well-known/mta-sts.txt and set the content type to text/plain. Make sure HTTPS redirect is enabled.


Step 3 — Add the DNS TXT record

Add a TXT record at _mta-sts.yourapp.com:

_mta-sts.yourapp.com.  IN  TXT  "v=STSv1; id=20260101000000Z"
Enter fullscreen mode Exit fullscreen mode

The id field is how sending servers know when your policy has changed. It's a version string — you can use a timestamp or any string up to 32 characters. Every time you update your policy file, update the id in this DNS record. Sending servers that have cached your policy check the id to know whether to re-fetch.

Common mistake: Updating the policy file without updating the id means sending servers keep using the cached version. If you're changing from testing to enforce, the old cached policy stays in effect until the max_age expires.


Setting up TLS Reporting (TLS-RPT)

TLS Reporting is RFC 8460. It lets you receive reports when sending mail servers encounter TLS failures while trying to deliver to your domain.

You add one DNS TXT record at _smtp._tls.yourapp.com:

_smtp._tls.yourapp.com.  IN  TXT  "v=TLSRPTv1; rua=mailto:tls-reports@yourapp.com"
Enter fullscreen mode Exit fullscreen mode

That's it. Sending servers that support TLS-RPT will now send daily JSON reports to that address summarizing:

  • How many messages were delivered successfully with TLS
  • How many failed, and why
  • Which sending servers encountered the failures

The reports are machine-readable JSON. You'll want either a dedicated inbox you check periodically or a service that parses and aggregates them. Report URI and Postmark's DMARC/TLS aggregator both handle TLS-RPT reports.

Sample report structure:

{
  "organization-name": "Google Inc.",
  "date-range": {
    "start-datetime": "2026-01-01T00:00:00Z",
    "end-datetime": "2026-01-01T23:59:59Z"
  },
  "policies": [{
    "policy": {
      "policy-type": "sts",
      "policy-domain": "yourapp.com"
    },
    "summary": {
      "total-successful-session-count": 1240,
      "total-failure-session-count": 3
    },
    "failure-details": [{
      "result-type": "certificate-expired",
      "sending-mta-ip": "209.85.128.0",
      "receiving-mx-hostname": "mail.yourapp.com",
      "failed-session-count": 3
    }]
  }]
}
Enter fullscreen mode Exit fullscreen mode

Three failures from certificate expiry. Without TLS-RPT, you'd never know.


Verifying your setup

Check the DNS record:

dig TXT _mta-sts.yourapp.com +short
dig TXT _smtp._tls.yourapp.com +short
Enter fullscreen mode Exit fullscreen mode

Check the policy file is reachable:

curl -I https://mta-sts.yourapp.com/.well-known/mta-sts.txt
curl https://mta-sts.yourapp.com/.well-known/mta-sts.txt
Enter fullscreen mode Exit fullscreen mode

Expected output from the first command: HTTP/2 200 with content-type: text/plain.

Full validation tools:

Tool What it checks
mta-sts.io Full MTA-STS policy validation
internet.nl Comprehensive mail security check including MTA-STS
checktls.com TLS configuration on your MX hosts
MXToolbox MTA-STS policy lookup

Common failure modes

Policy file returns wrong content type
Some static hosts serve .txt files as application/octet-stream. Sending servers will reject the policy. Set Content-Type: text/plain explicitly in your hosting config.

Certificate on mta-sts subdomain doesn't cover the subdomain
If you're using a wildcard cert for *.yourapp.com, make sure it covers mta-sts.yourapp.com. A separate cert for just the subdomain also works.

MX host certificate doesn't match hostname
Your policy file lists mail.yourapp.com as an MX host. The certificate on that host needs to include mail.yourapp.com in the SAN. If it only has the bare domain or a different hostname, validation will fail under enforce mode.

id not updated after policy change
Covered above, but worth repeating: if you change the policy file without changing the id, sending servers use the stale cached version. The id is how they know to re-fetch.

HTTP redirect on the policy URL
The policy must be served directly over HTTPS. If your setup serves HTTP and redirects to HTTPS, some sending servers won't follow the redirect and will treat the policy as unavailable.


The rollout sequence

Here's the order that avoids breaking inbound email:

Week 1-2: deploy in testing mode

  • Deploy the policy file with mode: testing
  • Add both DNS records (_mta-sts and _smtp._tls)
  • Verify with the validation tools listed above

Week 2-4: collect reports and fix issues

  • Read the TLS-RPT reports coming into your inbox
  • Fix any certificate or TLS config issues on your MX hosts
  • Re-check with checktls.com

Week 4 onwards: switch to enforce

  • Update the policy file: change mode: testing to mode: enforce
  • Update the id value in your _mta-sts DNS TXT record
  • Keep watching TLS-RPT reports for anything new

Don't rush the middle step. The reports will show you things you didn't know were broken.


Monitoring

Once MTA-STS is in enforce mode, any DNS misconfiguration on your end can break inbound email delivery. If your policy file goes down, if the mta-sts subdomain certificate expires, or if someone changes the DNS records unexpectedly, sending servers that respect MTA-STS will stop delivering to you.

Two things to monitor:

The HTTPS endpoint. Uptime monitoring on https://mta-sts.yourapp.com/.well-known/mta-sts.txt should alert you if the file becomes unavailable or returns a non-200 status.

The DNS records. If your _mta-sts TXT record is modified or deleted, sending servers can't find your policy. ZeroHook monitors DNS records continuously and alerts you when something changes — useful not just for MTA-STS but for the full set of email security records (SPF, DKIM, DMARC, MX). One change to the wrong record can quietly break email in ways that take days to diagnose: zerohook.org.


Quick reference

DNS records you need:

Record Type Value
_mta-sts.yourapp.com TXT v=STSv1; id=20260101000000Z
_smtp._tls.yourapp.com TXT v=TLSRPTv1; rua=mailto:tls-reports@yourapp.com

Policy file location:

https://mta-sts.yourapp.com/.well-known/mta-sts.txt
Enter fullscreen mode Exit fullscreen mode

Policy file content:

version: STSv1
mode: testing
mx: mail.yourapp.com
max_age: 86400
Enter fullscreen mode Exit fullscreen mode

Rollout order: deploy in testing → collect reports → fix issues → switch to enforce → monitor.


TL;DR

SPF, DKIM, and DMARC don't protect the connection between mail servers. A network-level attacker can strip TLS from SMTP and your email travels in plaintext with no indication anything went wrong.

MTA-STS fixes that by publishing a policy that says "require TLS, validate the cert, refuse delivery if it fails." TLS Reporting gives you visibility into when and why TLS is failing so you can fix it before you switch to enforce mode.

Setup is two DNS records and a text file on a subdomain. The tricky part is the rollout order — start in testing, read the reports, fix your cert issues, then enforce.


Part of an ongoing series on DNS and email security.

Top comments (0)