Inbox
articleJune 9, 20267 min read

MTA-STS Setup: Publish a Policy for Your Domain

Publish an MTA-STS policy that forces senders to deliver mail to your MX hosts over verified TLS. A practical walkthrough of the HTTPS file, the TXT record, and the modes that matter.

By SESMetric Editorial

Plain SMTP still falls back to cleartext when STARTTLS fails or when a man-in-the-middle strips it. MTA-STS (RFC 8461) closes that gap by letting your domain publish a signed-by-HTTPS policy that tells sending MTAs, "you must reach my MX hosts over TLS, and here is the list of names that count."

This guide walks through a complete MTA-STS setup: the HTTPS-served policy file, the discovery TXT record, the three policy modes, the companion TLS-RPT record, and the deployment traps that bite teams the first time they roll this out.

How MTA-STS works in 30 seconds

A sending MTA that wants to deliver to example.com does two lookups before opening an SMTP connection:

  1. A TXT lookup at _mta-sts.example.com to find out whether a policy exists and what its current id is.
  2. An HTTPS GET to https://mta-sts.example.com/.well-known/mta-sts.txt to fetch the policy itself.

The policy lists your MX hostnames, the enforcement mode, and how long the sender may cache it. If the mode is enforce and the MX certificate does not match a name in the policy, the sender must fail the delivery rather than fall back to plaintext.

That is the whole protocol. The work is in publishing those two records correctly and keeping them in sync with reality.

Prerequisites for MTA-STS setup

Before you publish anything, confirm:

  • Every host listed in your MX records terminates STARTTLS with a certificate that is valid for the MX hostname (not just the domain).
  • You control DNS for example.com and can add subdomains.
  • You can serve HTTPS on mta-sts.example.com with a publicly trusted certificate.
  • You can edit DNS to add a second TXT subdomain for TLS-RPT reporting.

If any MX host is on a shared platform and the cert does not match the MX hostname, fix that first. Publishing an enforce policy that points at a mismatched cert will silently break inbound mail from MTA-STS-aware senders.

Step 1: Host the policy file over HTTPS

The policy lives at a fixed path on a fixed subdomain:

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

The subdomain name mta-sts is mandatory. The path /.well-known/mta-sts.txt is mandatory. The file is served with Content-Type: text/plain. It looks like this:

version: STSv1
mode: enforce
mx: mx1.example.com
mx: mx2.example.com
mx: *.mail.example.com
max_age: 604800

A few things to notice:

  • version: STSv1 is the only valid version today.
  • mode is one of testing, enforce, or none. More on those below.
  • Each MX entry gets its own mx: line. Wildcards work for a single label, so *.mail.example.com matches a.mail.example.com but not mail.example.com itself.
  • max_age is the cache lifetime in seconds. RFC 8461 caps it at 31557600 (about a year). A week (604800) is a reasonable starting value.

The file is line-oriented, plain text, LF or CRLF, no JSON, no signature. The TLS handshake on the HTTPS endpoint is what authenticates it.

TLS requirements for the policy host

The HTTPS endpoint must present a certificate that is valid for mta-sts.example.com. Self-signed certificates are rejected. Senders verify the chain against the public CA bundle exactly like a browser would. A redirect from mta-sts.example.com to another host is not allowed for the policy fetch.

If you front the host with a CDN, make sure the CDN's edge certificate covers mta-sts.example.com and that the cache returns the current policy promptly when you update it.

Step 2: Publish the discovery TXT record

The TXT record at _mta-sts.example.com tells senders that a policy exists and gives it a version identifier. Add this record at your DNS provider:

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

The id is an opaque string up to 32 characters. Senders cache it alongside the policy file. When you change the policy file, you must also change id; that is the signal for senders to drop their cached policy and refetch.

A common convention is a timestamp like 20260519T120000 or a short UUID. Whatever you pick, treat id as a version pin: every policy edit gets a new id in the same deploy.

If the TXT record is missing or malformed, senders that previously cached your policy keep using their cached copy until max_age expires, and new senders treat your domain as if MTA-STS were not configured. There is no harm in publishing the TXT record before the HTTPS file is live, because senders cannot fetch a policy without the file, but there is no benefit either. Land them together.

Step 3: Pick a policy mode

