MTA-STS Example: A Working Policy File and DNS Record (RFC 8461)
MTA-STS adoption sits at 0.3% — 15,997 of 5,499,028 domains scanned by DMARCguard’s first-party study. The blocker is rarely the protocol; it is the absence of a copy-pasteable example. Below are two artefacts you can publish today for example.com, then we explain every line.
Artefact 1 — DNS TXT record at _mta-sts.example.com:
_mta-sts.example.com. 300 IN TXT "v=STSv1; id=20260513120000Z;" Artefact 2 — Policy file served at https://mta-sts.example.com/.well-known/mta-sts.txt:
version: STSv1
mode: enforce
mx: mx1.example.com
mx: mx2.example.com
max_age: 1209600 Replace example.com with your domain, replace the mx: lines with your real inbound MX hostnames, and you have a working RFC 8461 MTA-STS deployment. This MTA-STS example is the artefact-first version of the full RFC 8461 walkthrough — the learn page covers the threat model and history; this post is the file you ship.
What MTA-STS Does in 60 Seconds
MTA-STS — short for Mail Transfer Agent Strict Transport Security — tells sending mail servers “use TLS or do not deliver.” Without it, SMTP between mail servers relies on opportunistic STARTTLS, which an active attacker on the path can strip. The sender sees the stripped offer, falls back to plaintext, and the message is read or modified in transit. MTA-STS is the published commitment that closes that downgrade hole.
The protocol has two components, and both must be present for senders to honor the policy:
- A DNS TXT record at
_mta-sts.<domain>carryingv=STSv1and anid=token (RFC 8461 §3.1). The TXT record is the beacon — it tells senders “go fetch a policy.” - A policy file at
https://mta-sts.<domain>/.well-known/mta-sts.txt, served over HTTPS with a valid certificate (RFC 8461 §3.2). The policy file carries the actual rules: which MX hostnames to trust, which mode to enforce, how long to cache.
Senders that already trust your DNS — anyone running Postfix, Exim, Halon, Microsoft Exchange Online, Google’s outbound infrastructure — read the TXT, fetch the policy file, and refuse delivery if the receiving MX does not present a valid certificate matching the policy. The threat model — tls downgrade attack against STARTTLS — is what MTA-STS exists to neutralize. RFC 8461 §1 gives the full motivation; the TL;DR is that opportunistic encryption is not a security guarantee, and MTA-STS makes the guarantee explicit.
The MTA-STS DNS Record, Annotated
The TXT record is short by design. Render the DNS-record artefact again, then walk every token:
_mta-sts.example.com. 300 IN TXT "v=STSv1; id=20260513120000Z;" | Token | Meaning |
|---|---|
_mta-sts.example.com. | Fixed prefix per RFC 8461 §3.1. The leading underscore label is mandatory; senders look up exactly this name. |
300 | TTL in seconds. A short TTL is intentional so id= rotation propagates fast. |
IN TXT | DNS class and type. MTA-STS uses TXT, not a custom record type. |
"v=STSv1; id=20260513120000Z;" | The two required fields. v=STSv1 is the only legal version today; id= is any token up to 32 alphanumeric characters that uniquely identifies this revision of the policy file. |
The id= rule is the rule that catches operators (RFC 8461 §3.1, §5.1). Any change to the policy file MUST come with a new id. Senders cache by id; if you edit the policy without rotating the id, your edits are invisible until the cached max_age window expires — which can be days or weeks.
Three real id= styles in the wild:
- ISO-like Z form —
20251202110430Z(posteo.de, captured 2026-04-29). - Compact ISO with
T—20230615T153000(Cloudflare Email Routing, launch blog 2023-10-26). - Unix epoch —
1638997389(comcast.net, equates to 2021-12-08 21:03:09 UTC).
All three are RFC-legal. Pick one and keep it consistent — the id is opaque to senders, so the only thing that matters is monotonic novelty.
The most common mta-sts record mistakes we see in production deployments — sourced from URIports’ January 2026 top-1M survey:
- Missing A or AAAA records on the policy host. 34% of invalid MTA-STS deployments have no IP for
mta-sts.<domain>, which means the policy fetch fails before TLS even starts. - Forgetting the underscore prefix. A TXT at
mta-sts.example.com(no underscore) is invisible to senders. - Reusing the same
id=after editing the policy. The edit ships, senders ignore it, the operator is confused.
The MTA-STS Policy File, Field by Field
The policy file is also short. Render it again with annotations:
version: STSv1
mode: enforce
mx: mx1.example.com
mx: mx2.example.com
max_age: 1209600 Four directives, all mandatory in mode: enforce policies:
version: STSv1— the only legal value today (RFC 8461 §3.2). When STSv2 ships, this changes; until then, anything else is invalid.mode:— one ofnone,testing,enforce. Each is a complete deployment posture; we cover all three as full policy files in the next section.mx:— one entry per inbound MX hostname. RFC 8461 §4.1 explicitly permits a leading wildcard prefix (*.aspmx.l.google.com), and every captured tier-1 provider policy uses one.max_age:— sender cache duration in seconds. This is how long a successfully-fetched policy stays sticky on the sender side without re-fetching.
The max_age reality check, captured 2026-04-29 against six tier-1 mailbox providers:
| Domain | mode | max_age (s) | max_age (human) |
|---|---|---|---|
| google.com | enforce | 86400 | 1 day |
| gmail.com | enforce | 86400 | 1 day |
| googlemail.com | enforce | 86400 | 1 day |
| microsoft.com | enforce | 604800 | 7 days |
| outlook.com | enforce | 604800 | 7 days |
| yahoo.com | testing | 86400 | 1 day |
Google’s three domains all sit at 86400 — the protocol’s effective floor — trading attack-window protection for the ability to roll a policy change in 24 hours. Microsoft’s two domains sit at 604800 (7 days), trading agility for reduced sender refresh load. The UK NCSC recommends 1209600 (14 days) for enforce; the maximum permitted by the spec is 31557600 (1 year).
Two operational quirks worth knowing:
- Wildcard MX is universal among hyperscalers despite NCSC discouragement. Every captured tier-1 policy uses
mx: *.<something>— Google’s*.aspmx.l.google.com, Microsoft’s*.mail.protection.outlook.com, Yahoo’s*.am0.yahoodns.net. NCSC’s guidance is to enumerate, but at hyperscale, enumeration is impractical. - Line endings are CRLF per RFC 8461 §3.2, but Yahoo serves LF-only. Strict parsers may reject LF-only bodies. If you hand-write the file on Linux, run it through a sanity check; static-hosting platforms generally serve CRLF without further work.
Skip the hand-edit entirely — paste your domain into our MTA-STS generator and download a ready-to-host policy file with valid CRLF.
Mode Progression Playbook — none → testing → enforce
This is the section the SERP is missing. No top-10 page renders all three mta-sts policy modes as three full, copy-pasteable policy files. Here they are.
Mode none — formally repeals a previously-published policy. Per RFC 8461 §5, senders only pick up the new mode after their cached policy expires (max_age) or the TXT id is rotated to force a refetch — there is no auto-flush. Once the new policy lands, subsequent connections skip enforcement. Use for graceful retirement before deletion:
version: STSv1
mode: none
mx: mx1.example.com
mx: mx2.example.com
max_age: 86400 Mode testing — publishes the policy and lets sender MTAs validate against it, but failures are reported via TLS-RPT instead of bouncing mail. This is your monitoring window:
version: STSv1
mode: testing
mx: mx1.example.com
mx: mx2.example.com
max_age: 86400 Mode enforce — bouncing mode. Senders refuse delivery if TLS fails or the receiving MX is not in the mx: list:
version: STSv1
mode: enforce
mx: mx1.example.com
mx: mx2.example.com
max_age: 1209600 Population data
Among MTA-STS-publishing domains in the top 1M, URIports’ January 2026 survey finds approximately 54% sit at enforce and 45% at testing. The split has been remarkably stable over three years — meaning “indefinite testing” is a normal-but-not-ideal posture rather than a transient state. Yahoo is the canonical example: their yahoo.com policy has been in mode: testing for seven-plus years. Senders see the beacon, gain TLS-RPT visibility, and gain zero downgrade-attack protection.
Decision criteria for the flip
Every primary source publishes the same shape of advice — stay in testing until the reports go quiet — and a different minimum duration:
- Google Workspace: “We recommend starting with testing mode for 2 weeks. 2 weeks of report data is enough.”
- UK security.gov.uk: “monitoring your email traffic for at least 2 weeks to check that there are no failures in your TLS-RPT reports.”
- NCSC: “monitoring your email traffic for the period of at least a month.”
- Mimecast: “Generally, at least 4-6 weeks of testing mode data should be considered.”
The TLS-RPT-driven trigger is concrete: zero sts-policy-fetch-error, zero certificate-host-mismatch, and zero certificate-expired reports across the chosen window. URIports’ 2026 data shows those three failure types dominate the misconfigured tail (34%, 5%, and 6% of invalid deployments respectively), and the IMC ‘25 measurement paper confirms that 94.5% of TLS errors on self-managed policy hosts come from CN/SAN mismatches.
Rollback hard rule
Set up your reporting channel before flipping anything — see TLS-RPT report explained for the JSON anatomy and per-failure remediation.
Multi-Provider Examples
No top-10 SERP page covers Google Workspace, Microsoft 365, and Postfix in a single article. Here are all three.
MTA-STS for Google Workspace
For a custom domain whose MX points to Google Workspace inbound (aspmx.l.google.com plus the alt1–alt4 fallbacks), the canonical policy is one wildcard plus one explicit hostname:
version: STSv1
mode: enforce
mx: aspmx.l.google.com
mx: *.aspmx.l.google.com
max_age: 604800 Google’s own google.com policy adds smtp.google.com (their outbound submission host, port 587) as a defensive belt-and-suspenders entry alongside the inbound MXes — harmless redundancy, since senders never deliver to it. For a Workspace tenant, leave it out. Gmail consumer mailboxes use a different MX namespace (gmail-smtp-in.l.google.com); if you forward to consumer Gmail, that hostname goes here instead.
MTA-STS for Microsoft 365 / Exchange Online
Microsoft Learn endorses this exact body verbatim for any tenant whose MX points to Exchange Online Protection:
version: STSv1
mode: enforce
mx: *.mail.protection.outlook.com
max_age: 604800 One wildcard covers the entire EOP namespace. Watch for the mta-sts office 365 consumer-vs-tenant trap: Outlook.com Consumer (outlook.com, hotmail.com, live.com, msn.com) lives behind *.olc.protection.outlook.com, not *.mail.protection.outlook.com. The two namespaces are not interchangeable. If you operate an Outlook.com consumer namespace through the Microsoft DKIM/DMARC stack, swap the mx: line to the olc form.
Postfix sender-side coexistence
If you run Postfix outbound and want both DANE and MTA-STS — see What is DANE? for the protocol — the order of resolvers in smtp_tls_policy_maps is the precedence rule. The default ordering puts MTA-STS ahead of DANE, which silently overrides DANE’s authenticated channel:
# /etc/postfix/main.cf — DANE-first, MTA-STS as fallback.
# Resolver order is the precedence rule. List the DANE policy resolver
# (responding `dane-only` for Mandatory DANE) BEFORE the MTA-STS resolver,
# otherwise a resolved MTA-STS policy silently overrides DANE.
smtp_dns_support_level = dnssec
smtp_tls_security_level = dane
smtp_tls_policy_maps =
socketmap:unix:/run/dane-policy/dane-policy.sock:dane
socketmap:inet:127.0.0.1:8461:postfix The footgun is documented verbatim in the upstream postfix-mta-sts-resolver README: a resolved MTA-STS policy overrides DANE because the resolver responds with secure for any successful policy lookup. The fix — list the DANE policy resolver (responding dane-only) before the MTA-STS resolver — is the safe configuration.
Hosting-Provider Recipes for .well-known/mta-sts.txt
The 0.3% adoption rate is a hosting-stack problem, not a DNS problem. Of ten mainstream stacks, exactly one ships a first-party MTA-STS recipe (Cloudflare Workers); the rest work but require composition. Friction scores below are 1 (paste-and-go) to 5 (multi-service wiring). The well known mta sts txt path is the same on every host.
| Stack | Friction | What you ship | Notes |
|---|---|---|---|
| Cloudflare Workers | 1 | A Worker that returns the policy on /.well-known/mta-sts.txt | The only vendor with a published RFC 8461 recipe; Custom Domain on mta-sts.<domain> provisions DNS + cert automatically. Great mta-sts cloudflare answer. |
| Caddy (self-hosted) | 1 | A 4-line site block plus the file | Caddy’s Automatic HTTPS handles ACME, redirects, and renewal as long as ports 80/443 reach the host. |
| Vercel (static) | 2 | public/.well-known/mta-sts.txt | Vercel marks /.well-known as reserved against rewrites; Let’s Encrypt cert auto-issued per added domain. |
| Netlify | 2 | Commit the file, add domain alias | Netlify’s CLI explicitly excludes .well-known from dotfile-stripping; cert auto-renewed. |
| Nginx + certbot | 2 | A vhost on mta-sts.<domain> | certbot --nginx -d mta-sts.<domain>; ACME http-01 lives at /.well-known/acme-challenge/ and does not collide with the MTA-STS path. |
| AWS S3 + CloudFront | 3 | Bucket + distribution + ACM cert | ACM cert MUST live in us-east-1, the SAN must list mta-sts.<domain>, and the distribution’s Alternate Domain Names must match — otherwise CloudFront returns 502. |
| GitHub Pages | 3 | The file + .nojekyll at repo root | Default Jekyll silently strips dotfile directories; without .nojekyll, /.well-known/mta-sts.txt 404s. |
| Cloudflare R2 (public) | 3 | Public bucket + Custom Domain on mta-sts.<domain> | Object-storage path is fine; ensure the r2.dev reserved domain is not the only binding — public access requires a Custom Domain. |
| Cloudflare Pages | 4 | Pages + a Worker in front | Pages does not reliably serve /.well-known/; Cloudflare staff recommend fronting Pages with a Worker, in which case the Workers recipe applies. |
The Cloudflare Workers recipe in five lines:
// Bind this Worker to a Custom Domain on `mta-sts.example.com`.
// Cloudflare provisions DNS + an edge cert automatically.
const POLICY = `version: STSv1
mode: enforce
mx: mx1.example.com
mx: mx2.example.com
max_age: 1209600
`;
export default {
async fetch(request) {
const url = new URL(request.url);
if (url.pathname !== "/.well-known/mta-sts.txt") {
return new Response("Not found", { status: 404 });
}
return new Response(POLICY, {
headers: { "content-type": "text/plain; charset=utf-8" },
});
},
}; The Caddy site block:
mta-sts.example.com {
root * /var/www/mta-sts
file_server
} The Nginx vhost (run certbot --nginx -d mta-sts.example.com first):
# /etc/nginx/sites-enabled/mta-sts.example.com.conf
# Issue cert: certbot --nginx -d mta-sts.example.com
server {
listen 443 ssl http2;
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;
alias /var/www/mta-sts/.well-known/mta-sts.txt;
}
} Two production-failure stories worth keeping bookmarked:
“I have successfully connected my custom domain to GitHub and enforced HTTPS… When I visit
https://mta-sts.emberclay.com/.well-known/mta-sts.txtI am seeing a 404 error.” — github.com/orgs/community/discussions/122773, 2024-05-07
The fix was a one-line .nojekyll file at the repo root. Default GitHub Pages strips dotfile directories, so .well-known/ never makes it to the build output.
“The connection for
https://mta-sts.example.com/.well-known/mta-sts.txton port 443 got refused. I switched the port to 443 for testing purposes and it worked as expected.” — github.com/mailcow/mailcow-dockerized/issues/6739, 2025-09-12
mailcow hard-wires its MTA-STS endpoint to port 443. Admins fronting mailcow with another reverse proxy on a non-443 HTTPS_PORT cannot serve the policy at all without patching the upstream.
MTA-STS + DANE Coexistence
If your MX hostnames live in a DNSSEC-signed zone, publish DANE TLSA records alongside MTA-STS. If not, MTA-STS-only is the honest answer — do not publish a TLSA you cannot anchor in DNSSEC. Verified dual-stack publishers in 2026 are a small club:
| Domain | TLSA captured | MTA-STS mode | max_age |
|---|---|---|---|
| posteo.de | 3 1 1 a73a9adb16bd5a4131df79c446438e138da78fbb64d4ebad97017a4fad4ec92e (DANE-EE/SPKI/SHA-256) | enforce | 1209600 (~14 days) |
| mailbox.org | 3 1 1 996ad31d65e03f038b8ec950f6f26611529da03e3a283e4400cba2edd04b8a88 | enforce | 2419200 (~28 days) |
| protonmail.com | 3 1 1 6111A5698D23C89E09C36FF833C1487EDC1B0C841F87C49DAE8F7A09E11E979E (paired with 3 1 1 76BB66711D…) | enforce | 604800 (7 days) |
| comcast.net | 3 1 1 29b116c43593748345aa7f4c43717e792f94137a88b93d674de2ce1162f98625 | enforce | 2592000 (30 days) |
gmail.com is the canonical counter-example: Google’s MX zones are unsigned, so DANE is structurally unavailable. Google publishes a working _mta-sts.gmail.com and stops there. The right move is the same one Google made: publish what you can anchor, omit what you cannot.
Sender precedence — what fires when both apply
- Postfix with
postfix-mta-sts-resolver. A resolved MTA-STS policy silently overrides DANE unless the operator orders resolvers correctly insmtp_tls_policy_maps. The README is unusually honest about this: list the DANE policy resolver (respondingdane-onlyfor Mandatory DANE) before the MTA-STS resolver. The Postfix snippet earlier in this post is the safe ordering. - Exim. Native DANE only; MTA-STS requires a third-party Perl event handler. Default behaviour favors DANE.
- Microsoft Exchange Online. Per the May 2025 connector-modes blog, two enforcement modes exist — Opportunistic (best-effort DANE and MTA-STS) and Mandatory SMTP DANE only. There is no “Mandatory MTA-STS” mode. EXO treats DANE as authoritative; MTA-STS rides alongside as an opportunistic check.
The clean cross-vendor reading: no major MTA’s first-party docs say “an MTA-STS pass can rescue a DANE failure.” Where both are implemented natively, DANE is documented as the stricter check.
Failure receipt
In June 2024, on the public mailop@mailop.org list, operators reported Exchange Online rejecting dual-stack mail at scale with 550 5.7.324 dnssec-invalid: Destination domain returned invalid DNSSEC records. Root cause: the published RRset contained 14 duplicate TLSA records (one for every Let’s Encrypt intermediate), which RFC 6698 forbids. An MTA-STS pass cannot rescue a strict-DNSSEC rejection — Microsoft does not document MTA-STS as an override path.
The operational argument for publishing both is asymmetric: when your MTA-STS host’s Let’s Encrypt cert fails to renew on Sunday morning, DANE-aware senders deliver via the TLSA channel; when a DNSSEC-skeptical sender skips the TLSA check, MTA-STS catches it. Pair both with TLS-RPT report explained so you see the failure before the helpdesk does.
Validate Your Deployment
Three commands, in this order:
dig +short TXT _mta-sts.your-domain.com— confirm the TXT resolves and the body matchesv=STSv1; id=....curl -sS https://mta-sts.your-domain.com/.well-known/mta-sts.txt— confirm HTTP 200, valid TLS, parseable body.dig +short TLSA _25._tcp.your-mx.your-domain.com— only if you publish DANE; sanity-check coexistence.
Three tools that handle this in one paste:
- MTA-STS checker — paste a domain, get TXT + policy validation, plus diff if the two disagree.
- MTA-STS generator — paste your MX hostnames, get a ready-to-host policy file with valid CRLF.
- TLS-RPT generator — pair MTA-STS with reporting before flipping
mode: enforce.
The failure-mode quick reference, sourced from URIports’ top-1M survey:
| Failure | Share of invalid deployments |
|---|---|
Missing A or AAAA on mta-sts.<domain> | 34% |
| HTTPS certificate problems on the policy host | 25% |
| Expired cert on the policy host | 6% |
| Common Name / SAN mismatch | 5% |
Three of those four are TLS-cert problems on the policy host. If the mta-sts test returns clean today and you do not monitor the cert, you will learn about its expiry from a delivery incident, not a dashboard.
FAQ
What is the syntax of MTA-STS?
The MTA-STS DNS TXT record uses two fields: v=STSv1 (mandatory version) and id=<unique-token> (changes whenever the policy file changes). The HTTPS policy file uses four directives: version: STSv1, mode: none|testing|enforce, one or more mx: lines, and max_age: in seconds. RFC 8461 §3.1 covers the TXT record; §3.2 covers the policy file body.
How do I verify my MTA-STS record?
Run dig +short TXT _mta-sts.your-domain.com to confirm the TXT exists, then curl -sS https://mta-sts.your-domain.com/.well-known/mta-sts.txt to confirm the policy file fetches over HTTPS with a valid certificate. DMARCguard’s MTA-STS checker does both in one paste and flags any mismatch between the two.
What does mode: testing actually do?
testing mode publishes the policy and lets sender MTAs validate against it, but failures are reported via TLS-RPT instead of bouncing mail. Stay in testing for at least 2 weeks (Google Workspace minimum) to 1 month (NCSC). Approximately 45% of top-1M MTA-STS deployments still sit in testing per URIports’ January 2026 survey, so indefinite testing is common.
Can I use a wildcard mx: value?
Yes — RFC 8461 §4.1 permits wildcard prefixes like *.aspmx.l.google.com. Every captured tier-1 provider policy uses them. UK NCSC guidance discourages wildcards on principle, but for hyperscale receivers (Google, Microsoft, Outlook.com) enumerating every MX hostname is impractical and the wildcard is the only usable form.
Should I publish MTA-STS and DANE together?
Yes, if your MX hostnames are in DNSSEC-signed zones. Publishing both gives you a working channel during the other’s outage — DANE carries traffic when your MTA-STS cert fails to renew, MTA-STS carries traffic when a sender skips DNSSEC. If your zone is unsigned, MTA-STS-only is the honest answer; do not publish a TLSA you cannot anchor.
What’s the difference between MTA-STS and TLS-RPT?
MTA-STS is the policy (“require TLS or refuse”); TLS-RPT is the telemetry channel (“send me a JSON report when TLS validation fails”). They are independent records but operationally inseparable — flipping MTA-STS to enforce without TLS-RPT means failures bounce silently. Pair them via _smtp._tls.<domain>. See TLS-RPT report explained.
Why is MTA-STS adoption only 0.3%?
Hosting friction. DMARCguard’s scan of 5,499,028 domains found 15,997 MTA-STS deployments. The DNS record is trivial; the HTTPS-served policy file requires a static-hosting stack with auto-TLS on mta-sts.<domain>. Only Cloudflare Workers ships a first-party MTA-STS recipe today; eight other mainstream stacks need composition, and the failure modes are silent (Jekyll strips the directory, CloudFront 502s on a SAN miss).
Conclusion
Two artefacts are all it takes to publish a working mta-sts example: the _mta-sts.<domain> TXT record carrying v=STSv1 and a unique id=, and the policy file at https://mta-sts.<domain>/.well-known/mta-sts.txt carrying version, mode, mx, and max_age. Five rules carry every safe MTA-STS deployment:
- Rotate
id=whenever the policy file changes — senders cache byid, not by content. - Stay in
mode: testingfor at least 2 weeks; flip tomode: enforceonly when TLS-RPT reports show zero failures. - Publish
mode: nonefor one fullmax_agecycle before deleting the host — never just delete. - Pair with TLS-RPT every time, and with DANE when your zone is DNSSEC-signed.
- Monitor the policy-host TLS certificate; expiry is the single most common silent failure.
The 0.3% adoption number is a hosting-friction story, not a protocol story; with both artefacts above, the file you are missing is now the one above your scrollbar.