MTA-STS Policy File Syntax: version, mode, mx, max_age, the HTTPS+well-known Fetch Contract, BOM/Comment Rules, and the _mta-sts id-Token Coupling

Focused byte-level reference for the MTA-STS policy file at https://mta-sts.<domain>/.well-known/mta-sts.txt: every canonical key per RFC 8461 §3.2 (version: STSv1, mode: enforce|testing|none, mx: with single-label wildcard semantics, max_age: with the one-year hard maximum), the HTTPS+public-PKI fetch contract, the no-BOM and no-comments rules, the id-token coupling with the _mta-sts DNS record, six worked example policies, and ten common operator mistakes — each producing silent policy rejection visible only via TLS-RPT.

This reference is provided in English. MTA-STS is specified in an English-language RFC (RFC 8461) and the canonical syntactic vocabulary (version: STSv1, mode: enforce, mx: mail.example.com, max_age: 604800, .well-known/mta-sts.txt) does not translate. We keep the body in the source language so technical terms remain consistent with the specification.

TL;DR

An MTA-STS policy is a plain-text key-value file served from https://mta-sts.<domain>/.well-known/mta-sts.txt with four mandatory keys. The policy-file syntax is governed by RFC 8461 §3.2.

  • version: STSv1 — mandatory, case-sensitive uppercase STSv1, MUST appear first.
  • mode: enforce|testing|none — mandatory deployment-stage tag, case-sensitive lowercase. enforce applies the policy, testing produces TLS-RPT reports without bouncing, none disables.
  • mx: <pattern> — one or more lines naming acceptable MX hostnames. Each entry is either an exact hostname (mail.example.com) or a single-label wildcard (*.example.com) matching one DNS label of depth-one expansion.
  • max_age: <seconds> — mandatory policy cache lifetime. Maximum 31557600 (one year) per RFC 8461.
  • HTTPS-only — the policy URL MUST use HTTPS with a publicly-trusted CA certificate. Self-signed and private-PKI certs are rejected.
  • No BOM — UTF-8 byte-order marks at file head cause the policy to be rejected (the version line fails to match).
  • No comments — RFC 8461 §3.2 defines only key-value lines; any unrecognised line (including `# comment`) is treated as malformed and the policy is rejected.
  • DNS coupling — the policy file is paired with a _mta-sts.<domain> TXT record carrying an id=<token> tag. Every policy change MUST be accompanied by a new id token so senders re-fetch.

The Domain Security Checker validates the live policy file from multiple geographic resolvers and surfaces every syntactic drift class documented on this page. Use TLS-RPT to monitor policy enforcement outcomes after deployment.

1. What "policy file syntax" means for MTA-STS

MTA-STS is a two-layer specification. The DNS LAYER publishes a _mta-sts.<domain> TXT record with v=STSv1; id=<token> that signals senders WHEN to re-fetch the policy. The POLICY LAYER serves the actual policy content from https://mta-sts.<domain>/.well-known/mta-sts.txt — a plain-text key-value file with the four canonical keys governing what TLS posture senders enforce for inbound mail.

This page covers the POLICY-LAYER SYNTAX exhaustively. The protocol-level context lives in the sibling reference pages:

  • MTA-STS explained — the protocol pillar — what MTA-STS is, how it works at the protocol level, the relationship to STARTTLS, the trust-anchor model, and the deployment workflow.
  • MTA-STS vs DANE for SMTP — the policy-mechanism decision view between MTA-STS (web-PKI trust anchor) and DANE (DNSSEC trust anchor).
  • TLS-RPT explained — the operational feedback loop that makes MTA-STS deployable. Without TLS-RPT, an enforce-mode policy failure is invisible.