The mode field decides what a sender does when the MX certificate does not match the policy:

  • none — the domain explicitly declines MTA-STS. Use this only to retract a previously published policy without leaving stale caches in place.
  • testing — the sender reports failures via TLS-RPT but still delivers the mail. This is the safe rollout mode.
  • enforce — the sender must refuse to deliver if the MX cert does not match. This is the production mode that actually closes the downgrade gap.

Start in testing for at least one full max_age cycle. Read the TLS-RPT reports. Fix every reported mismatch. Only then change mode: testing to mode: enforce (and bump id).

Step 4: Add the TLS-RPT companion record

TLS-RPT (RFC 8460) is a separate record that asks sending MTAs to mail you a daily JSON report of TLS negotiation failures, including MTA-STS policy mismatches. Without it, you are flying blind during the testing phase.

Publish a TXT record at _smtp._tls.example.com:

_smtp._tls.example.com.    IN  TXT  "v=TLSRPTv1; rua=mailto:tls-reports@example.com"

You can also send reports to an HTTPS endpoint with rua=https://..., but the mailto: form is the easiest to set up. Use a dedicated inbox; aggregate reports are JSON files attached to an otherwise unremarkable email and not pleasant to read in a personal inbox.

How senders discover and apply the policy

When a sender first contacts your domain:

  1. It resolves the MX records normally.
  2. It looks up _mta-sts.example.com TXT. If the record is absent, MTA-STS does not apply.
  3. It fetches https://mta-sts.example.com/.well-known/mta-sts.txt over HTTPS, verifying the certificate against the public CA bundle.
  4. It caches the policy keyed by the id value for up to max_age seconds.
  5. For each subsequent delivery within max_age, it re-resolves the TXT record. If the id changed, it refetches the file. Otherwise it uses the cached policy.
  6. Before opening SMTP, it checks that the MX hostname appears in the policy's mx: list and that STARTTLS succeeds with a cert valid for that name.

The cache is per sender. You cannot force a global flush. That is what makes the id-pinning discipline matter.

Common MTA-STS deployment gotchas

HTTPS certificate mismatches

The single most common failure mode is the mta-sts host certificate not covering mta-sts.example.com. A wildcard *.example.com cert works. A cert for example.com alone does not. If you use a CDN, verify the edge cert by hand with openssl s_client -connect mta-sts.example.com:443 -servername mta-sts.example.com before publishing the TXT record.

Policy cache staleness

Forgetting to bump id after editing the policy file is a silent bug. Senders happily keep delivering against the old MX list because their cache is still valid. If a delivery report shows the previous policy still in effect, check that id changed and that the TXT record actually propagated.

MX list drift

The mx: list is the canonical source of truth for which hosts may receive your mail under TLS. If you add a new MX in DNS but forget to add it to the policy, MTA-STS-aware senders in enforce mode will refuse delivery to the new host. Treat the policy file as part of the same change set as your MX records, and gate any MX rollout behind a refreshed policy with a new id.

Wildcards do not nest

*.mail.example.com matches one label only. If you operate eu.mx.mail.example.com, you need either an explicit mx: entry or a separate wildcard at the right depth. Test before you flip to enforce.

Policy host downtime

If the HTTPS endpoint is unreachable when a sender tries to refresh a near-expired policy, the sender falls back to the cached copy until max_age runs out, then treats the domain as non-MTA-STS. Long outages on mta-sts.example.com quietly weaken your posture. Monitor the endpoint the same way you monitor your main site.

Testing your policy before enforce

Land a working setup in three checkpoints.

First, verify the file and the TXT record exist and parse:

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

You want a 200 OK with content-type: text/plain, a body that begins with version: STSv1, and TXT records that match what you intended.

Second, confirm the MX hosts actually serve a matching certificate:

for mx in $(dig +short MX example.com | awk '{print $2}' | sed 's/\.$//'); do
  echo "== $mx =="
  openssl s_client -starttls smtp -connect "$mx:25" -servername "$mx" </dev/null 2>/dev/null \
    | openssl x509 -noout -subject -ext subjectAltName
done

Every MX hostname (or a wildcard that covers it) must appear in the cert's Subject Alternative Names.

Third, leave the policy in mode: testing for one full max_age cycle and read the TLS-RPT aggregate reports. Zero failures across a week of real sender traffic is the green light to bump id and flip to mode: enforce.

Once enforced, MTA-STS makes the downgrade attack you were worried about visible and unsuccessful. Combined with DKIM for content authentication and a strict DMARC policy for alignment, your domain stops being an easy target for in-transit tampering.

Tagssmtptlsmta-stsdeliverability