← Blog · October 15, 2026

MTA-STS and TLS-RPT Setup Guide: Enforce TLS for Inbound Email

SMTP was designed without encryption. STARTTLS added it later, but only as an option. If an attacker strips the STARTTLS offer from the server’s response, or presents a forged certificate, most sending servers fall back to plaintext delivery and never tell you. MTA-STS (RFC 8461) closes that gap. It lets your domain tell other mail servers: “Only deliver to my MX hosts over TLS with a valid certificate, and if you can’t, don’t deliver.” TLS-RPT (RFC 8460) is the reporting side. Sending servers send you daily reports on TLS failures they saw when delivering to you.

This guide covers the full setup: the two DNS records, the HTTPS-hosted policy file, a safe rollout from testing to enforce mode, and the mistakes that most often break it.

How MTA-STS Works

MTA-STS has three parts:

  1. A DNS TXT record at _mta-sts.example.com. It says a policy exists and gives it an ID.
  2. A policy file served over HTTPS at https://mta-sts.example.com/.well-known/mta-sts.txt. It lists your allowed MX hosts and the enforcement mode.
  3. Sender-side caching. A supporting sender (Gmail, Microsoft 365, Yahoo and others) fetches the policy, caches it for max_age seconds, and checks every later delivery against it. It only fetches the file again when the id in DNS changes.

The policy is fetched over HTTPS with a certificate checked against the Web PKI, so an attacker who can tamper with DNS or SMTP traffic can’t easily forge it. That is what makes MTA-STS stronger than opportunistic STARTTLS. You don’t need DNSSEC, unlike DANE.

Prerequisites

Check these before you publish anything:

  • Every MX host supports STARTTLS.
  • Every MX host presents a certificate that is valid, unexpired, issued by a public CA, and has a name that matches the MX hostname. A self-signed certificate, or one issued for a different name, will fail under enforce mode.
  • You can host a small text file over HTTPS on the mta-sts subdomain, with a valid certificate for that name.

To check an MX host’s certificate:

openssl s_client -connect mail.example.com:25 -starttls smtp -servername mail.example.com 2>/dev/null | openssl x509 -noout -subject -ext subjectAltName -enddate

The MX hostname has to appear in the Subject Alternative Name list.

Step 1: Publish the TLS-RPT Record First

Turn on reporting before anything else so you can see problems before they cost you mail. Add a TXT record:

_smtp._tls.example.com.  3600  IN  TXT  "v=TLSRPTv1; rua=mailto:tlsrpt@example.com"

You can list several destinations separated by commas, and https:// endpoints are allowed too. Reports arrive about once a day as gzip-compressed JSON. Each one shows successful and failed session counts per policy, plus failure types such as certificate-expired, certificate-host-mismatch, starttls-not-supported or validation-failure.

Use a dedicated mailbox or a report-processing service. Report volume grows with the number of sending domains.

Step 2: Create the Policy File

Start in testing mode:

version: STSv1
mode: testing
mx: mail.example.com
mx: backup-mx.example.com
max_age: 86400

Field reference:

  • version: always STSv1.
  • mode: testing means senders report failures but still deliver. enforce means senders refuse to deliver over a connection that fails validation. none withdraws the policy.
  • mx: one line per allowed MX pattern. A wildcard matches exactly one leftmost label. *.example.net matches mx1.example.net, but not a.b.example.net and not example.net itself.
  • max_age: how long senders cache the policy, in seconds. The maximum is 31557600 (about one year). Use a short value while testing. One to two weeks (604800 to 1209600) or longer is common once you enforce.

MX Patterns for Common Providers

# Google Workspace (classic MX set)
mx: aspmx.l.google.com
mx: *.aspmx.l.google.com

# Google Workspace (newer single MX)
mx: smtp.google.com

# Microsoft 365
mx: *.mail.protection.outlook.com

Every MX record you publish has to match at least one mx: line. Run dig +short MX example.com and compare the results line by line.

Step 3: Host the Policy File

The file has to be available at exactly:

https://mta-sts.example.com/.well-known/mta-sts.txt

Requirements:

  • Serve it over HTTPS with a valid certificate for mta-sts.example.com.
  • Return HTTP 200 directly. Senders must not follow redirects, so a 301 to www or to another host breaks the policy.
  • Use Content-Type: text/plain.
  • CRLF line endings are what the RFC specifies. Most senders accept LF, but CRLF is the safe choice.

A static host works fine (a small VPS, GitHub Pages with a custom domain, Cloudflare Pages or Workers, an S3 bucket behind CloudFront). Here is a minimal nginx server 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;
        root /var/www/mta-sts;
    }
}

Step 4: Publish the MTA-STS DNS Record

_mta-sts.example.com.  3600  IN  TXT  "v=STSv1; id=20261015T120000"

The id can be any alphanumeric string up to 32 characters. A timestamp is the easiest choice. Change the id every time you edit the policy file. Senders only re-fetch the policy when the id changes. If you don’t change it, they keep using the cached copy until max_age runs out.

Step 5: Verify

dig +short TXT _mta-sts.example.com
dig +short TXT _smtp._tls.example.com
curl -sv https://mta-sts.example.com/.well-known/mta-sts.txt

In the curl output, check for HTTP 200 with no redirect, Content-Type: text/plain, a certificate valid for mta-sts.example.com, and a policy body that matches what you wrote.

Step 6: Move from Testing to Enforce

  1. Leave mode: testing in place for at least one to two weeks and read the TLS-RPT reports.
  2. Fix every failure you see: expired certificates, hostname mismatches, MX hosts missing from the policy.
  3. Change the file to mode: enforce and raise max_age, for example to 1209600.
  4. Update the id in the _mta-sts TXT record.
  5. Keep reading the reports. Under enforce mode, any failure they show is mail that was not delivered.

Common MTA-STS Errors and Fixes

Policy changes are ignored

The id wasn’t changed. Senders are still using the old cached policy. Change the id in DNS.

“Policy fetch failed” or no policy found

Possible causes: the mta-sts subdomain has no A/AAAA record, its certificate is missing or invalid, the server returns a redirect, or the path is wrong (it’s .well-known with a leading dot). Some CDNs and hosting panels redirect all traffic to www by default, so check for that.

certificate-host-mismatch in TLS-RPT

The MX host’s certificate doesn’t include the MX hostname. This often happens when the MX is a CNAME-style vanity name such as mail.example.com pointing at a shared server whose certificate only covers server123.hoster.net. Either get a certificate that includes the MX name, or point the MX straight at the provider’s hostname.

Mail rejected after an MX migration

You added a new MX record but didn’t add it to the policy. Under enforce mode, senders with a cached policy refuse the new host. When you migrate, add the new MX to the policy and update the id before changing your MX records, and only remove the old entry after the switch.

Expired certificate on the MX

With MTA-STS enforced, an expired MX certificate means compliant senders stop delivering. Monitor certificate expiry on port 25 the same way you monitor it for your website.

How to Remove MTA-STS Safely

Don’t just delete the records. Senders keep enforcing their cached policy until max_age expires. Instead, publish mode: none, update the id, and keep both in place for at least the previous max_age. After that, remove the DNS record and the policy file.

MTA-STS vs DANE

DANE (TLSA records) solves the same problem but relies on DNSSEC. Some senders, such as Microsoft 365 for outbound mail and many European providers, validate DANE. Gmail validates MTA-STS. The two don’t conflict, and if your domain is signed with DNSSEC, deploying both gives you the widest coverage. If it isn’t, MTA-STS is the practical place to start.