uCheckeruChecker

MTA-STS Generator

We build both the TXT record and the policy file. Start in testing mode: with a mistake in the server list, enforce mode blocks inbound mail rather than merely complaining.

Policy mode

Start with testing: if the server list is wrong, enforce will block inbound mail. Switch over after a couple of weeks of reports.

Mail servers

List every MX host for the domain. Wildcards such as *.example.com are allowed. The list must match your actual MX records.

Policy lifetime, seconds

604800 is a week. Start smaller (86400) so a mistake can be rolled back quickly.

Generated record

v=STSv1; id=20261031142810
How to publish it
TypeNameTTL
TXT_mta-sts3600

Policy file

Serve this text at https://mta-sts.your-domain/.well-known/mta-sts.txt with a valid certificate and a text/plain content type.

version: STSv1
mode: testing
max_age: 86400

Change the id value every time you edit the policy file, because that is how receivers know to re-read it.

What MTA-STS is and what it protects against

MTA-STS (SMTP MTA Strict Transport Security, RFC 8461) protects mail arriving at your domain from interception on the way. STARTTLS asks for encryption inside a connection that is already open in clear text. An intermediate hop can strip the STARTTLS offer from the server's reply, and a sender that sees no encryption support sends the message in clear text. Both sides believe all is well. That is a downgrade attack, and it cannot be noticed from the inside.

MTA-STS removes that option: the policy is served over https and tells senders that the domain accepts mail only over TLS, with a valid certificate, on the listed servers. It cannot be stripped from the SMTP session because it arrives over a different channel. If the certificate name does not match or the server offers no TLS, enforce mode means the message is not sent at all. The RFC also requires MTA-STS servers to support TLS 1.2 or 1.3.

What else you need besides the record

  1. Set up the mta-sts.yourdomain subdomain and get a certificate for it.
  2. Serve the policy file at /.well-known/mta-sts.txt as text/plain.
  3. Publish the TXT record at _mta-sts.
  4. Set up TLS-RPT to see reports about failed connection attempts.

The policy file and the TXT record

version: STSv1
mode: testing
mx: mail.example.com
mx: *.mx.example.net
max_age: 86400
  • version: currently only STSv1.
  • mode: testing, enforce or none, covered below.
  • mx: one line per mail server. The asterisk replaces only the left-most label: *.mx.example.net matches a.mx.example.net but not b.a.mx.example.net.
  • max_age: how many seconds senders keep the policy, at most 31557600 (a year). The RFC suggests weeks or more.

You also need two DNS records: an address for the policy subdomain and the TXT record itself. The TXT has two tags: v=STSv1 and id. The id value is a string of up to 32 letters and digits, and you change it with every edit of the file.

mta-sts.example.com.  IN A    203.0.113.10
_mta-sts.example.com. IN TXT  "v=STSv1; id=20260917T120000"

MX lists for Google Workspace and Microsoft 365

Policy names are matched against the domain's MX records and against the receiving server's certificate. If the file misses an MX or lists outdated names, some senders will fail to deliver under enforce. So where do you get the right list? The easiest way is to look at what the providers publish themselves. On 16 October 2026 the google.com policy said:

mx: smtp.google.com
mx: aspmx.l.google.com
mx: *.aspmx.l.google.com

The microsoft.com policy that day had a single line, which also fits Microsoft 365 domains whose MX points at mail.protection.outlook.com:

mx: *.mail.protection.outlook.com

Before publishing, compare the list with what the MX lookup returns for your domain. When you change mail provider, update the policy before the MX records. Incidentally, Google runs max_age: 86400, a single day, even in enforce mode.

How to serve the policy file

The file must open at exactly https://mta-sts.example.com/.well-known/mta-sts.txt with a 200 status, because senders do not follow redirects and do not accept self-signed certificates. If you have a web server, an nginx block like this is enough:

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 {
        root /var/www/mta-sts;
        default_type text/plain;
    }
    location / {
        return 404;
    }
}

Without a server of your own, the file can live on static hosting with a custom domain, such as GitHub Pages or Cloudflare Pages: all that matters is that the mta-sts subdomain serves it over https with a valid certificate. To check the result:

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

The curl output should show status 200 and content-type: text/plain.

Three modes and the order to roll them out

  • testing: the sender evaluates the policy, still delivers the message on a mismatch and reports the failure through TLS-RPT, which is why every rollout starts here.
  • enforce: a mismatch cancels delivery.
  • none: the clean way to switch MTA-STS off. Deleting the file is not enough, since senders remember the previous policy until its max_age expires.

A sensible order: TLS-RPT first, then testing with a short max_age of a day or two. Watch the reports for a week: they show whether every MX passes and whether a backup server was forgotten. A week feels slow. Only after a clean one switch to enforce and, once the setup has settled, raise max_age. A large value protects against policy tampering, but senders will remember a mistake for just as long.

To opt out of MTA-STS, RFC 8461 suggests three steps: publish a policy with mode: none and a small max_age, change the id in the TXT record, and only once every earlier max_age has passed remove the record and the file.

Where it breaks

  • The file is not served as text/plain. The server sends text/html or a wrapper page, and the policy is treated as unreadable.
  • The certificate on mta-sts.example.com has expired. The policy subdomain lives apart from the main site and its renewal gets forgotten. The policy becomes unreachable, while under enforce senders keep demanding TLS based on the cached copy.
  • The id was not changed. Senders re-read the file when the id changes or max_age runs out. An edited policy with the old identifier can go unnoticed for weeks.
  • A redirect to www or anywhere else. RFC 8461 forbids senders to follow any 3xx response, so the policy is out of their reach.
  • The domain itself in the mx lines. The policy says mx: example.com instead of the mail server names. The sender matches the host from the MX record, say aspmx.l.google.com, against the policy, finds no match, and under enforce the message does not go out.
  • A forgotten backup MX. The primary is listed and has a good certificate, the secondary does not. Errors appear the moment the primary goes down.

MTA-STS on the sending side: Postfix and mta-sts-daemon

Everything above protects mail coming to you. If you run your own mail server, it can also honour other domains' policies when sending. Postfix does not check MTA-STS by itself; the postfix-mta-sts-resolver package does that. Install it with python3 -m pip install postfix-mta-sts-resolver; it runs the mta-sts-daemon on port 8461, and Postfix asks it for the policy of each recipient domain:

# /etc/mta-sts-daemon.yml
host: 127.0.0.1
port: 8461
cache:
  type: sqlite
  options:
    filename: "/var/lib/mta-sts/cache.db"
default_zone:
  strict_testing: false
  timeout: 4

# /etc/postfix/main.cf
smtp_tls_policy_maps = socketmap:inet:127.0.0.1:8461:postfix

After restarting the daemon and reloading Postfix, mail to domains with mode: enforce goes out only over TLS with certificate validation.

MTA-STS and DANE

Both solve the same problem in different ways. DANE publishes certificate data in DNS and needs working DNSSEC. MTA-STS makes do with ordinary DNS and an https certificate, so it is easier to deploy. Its one weak spot: an attacker who controls DNS can hide the policy on the very first contact, before the sender has cached it, which is why the RFC recommends a long max_age.

They do not conflict. RFC 8461 forbids a sender from letting MTA-STS validation override a failed DANE validation, so where both exist, DANE wins. More on it in the DANE for email guide.

Sources

Questions

Nearby

Encryption is set, now check the list

MTA-STS covers the channel, not the addresses. uChecker checks whether the mailboxes you write to actually exist. The first 100 addresses are free.

Check your list