Why the policy-file syntax matters independently: a syntactically malformed policy file is silently REJECTED by every conformant sender — the sender falls back to opportunistic TLS as if MTA-STS were not deployed at all. There is no error signal to the recipient operator from this layer; the only visible signal is TLS-RPT reports showing policy-type=no-policy-found (or sts-policy-invalid / sts-policy-fetch-error) when the recipient expects policy-type=sts. The syntactic failure modes documented below — UTF-8 BOM at file head, comments in the file, wrong scheme (HTTP instead of HTTPS), wrong Content-Type, malformed wildcard in mx:, capital-letter mode value — are operator-introduced and trivially avoidable once the syntax is understood.

The canonical MTA-STS policy file:

; URL:           https://mta-sts.example.com/.well-known/mta-sts.txt
; Content-Type:  text/plain
; HTTP status:   200
; Body:

version: STSv1
mode: enforce
mx: mail.example.com
mx: mail2.example.com
max_age: 604800

Every section below documents one syntactic surface of this canonical record.

2. The https://mta-sts.<domain>/.well-known/mta-sts.txt URL

Where: the policy file is served from the well-known location https://mta-sts.<domain>/.well-known/mta-sts.txt per RFC 8461 §3.3, where <domain> is the recipient domain being protected.

Why the mta-sts subdomain: isolating the policy URL under a protocol- specific subdomain lets operators run the MTA-STS HTTPS endpoint on infrastructure independent from the apex web site, with its own CDN, certificate, and routing rules. A common pattern is to point mta-sts.example.com at a CDN edge or a dedicated nginx instance serving static content from /.well-known/mta-sts.txt, while the apex example.com remains on a separate stack.

Operational rule: the sending MTA constructs the URL exactly as https://mta-sts.<recipient-domain>/.well-known/mta-sts.txt and fetches it via a standard HTTPS GET request. The hostname MUST resolve, the TLS certificate MUST validate against the public CA root store for the hostname mta-sts.<domain>, the HTTP status MUST be 200, and the body MUST match the policy-file syntax below.

Common pitfall: serving the policy file from https://example.com/.well-known/mta-sts.txt (apex domain instead of the mta-sts subdomain). RFC 8461 §3.3 specifies the well-known URL on the mta-sts subdomain ONLY; serving from the apex is non-conformant and conformant senders will not discover it. The mta-sts.<domain> name does NOT need to be the same hostname as your MX records — it is purely the policy-serving endpoint.

; DNS — both the apex MX records AND the mta-sts subdomain:
example.com.            IN  MX   10 mail.example.com.
mta-sts.example.com.    IN  A    203.0.113.20
mta-sts.example.com.    IN  AAAA 2001:db8::20

; HTTPS endpoint at mta-sts.example.com serves the policy file:
;   https://mta-sts.example.com/.well-known/mta-sts.txt

3. The HTTPS + public-PKI fetch contract

Scheme: HTTPS only. RFC 8461 §3.3 specifies that senders MUST use HTTPS to fetch the policy file. Plaintext HTTP is rejected — a sender that finds only HTTP available treats the recipient as having no MTA-STS policy.

