---
title: "MTA-STS Example: Working Policy File & DNS Record [2026]"
description: "Copy-paste a working MTA-STS DNS record and .well-known policy file. RFC 8461 examples for Google Workspace, Microsoft 365, Postfix, plus mode progression and validators."
publishedAt: 2026-05-13
tags: ["mta-sts", "tls", "rfc-8461", "smtp", "transport-security"]
faq:
  - question: "What is the syntax of MTA-STS?"
    answer: "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."
  - question: "How do I verify my MTA-STS record?"
    answer: "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."
  - question: "What does mode: testing actually do?"
    answer: "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."
  - question: "Can I use a wildcard mx: value?"
    answer: "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."
  - question: "Should I publish MTA-STS and DANE together?"
    answer: "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."
  - question: "What's the difference between MTA-STS and TLS-RPT?"
    answer: "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>."
  - question: "Why is MTA-STS adoption only 0.3%?"
    answer: "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."
---
# 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](/research/email-authentication/) 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`:**

<CodeBlock
  lang="dns"
  filename="_mta-sts.example.com TXT record"
  code={dnsRecord}
/>

**Artefact 2 — Policy file served at
`https://mta-sts.example.com/.well-known/mta-sts.txt`:**

<CodeBlock
  lang="yaml"
  filename=".well-known/mta-sts.txt"
  code={wellKnownPolicy}
/>

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](/learn/mta-sts/) — 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>` carrying `v=STSv1` and an
  `id=` 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.

<Figure
  src="/images/blog/mta-sts-example/mta-sts-example_two-component-model_diagram.svg"
  alt="MTA-STS DNS TXT record + HTTPS policy file two-component model"
  caption="A sender that already trusts your DNS reads the TXT, fetches the policy file, and refuses delivery if the receiving MX does not present a valid certificate matching the policy."
/>

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:

<CodeBlock
  lang="dns"
  filename="_mta-sts.example.com TXT record"
  code={dnsRecord}
/>

<DataTable caption="Token-by-token meaning of the MTA-STS TXT record">

| 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. |

</DataTable>

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](https://www.uriports.com/blog/mta-sts-survey-update-2026/):

- **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.

<CTA
  title="Validate your TXT record in seconds"
  description="Paste your domain into our MTA-STS checker. We resolve the TXT, fetch the policy file, and surface every mismatch in one report."
  buttonText="Open MTA-STS checker"
  buttonLink="/tools/mta-sts-checker/"
/>

## The MTA-STS Policy File, Field by Field

The policy file is also short. Render it again with annotations:

<CodeBlock
  lang="yaml"
  filename=".well-known/mta-sts.txt"
  code={wellKnownPolicy}
/>

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 of `none`, `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:

<DataTable caption="max_age values across tier-1 mailbox providers (captured 2026-04-29)">

| 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           |

