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 uppercaseSTSv1, MUST appear first.mode: enforce|testing|none— mandatory deployment-stage tag, case-sensitive lowercase.enforceapplies the policy,testingproduces TLS-RPT reports without bouncing,nonedisables.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 anid=<token>tag. Every policy change MUST be accompanied by a newidtoken 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 hostnamemail.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. Matchesmail.example.com,mx1.example.com,mx2.example.com. Does NOT matchsub.mail.example.com(two labels) orexample.com(zero labels, the apex). The wildcard appears ONLY as the leftmost label;*.example.*andmail.*.example.comare 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) ormax_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:
- The
_mta-sts.<domain>TXT DNS record carryingv=STSv1; id=<token>. This is the change-detection signal that tells senders WHEN to re-fetch. - The
https://mta-sts.<domain>/.well-known/mta-sts.txtHTTPS 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.
- UTF-8 BOM at file head —
Windows Notepad default. Causes
version:line to fail matching. Save as "UTF-8 without BOM". - 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. - 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.
- Self-signed or private-CA TLS
certificate on
mta-sts.<domain>— only publicly-trusted CA certificates validate. - Wrong Content-Type —
serving the file as
text/htmlorapplication/octet-streaminstead oftext/plaincauses rejection. - Capital-letter
modevalue —mode: Enforceormode: ENFORCEfails the case-sensitive token match. Use lowercaseenforce,testing, ornone. - Lowercase
versionvalue —version: stsv1fails the case-sensitive match. UseSTSv1. - Multi-label
mx:wildcard —*.*.example.comor**.example.comare malformed. Wildcards match exactly one label and appear only as the leftmost label. - Missing
idbump after policy edit — the policy file is edited but the_mta-stsDNS record still carries the oldid. Senders continue to use the cached old policy for up tomax_ageseconds. Bump theidin DNS on every policy edit. - Excessive
max_age— settingmax_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 onid-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.