Certificate trust anchor: the TLS certificate at mta-sts.<domain> MUST validate against the publicly-trusted CA root store (Let's Encrypt, DigiCert, Sectigo, etc.). The same root store senders use for general HTTPS validation applies — self-signed certificates, private-CA certificates, expired certificates, and certificates with hostname mismatches all cause the policy fetch to fail and the policy to be discarded.

Content-Type: the response MUST carry Content-Type: text/plain (or text/plain; charset=utf-8). Other Content-Type values (text/html, application/json, application/octet-stream) cause conformant senders to reject the policy.

HTTP status: the response MUST have status 200. Status 301/302/307/308 redirects MAY be followed by senders (RFC 8461 §3.3) but conformant implementations SHOULD limit the redirect chain to prevent loops. Any 4xx or 5xx response causes the policy fetch to fail.

Operational rule: the endpoint is a high-cardinality fetch target — every sending MTA in the world that decides to deliver mail to your domain will fetch this URL once per cache window. Serve it from a CDN or redundant-edge infrastructure to maintain availability.

Common pitfall: using HTTP-to- HTTPS auto-redirects via a 301 on the mta-sts hostname. Some senders skip redirect-following for the well-known fetch and treat the 301 as a fetch error. Serve the policy file directly via HTTPS GET with status 200, not via redirect.

4. The version: STSv1 tag (RFC 8461 §3.2, MANDATORY)

Tag: version: STSv1
Default: none. The version key is structurally MANDATORY and MUST appear FIRST in the file.
Why: declares the policy as MTA-STS v1. The forward-compatibility hook for any future MTA-STS revision; senders that find an unrecognised version reject the policy and fall back to opportunistic TLS, which is the safe default for unknown protocol versions.
Operational rule: the value is the case-sensitive literal STSv1 — uppercase STS, lowercase v, digit 1. As of 2026 no later version has been published.
Common pitfall: writing version: stsv1 (lowercase) or version: STSv2 (next-version speculation) or version: STS (no v1 suffix). All three cause conformant senders to reject the policy.

; Correct — first line, exact case:
version: STSv1
mode: enforce
mx: mail.example.com
max_age: 604800

; Wrong — lowercase:
version: stsv1
...

; Wrong — version not first:
mode: enforce
version: STSv1
mx: mail.example.com
max_age: 604800

5. The mode: enforce|testing|none tag (RFC 8461 §3.2, MANDATORY)

Tag: mode: <value>
Default: none. The mode key is structurally MANDATORY.
Why: the deployment-stage tag — controls what action sending MTAs take when policy evaluation fails.
Operational rule: exactly one of three values:

mode: enforce
Apply the policy. Senders that fail policy validation (certificate mismatch, MX not in the mx: list, STARTTLS not offered) refuse delivery — the connection is aborted and mail bounces back to the sender with a TLS-related error. This is the production state every deployed MTA-STS policy should ultimately reach.
mode: testing
Parse and evaluate the policy but DO NOT bounce on failure. Senders deliver mail as if MTA-STS were not present, but emit TLS-RPT reports describing what they would have done in enforce mode. This is the canonical pre-enforce burn-in stage — run for 7-14 days, review the TLS-RPT reports for any failures, fix issues, then promote to enforce.
mode: none
Suspend the policy. Senders treat the recipient as having no MTA-STS deployed. The operational use case is policy rollback: when an enforce-mode policy is causing unexpected mail bouncing, the operator switches to mode: none (and bumps the id token in DNS) to disable enforcement immediately while the underlying issue is diagnosed and fixed.

Common pitfall: writing mode: Enforce (capital E) or mode: ENFORCE (uppercase) or mode: strict (unrecognised value). The value is case-sensitive lowercase; any other case or any other value causes the policy to be rejected and senders fall back to opportunistic TLS.

; Correct — lowercase mode value:
version: STSv1
mode: enforce
mx: mail.example.com
max_age: 604800

; Wrong — capital E:
version: STSv1
mode: Enforce
mx: mail.example.com
max_age: 604800

; Wrong — unrecognised value:
version: STSv1
mode: strict
mx: mail.example.com
max_age: 604800

6. The mx: tag (RFC 8461 §3.2, MANDATORY, may repeat)

Tag: mx: <pattern>
Default: none. At least one mx: line is structurally MANDATORY; multiple lines are allowed and aggregate as a list.
Why: names the acceptable MX hostnames for the recipient domain. Senders enforce that the actual MX they connect to (and the SAN on the presented TLS certificate) matches one of the patterns in this list. Without an mx: match, the policy is violated and (in enforce mode) the delivery is refused.
Operational rule: each mx: line names exactly one pattern. Two kinds of patterns:

Exact hostname
mx: mail.example.com — matches only the literal hostname mail.example.com. The conservative pattern; explicit additions required for any new MX.
Single-label wildcard
mx: *.example.com — matches one DNS label of depth-one expansion. Matches mail.example.com, mx1.example.com, mx2.example.com. Does NOT match sub.mail.example.com (two labels) or example.com (zero labels, the apex). The wildcard appears ONLY as the leftmost label; *.example.* and mail.*.example.com are malformed.

Common pitfall: using a multi-label wildcard like **.example.com or *.*.example.com (both malformed), or expecting *.example.com to match the apex example.com (it does not), or expecting it to match backup.mail.example.com (it does not — that is two labels of expansion).

; Correct — exact hostnames enumerated:
version: STSv1
mode: enforce
mx: mail.example.com
mx: backup-mail.example.com
max_age: 604800

; Correct — single-label wildcard:
version: STSv1
mode: enforce
mx: *.example.com
max_age: 604800

; Wrong — multi-label wildcard:
mx: *.*.example.com

; Wrong — middle-label wildcard:
mx: mail.*.example.com

7. The max_age: tag (RFC 8461 §3.2, MANDATORY)

Tag: max_age: <seconds>
Default: none. The max_age key is structurally MANDATORY.
Why: the policy cache lifetime in seconds. Senders cache the fetched policy locally for max_age seconds and do not re-fetch the URL until the cache expires (or the id token in DNS changes).
Operational rule: positive integer, maximum 31557600 seconds (one year, calculated as 365.25 × 86400) per RFC 8461. Recommended values:

  • Initial deployment / testing mode: max_age: 86400 (24 hours). Short window so policy changes propagate quickly during the iteration cycle.
  • Stable enforce mode: max_age: 604800 (one week) or max_age: 2592000 (30 days). Balances re-fetch traffic against policy-change-propagation latency.
  • Production with rare changes: max_age: 31536000 (one year minus a leap-second margin). For the most-stable deployments.

Common pitfall: setting max_age: 31557600 exactly (the upper limit) without realising that a policy rollback then takes up to one year to fully propagate to all senders. The id token bump in DNS forces an early re-fetch, but only for senders that actually look at DNS — conformant implementations check DNS on every delivery, but defensive operators assume the worst-case full-cache-window propagation. Use a moderate max_age and rely on the id token for fast change- propagation when needed.

; Recommended for testing mode (24 hours):
max_age: 86400

; Recommended for stable enforce mode (one week):
max_age: 604800

; Recommended for stable enforce mode (30 days):
max_age: 2592000

; Maximum allowed (one year):
max_age: 31536000

; Wrong — over the RFC maximum:
max_age: 100000000

; Wrong — non-integer:
max_age: 7d

8. No BOM, no comments, no extra whitespace

No UTF-8 BOM: RFC 8461 §3.2 specifies the policy file as plain ASCII / UTF-8 without a byte-order mark. The three-byte BOM sequence EF BB BF at file head appears in files saved by Windows Notepad with "UTF-8 with BOM" encoding, some IDE defaults, and a handful of editors. Conformant senders parse the file from byte zero — the BOM is not stripped, so the version line starts with the BOM bytes followed by version: STSv1, which fails the version-key match.

How to verify no BOM:

; Inspect the first three bytes:
$ curl -s https://mta-sts.example.com/.well-known/mta-sts.txt | head -c 3 | xxd

; Correct output (the "v" of "version" plus next two letters):
00000000: 7665 72                                  ver

; Wrong output (BOM bytes EF BB BF then "v"):
00000000: efbb bf                                  ...

How to fix: re-save the file as "UTF-8 without BOM". Notepad++: Encoding menu → Encode in UTF-8 without BOM. vim: :set nobomb then :w. PowerShell: Out-File -Encoding utf8 (not the default utf8BOM).

No comments: RFC 8461 §3.2 defines only key-value lines. Any line not matching the key: value shape — including # comment, // note, blank lines in the middle of the file, or any line with a different separator — is treated as malformed and the entire policy is rejected.

Whitespace handling: the separator between key and value is : (colon plus space). Senders trim surrounding whitespace from values during parsing, but exotic whitespace (tabs, non-breaking spaces, em-spaces) is risky — stick to the canonical : separator and standard ASCII spaces.

Line endings: CRLF (0D 0A) and LF (0A) line endings are both accepted by conformant senders. CR-only line endings (old-style Mac OS 9) are NOT defined by RFC 8461 and may be treated as malformed by strict parsers — use LF or CRLF.

9. The _mta-sts DNS record and the id= token coupling

The pair: MTA-STS uses two coordinated DNS / HTTPS resources:

  1. The _mta-sts.<domain> TXT DNS record carrying v=STSv1; id=<token>. This is the change-detection signal that tells senders WHEN to re-fetch.
  2. The https://mta-sts.<domain>/.well-known/mta-sts.txt HTTPS endpoint carrying the actual policy file content documented on THIS page.

Operational rule: the id tag in the DNS record is an opaque token (often a date-derived string like 20260522T120000Z) that operators bump every time they change the policy file. Senders cache the policy keyed on the (policy-domain, id) tuple; when a new id appears in DNS, senders treat the cached policy as stale and re-fetch the URL within their next message-delivery attempt.

Why a coupled two-resource design: the DNS lookup is cheap and discoverable on every SMTP delivery; the HTTPS fetch is expensive and should happen only when the policy changes. The token-in-DNS, content-in-HTTPS split lets DNSSEC protect the change-detection token even when the policy file is served from a separate web-PKI trust chain.

Operational rule for changes: whenever you edit the policy file, you MUST ALSO bump the id token in DNS. Editing the policy file alone results in senders continuing to use the cached (now-stale) policy for up to max_age seconds. Bumping the id alone (without editing the policy file) forces senders to re-fetch but they see the unchanged content — wasted fetches.

; The DNS record (change-detection):
_mta-sts.example.com.  IN  TXT  "v=STSv1; id=20260522T120000Z"

; The HTTPS endpoint (policy content):
;   https://mta-sts.example.com/.well-known/mta-sts.txt
; serving:
version: STSv1
mode: enforce
mx: mail.example.com
max_age: 604800

; To roll a policy change:
;   1. Edit the .well-known/mta-sts.txt file.
;   2. Update the id= token in DNS to a new value.
;   3. Wait for senders to re-fetch within their max_age window.

10. Canonical examples

Example 1 — minimal enforce policy (single MX, one-week cache):

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

Example 2 — testing-mode burn-in (24h cache for fast iteration):

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

Example 3 — multi-MX enforce policy with explicit enumeration:

version: STSv1
mode: enforce
mx: mx1.example.com
mx: mx2.example.com
mx: mx3.example.com
mx: mx4.example.com
mx: mx5.example.com
max_age: 2592000

Example 4 — single-label wildcard (shorter file, broader coverage):

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

Example 5 — emergency rollback (mode: none suspends enforcement, short cache so senders re-fetch quickly when you restore enforce):

version: STSv1
mode: none
mx: mail.example.com
max_age: 86400

Example 6 — combined deployment: the DNS record + policy file for a complete MTA-STS deployment (with TLS-RPT for monitoring):

; DNS records:
_mta-sts.example.com.  IN  TXT  "v=STSv1; id=20260522T120000Z"
_smtp._tls.example.com. IN  TXT  "v=TLSRPTv1; rua=mailto:tlsrpt@example.com"

; HTTPS policy file at https://mta-sts.example.com/.well-known/mta-sts.txt:
version: STSv1
mode: enforce
mx: mail.example.com
max_age: 604800

11. Common operator mistakes

Ten canonical syntactic drift classes, enumerated as a punch list. Every one results in the policy being silently rejected by conformant senders, with TLS-RPT sts-policy-invalid or sts-policy-fetch-error as the only operator-visible signal.

  1. UTF-8 BOM at file head — Windows Notepad default. Causes version: line to fail matching. Save as "UTF-8 without BOM".
  2. Comments in the file — RFC 8461 §3.2 has no comment syntax. A line starting with # or any other non-key character causes the entire policy to be rejected.
  3. HTTP instead of HTTPS — the policy URL MUST use HTTPS. A 301 redirect from HTTP to HTTPS is treated as a fetch error by some senders.
  4. Self-signed or private-CA TLS certificate on mta-sts.<domain> — only publicly-trusted CA certificates validate.
  5. Wrong Content-Type — serving the file as text/html or application/octet-stream instead of text/plain causes rejection.
  6. Capital-letter mode value — mode: Enforce or mode: ENFORCE fails the case-sensitive token match. Use lowercase enforce, testing, or none.
  7. Lowercase version value — version: stsv1 fails the case-sensitive match. Use STSv1.
  8. Multi-label mx: wildcard — *.*.example.com or **.example.com are malformed. Wildcards match exactly one label and appear only as the leftmost label.
  9. Missing id bump after policy edit — the policy file is edited but the _mta-sts DNS record still carries the old id. Senders continue to use the cached old policy for up to max_age seconds. Bump the id in DNS on every policy edit.
  10. Excessive max_age — setting max_age: 31557600 (one year) makes policy rollback take up to one year to fully propagate in the worst case. Use a moderate value (604800 to 2592000) and rely on id-bump for fast change-propagation.

The Domain Security Checker surfaces drift classes 1, 2, 3, 4, 5, 6, 7, and 8 via live HTTPS GET against the policy URL. TLS-RPT (per help_tls_rpt_explained.php) surfaces drift class 9 via sts-policy-fetch-error or sts-policy-invalid reports from senders.

12. Frequently asked questions

What URL does the MTA-STS policy file live at?
The policy file is served from https://mta-sts.<domain>/.well-known/mta-sts.txt. The hostname is the well-known mta-sts subdomain prefixed to the recipient domain. The scheme MUST be HTTPS — plaintext HTTP is rejected by conformant senders because the policy controls TLS-enforcement decisions and must itself be transported under TLS. The TLS certificate at mta-sts.<domain> MUST validate against the public CA root store; self-signed and private-PKI certificates are rejected. The Content-Type MUST be text/plain (or text/plain; charset=utf-8). The HTTP status MUST be 200; redirects are followed but conformant senders SHOULD limit redirect chains. RFC 8461 section 3.3 specifies that senders MAY use HEAD before GET to discover the policy size before fetching the full body.
What are the four canonical keys in an MTA-STS policy file?
RFC 8461 section 3.2 defines four canonical keys: (1) version — the policy version identifier, currently the only valid value is STSv1, case-sensitive, MUST appear first. (2) mode — the deployment-stage tag, one of enforce / testing / none, case-sensitive. (3) mx — one or more lines naming acceptable MX hostnames; each entry is either an exact hostname (mail.example.com) or a single-label wildcard (*.example.com). (4) max_age — the policy cache lifetime in seconds; minimum 86400 (24h) recommended, maximum 31557600 (one year) per RFC 8461. All four keys are MANDATORY; a policy file missing any of them is rejected as malformed by conformant senders. The order of keys is loosely defined — version MUST be first but the other three may appear in any order — though the canonical convention is version then mode then mx (one line per MX) then max_age.
How does mx: wildcard matching work?
RFC 8461 section 3.2 specifies mx: wildcards as single-label only. An entry like *.example.com matches one DNS label of depth-one expansion — so it matches mail.example.com and mx1.example.com but does NOT match sub.mail.example.com (two-label) or example.com (zero-label, the bare apex). The wildcard always appears as the leftmost label; you cannot use *.example.* or mail.*.example.com — those are malformed and the policy is rejected. The single-label restriction prevents over-broad policies that would accidentally accept any subdomain. The canonical operational use case: an organisation that names MX hosts in a predictable pattern (mx1.example.com, mx2.example.com, ..., mx5.example.com) can use a single *.example.com entry instead of enumerating each one — saving boilerplate at the cost of accepting any future *.example.com host that gets added to MX records. The more-conservative posture: enumerate each MX hostname explicitly (mx1.example.com on its own line, mx2.example.com on its own line, etc.) so any future MX additions require an explicit policy update.
What is the maximum max_age value and what value should I use?
RFC 8461 section 3.2 specifies max_age as a positive integer of seconds with a hard maximum of 31557600 (one year, calculated as 365.25 × 86400). The minimum is not formally specified by the RFC, but values under 86400 (24 hours) produce excessive policy re-fetch traffic and are generally avoided. Operational recommendations: (1) Initial deployment / testing mode — start with max_age: 86400 (24 hours) so policy changes propagate quickly during the deployment iteration. (2) Stable enforce mode — bump to max_age: 604800 (one week) or max_age: 2592000 (30 days) once the policy is stable. (3) Production with rare changes — max_age: 31536000 (one year minus a leap-second margin) for the most-stable deployments. The trade-off: longer max_age reduces fetch traffic at the cost of slower policy-change propagation. Senders cache the policy locally for max_age seconds keyed on the policy-id-from-DNS, so a policy change requires both editing the file AND bumping the id= tag in the _mta-sts TXT record for senders to re-fetch within the max_age window.
Are comments allowed in the MTA-STS policy file?
No. RFC 8461 section 3.2 defines the policy file grammar as a list of key-value pairs ONLY; any line not matching the `key: value` shape is treated as malformed. There is no comment syntax — no #, no //, no ;, nothing. A line like `# my note` will cause the entire policy to be rejected by conformant senders, which will then fall back to opportunistic TLS (the equivalent of `mode: none`). This is structurally different from many DNS records or configuration files that permit comments. The operational implication: any annotation about the policy file MUST live OUTSIDE the file — in a configuration-management commit message, in a wiki page, in an inline comment in your infrastructure-as-code that generates the file, but never IN the served file itself. If you need a self-documenting policy, expand the mx: enumeration to include obvious-named hostnames (mail-primary.example.com vs mail-backup.example.com) rather than relying on comments.
Why must the policy file not start with a UTF-8 BOM?
RFC 8461 section 3.2 specifies the policy file as plain ASCII / UTF-8 text without any byte-order mark. A UTF-8 BOM (the three-byte sequence EF BB BF at file head) appears in files saved by Windows Notepad, some IDE configurations, and a handful of editors that default to "UTF-8 with BOM". The BOM bytes appear at the start of the file BEFORE the `version: STSv1` line. Conformant senders parse the file from byte zero — the BOM is not stripped before parsing, so the version line starts with the BOM bytes followed by `version: STSv1`, which fails the version-key match (the parser is looking for `version` as the first bytes of the first line, not three BOM bytes followed by `version`). The policy is rejected and senders fall back to opportunistic TLS. Symptom: the file looks correct in a text editor (the editor renders the BOM as invisible), but `dig` / `curl` / `xxd` shows the BOM bytes. Fix: re-save the file as "UTF-8 without BOM" (Notepad++: Encoding menu > Encode in UTF-8 without BOM; vim: `:set nobomb` then `:w`; Windows PowerShell: use `Out-File -Encoding utf8` not the default `utf8BOM`).
What is the relationship between the policy file and the _mta-sts DNS record?
They work in coordinated tandem. The _mta-sts.<domain> TXT record (e.g., `v=STSv1; id=20260522T120000Z`) is the change-detection signal that tells senders WHEN to re-fetch the policy file. The policy file at https://mta-sts.<domain>/.well-known/mta-sts.txt is the actual policy content senders evaluate. The id= tag in the DNS record is an opaque token (often a date-derived string) that operators bump every time they change the policy file. Senders cache the policy keyed on the (policy-domain, id) tuple; when a new id appears in DNS, senders treat the cached policy as stale and re-fetch the URL within their next message-delivery attempt. The two-record architecture exists because: (a) the DNS lookup is cheap and discoverable on every SMTP delivery, while the HTTPS fetch is expensive and should happen only when the policy changes, (b) the DNS-and-HTTPS-coupling lets DNSSEC protect the change-detection token even when the policy file itself is served from a separate web-PKI trust chain, (c) the id token semantically separates "this policy is current" from "this policy file is fetchable" — an unreachable HTTPS endpoint with a valid id token causes senders to use the previously-cached policy rather than failing closed.
Can I serve the policy file from a CDN or load balancer?
Yes, and this is operationally common. The /.well-known/mta-sts.txt URL can be served from any HTTPS endpoint that meets RFC 8461 requirements: publicly-trusted CA certificate validating for the mta-sts.<domain> hostname, Content-Type text/plain, 200 status. Serving from a CDN (Cloudflare, Fastly, CloudFront, Akamai) is recommended for high-availability domains because the policy URL is a high-cardinality fetch target — every sending MTA in the world that decides to deliver mail to your domain will fetch this URL once per cache window. Operational tips: (1) Set CDN cache-control TTL to match or slightly exceed max_age so the origin sees fetch traffic only on cache misses. (2) Ensure the CDN edge serves the file with Content-Type text/plain — some CDNs default to text/html for .txt files served from /.well-known. (3) Monitor the CDN-edge 5xx rate; a single 5xx burst can cause global policy re-fetch failures, which conformant senders treat as a transient error and proceed with the cached policy (no immediate outage). (4) Coordinate cache-bust on the CDN with the id= bump in DNS to ensure senders re-fetching after the id change actually see the new policy and not a stale CDN cache.
What happens if my policy file has a syntactically valid but invalid mode value?
The policy is rejected as malformed. RFC 8461 section 3.2 specifies mode as taking exactly one of three values: enforce, testing, none. Any other value (mode: strict, mode: ENFORCE, mode: deploy, mode: production) fails the mode-token match and causes conformant senders to reject the entire policy. The sender then falls back to opportunistic TLS for the recipient domain — equivalent to no MTA-STS deployment at all. The most common operator error is case-sensitivity: writing `mode: Enforce` (capital E) instead of `mode: enforce` (lowercase). RFC 8461 does not formally specify case-sensitivity, but the canonical convention is lowercase and most sender implementations enforce it strictly. The defensive posture: always use lowercase for mode values, and verify your policy parses correctly by fetching it through a TLS-RPT-aware sender and observing whether `policy-type=sts` appears in the resulting reports (a successfully-parsed policy produces sts-policy-type reports; a rejected policy produces no-policy-found reports because the sender treats the recipient as having no MTA-STS).
How do I test my policy file before going to enforce mode?
Deploy `mode: testing` first — RFC 8461 section 4.2 specifies testing mode as the canonical pre-enforce burn-in stage. In testing mode, senders parse the policy, evaluate it against actual SMTP sessions, and EMIT TLS-RPT REPORTS describing what they would have done in enforce mode — without actually enforcing anything. So a misconfigured policy that would cause silent mail bouncing in enforce mode produces TLS-RPT failures in testing mode that the recipient operator can review before bumping to enforce. The recommended burn-in workflow: (1) Publish the policy file with mode: testing and a short max_age (86400 seconds). (2) Publish the _mta-sts DNS record with the policy id token. (3) Deploy TLS-RPT (RFC 8460) at _smtp._tls.<domain> with rua= pointing at your reporting destination. (4) Wait 7-14 days for sender population to fetch the policy and emit reports. (5) Review TLS-RPT reports for sts-policy-fetch-error, sts-policy-invalid, sts-webpki-invalid, certificate-host-mismatch — any non-zero counts indicate a policy issue that would cause bouncing in enforce mode. (6) Fix any issues, bump the policy id in DNS so senders re-fetch, wait another cycle. (7) Once TLS-RPT reports are clean for at least 7 days, swap mode: testing to mode: enforce and bump the policy id one more time. See help_tls_rpt_report_format.php for the TLS-RPT result-type catalogue.

Validate your MTA-STS policy now

DMARC Analyzer ingests TLS-RPT reports about your MTA-STS policy and surfaces per-result-type breakdowns in the dashboard. Cross-check the DNS + HTTPS state of your policy with the Domain Security Checker before deploying.

Sign up — Free plan Audit my MTA-STS policy