</DataTable>

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](/tools/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.

<Figure
  src="/images/blog/mta-sts-example/mta-sts-example_mode-progression_flowchart.svg"
  alt="MTA-STS mode progression flowchart: none, testing, enforce with TLS-RPT decision gate"
  caption="The TLS-RPT decision gate: flip from testing to enforce only when sts-policy-fetch-error, certificate-host-mismatch, and certificate-expired counters all sit at zero."
/>

**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:

<CodeBlock
  lang="yaml"
  filename=".well-known/mta-sts.txt — mode: none (repeal)"
  code={modeNone}
/>

**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:

<CodeBlock
  lang="yaml"
  filename=".well-known/mta-sts.txt — mode: testing"
  code={modeTesting}
/>

**Mode `enforce`** — bouncing mode. Senders refuse delivery if TLS fails or
the receiving MX is not in the `mx:` list:

<CodeBlock
  lang="yaml"
  filename=".well-known/mta-sts.txt — mode: enforce"
  code={modeEnforce}
/>

### Population data

Among MTA-STS-publishing domains in the top 1M,
[URIports' January 2026 survey](https://www.uriports.com/blog/mta-sts-survey-update-2026/)
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

<Callout type="warning" title="Never just delete the policy file">
  Senders cache by `id` for `max_age` seconds, and a cached `enforce`
  policy outlives operator amnesia. The Mail-in-a-Box → Yandex incident
  from January 2021 is the canonical receipt: an operator decommissioned
  MIAB, repointed MX to Yandex, and deleted the MTA-STS policy. Gmail kept
  enforcing the cached policy for roughly a week, bouncing inbound mail
  with `The MX host does not match any MX allowed by the STS policy`. The
  safe rollback is to publish `mode: none` for at least one full `max_age`
  cycle, let the cache flush, then retire the host.
</Callout>

Set up your reporting channel before flipping anything — see
[TLS-RPT report explained](/blog/tls-rpt-report/) 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:

<CodeBlock
  lang="yaml"
  filename=".well-known/mta-sts.txt — Google Workspace tenant"
  code={googleWorkspace}
/>

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:

<CodeBlock
  lang="yaml"
  filename=".well-known/mta-sts.txt — Microsoft 365 / Exchange Online"
  code={microsoft365}
/>

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?](/blog/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:

<CodeBlock
  lang="ini"
  filename="/etc/postfix/main.cf — DANE-first, MTA-STS as fallback"
  code={postfixPolicyMaps}
/>

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.

<DataTable caption="Hosting stack recipes for mta-sts.<domain> with friction scores">

| 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.                    |

</DataTable>

The Cloudflare Workers recipe in five lines:

<CodeBlock
  lang="js"
  filename="cloudflare-worker.js"
  code={cloudflareWorker}
/>

The Caddy site block:

<CodeBlock lang="caddy" filename="Caddyfile" code={caddyfile} />

The Nginx vhost (run `certbot --nginx -d mta-sts.example.com` first):

<CodeBlock lang="nginx" filename="mta-sts.example.com.conf" code={nginxConf} />

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.txt` I 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.txt`
> on 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:

<DataTable caption="Verified MTA-STS + DANE dual-stack publishers (captured 2026-04-29)">

| 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)    |

</DataTable>

`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.

<Figure
  src="/images/blog/mta-sts-example/mta-sts-example_dane-coexistence_diagram.svg"
  alt="MTA-STS and DANE coexistence: precedence rules in Postfix, Exim, Exchange Online"
  caption="No major MTA documents 'an MTA-STS pass rescues a DANE failure.' Where both are native, DANE is the stricter check."
/>

### 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
  in `smtp_tls_policy_maps`. The README is unusually honest about this:
  list the DANE policy resolver (responding `dane-only` for 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](/blog/tls-rpt-report/) so you see the failure
before the helpdesk does.

## Validate Your Deployment

Three commands, in this order:

1. **`dig +short TXT _mta-sts.your-domain.com`** — confirm the TXT
   resolves and the body matches `v=STSv1; id=...`.
2. **`curl -sS https://mta-sts.your-domain.com/.well-known/mta-sts.txt`** —
   confirm HTTP 200, valid TLS, parseable body.
3. **`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](/tools/mta-sts-checker/) — paste a domain, get TXT +
  policy validation, plus diff if the two disagree.
- [MTA-STS generator](/tools/mta-sts-generator/) — paste your MX
  hostnames, get a ready-to-host policy file with valid CRLF.
- [TLS-RPT generator](/tools/tls-rpt-generator/) — pair MTA-STS with
  reporting before flipping `mode: enforce`.

The failure-mode quick reference, sourced from
[URIports' top-1M survey](https://www.uriports.com/blog/mta-sts-survey-update-2026/):

<DataTable caption="Most common MTA-STS failure modes among invalid deployments">

| 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%                           |

</DataTable>

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](/tools/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](/blog/tls-rpt-report/).

### 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 by `id`,
  not by content.
- Stay in `mode: testing` for at least 2 weeks; flip to `mode: enforce`
  only when TLS-RPT reports show zero failures.
- Publish `mode: none` for one full `max_age` cycle 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.

<CTA
  title="Validate your MTA-STS deployment in seconds"
  description="Paste your domain into DMARCguard's MTA-STS checker. We resolve the TXT, fetch the policy file, and surface every mismatch in one report — then pair it with our TLS-RPT generator before you flip enforce."
  buttonText="Open MTA-STS checker"
  buttonLink="/tools/mta-sts-checker/"
/>

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.