RFC 9989, 9990, and 9991 are published: what changed for DMARC
DMARC is now an IETF Standards Track protocol. RFC 9989, 9990, and 9991 replace RFC 7489 from 2015. Records still start with v=DMARC1, so most production deployments need nothing immediate. But the spec has real changes: new tags, the Public Suffix List is out, the p=reject guidance has flipped, and aggregate reports gained fields. Here is what changed and what to do about it.
Standards basis: Analysis based on RFC 9989 for DMARC policy records, RFC 9990 for aggregate reports, and RFC 9991 for failure reports. RFC 7489 is discussed as historical context.
The IETF spent about five and a half years revising the DMARC standard. The first working-group draft appeared in November 2020, and the effort shipped in May 2026 as three Standards Track RFCs:
- RFC 9989, the core DMARC protocol. Obsoletes RFC 7489.
- RFC 9990, aggregate reporting.
- RFC 9991, failure reporting.
This is the first substantive update to the core DMARC specification since the original RFC 7489 in 2015. It is also the first time DMARC has formal IETF endorsement: RFC 7489 was published via the Independent Submission Stream as Informational, not by the IETF itself. RFC 9989 is Proposed Standard.
There is no “DMARC2”. Records still begin with v=DMARC1 and every deployment in the wild keeps working. The spec underneath has changed in ways that are worth reading carefully though.
Last updated: June 9, 2026.
Disclosure: DMARCTrust is our product. This is a working postmaster’s read of the new RFCs: what changed, what matters in production, and what you should actually do about it.
What happened to RFC 7489?
RFC 7489 was the original DMARC specification, published in March 2015 as an Independent Submission with Informational status. It was never on the IETF Standards Track. RFC 9989 changes that: it is a Proposed Standard and obsoletes RFC 7489 along with RFC 9091. If you are looking for “the DMARC RFC” to cite, 9989 is now the current one. Nothing you published under 7489 breaks. The v=DMARC1 syntax is preserved, and DMARC1 is still the only valid version value.
What actually changed
Pulled from Appendix C of RFC 9989:
- Standards Track, not Informational. DMARC is now a Proposed Standard with formal IETF endorsement.
- The single original document is now three: base protocol (9989), aggregate reports (9990), failure reports (9991). They can evolve independently without re-opening the others.
- The Public Suffix List is replaced by a DNS Tree Walk. Organizational Domain discovery no longer depends on the publicsuffix.org list.
- Three new tags:
np(non-existent subdomain policy),psd(public suffix domain flag),t(test mode flag). - Three tags removed:
pct,rf,ri. - The
p=rejectguidance is reversed. The new spec discouragesp=rejectfor domains that host general user mailboxes, and tells receivers to treatp=rejectasp=quarantineby default. - There is now a conformance section. RFC 9989 §8 lists explicit MUSTs for full participation by domain owners and mail receivers.
- The aggregate report schema gained
discovery_method,np, andtestingelements inpolicy_published. DKIMselectoris now required when reporting a DKIM signature.
v=DMARC1 is preserved. No existing record needs to change today.
p=reject is no longer the recommended end state
For a decade, the standard advice has been: monitor at p=none, move to p=quarantine, finish at p=reject. RFC 9989 pushes back on the last step. The language is direct.
From §7.4:
It is therefore critical that Mail Receivers MUST NOT reject incoming messages solely on the basis of a “p=reject” policy by the sending domain. Mail Receivers must use the DMARC policy as part of their disposition decision, along with other knowledge and analysis. […] In the absence of other knowledge and analysis, Mail Receivers MUST treat such failing mail as if the policy were “p=quarantine” rather than “p=reject”.
And from Appendix C.6:
In particular, this document makes explicit that domains for general-purpose email SHOULD NOT deploy a DMARC policy of “p=reject”.
The reasoning is the indirect-mail-flow problem that has dogged DMARC since day one. When a user posts to a mailing list, the list resends the message with the original From: header intact. SPF breaks (the list’s IP is not in the user’s SPF record), and DKIM often breaks too (the list rewrites the subject, adds a footer, or otherwise modifies the body). Under p=reject, the list’s subscribers stop receiving the message, the list software interprets the bounces as a dead address, and it auto-unsubscribes them.
The working group spent a decade waiting for ARC (RFC 8617) or some other technical fix to gain widespread adoption. None has. RFC 9989 just accepts that fact and adjusts the guidance.
In practice:
- If your domain is a transactional sender that never has humans posting from it (bank notifications, e-commerce receipts, dedicated marketing subdomains),
p=rejectmay still be appropriate. - If your domain hosts employee or customer mailboxes, where a human might subscribe to an external mailing list,
p=quarantineis the spec’s recommended end state. Not a stop on the way to reject. - For subdomains that never send mail, use
sp=rejecton the parent. That part of the playbook does not change.
This is the change most worth a second look at your current setup. We updated our p=none to enforcement migration playbook to reflect the new guidance.
What p=none, p=quarantine, and p=reject mean
Under RFC 9989 the p tag is the Domain Owner Assessment Policy (§4.7): the owner’s stated assessment of mail that uses the domain but fails DMARC, not a command the receiver must obey. The receiver weighs that assessment as one input in its own disposition decision and decides the actual handling itself (§5.3.6, §5.4). The same three values apply to sp (subdomains) and np (non-existent subdomains).
| Value | What the domain owner is asserting (§4.7) | How the receiver treats it (§5.4, §7.4) |
|---|---|---|
p=none |
The domain owner offers no expression of preference. | No handling change is requested. The receiver applies its normal local policy, and aggregate reports still flow so the owner can monitor. |
p=quarantine |
The domain owner considers such mail suspicious. It may be valid, but the failure creates a significant concern. | The receiver weighs this as one input. Final handling, such as routing to spam or adding scrutiny, is at its discretion. |
p=reject |
The domain owner considers every such failure a clear sign that the use of the domain is not valid. | The receiver MUST NOT reject solely on this basis. It uses the policy as part of its disposition decision, and absent other analysis treats the mail as p=quarantine. |
RFC 9989 drops RFC 7489’s implied “reject means reject”. The published p value is the owner’s assessment; the disposition is whatever the receiver decides to do with the message (§7.4).
The new tags: np, psd, t
np: policy for non-existent subdomains
np sets the assessment policy for subdomains that do not exist in DNS. It mirrors p and sp syntactically:
v=DMARC1; p=quarantine; sp=quarantine; np=reject; rua=mailto:[email protected]
The case for it: an attacker spoofs support.yourcompany.com even though you have never published that subdomain. np=reject makes spoofing those names visibly harder for the attacker without affecting any subdomain you do operate. If you do not publish np, the policy from sp (or p as fallback) applies.
np was first introduced in RFC 9091 as part of an experimental PSD profile. RFC 9989 imports it and obsoletes RFC 9091.
psd: Public Suffix Domain flag
psd is a flag, not a policy, and its two meaningful values have different audiences. psd=y is for Public Suffix Operators (country-code registries, gTLD operators, or organizations like fTLD that manage .bank): it marks the domain as a public suffix, which is what lets DMARC apply at a TLD-like level without an offline registry. psd=n is the value a regular owner might reach for: it declares that the domain is not a public suffix but is the Organizational Domain for itself and its subdomains, which is how a large or decentralized organization can pin an intermediate node like division.example.com as its own Organizational Domain. The default, psd=u, means undetermined, so the receiver settles it with the Tree Walk. Almost no end-user domain owner sets any of this. See RFC 9989 §4.7 for the tag and §4.10 for the rationale.
t: test mode flag
t replaces the most useful part of pct. It is a binary flag, not a percentage:
-
t=n(default): apply the declared policy as-is. -
t=y: request that receivers apply the next-lower policy instead. Ifp=reject; t=y, receivers treat the message as ifp=quarantine. Ifp=quarantine; t=y, receivers treat it asp=none.
t=y does not affect aggregate or failure report generation, so you keep visibility while you test. It is much coarser than pct=25 → pct=50 → pct=100, but RFC 9989 Appendix A.6 explains the trade-off: the working group found that the pct tag was usually not applied accurately unless the value was 0 or 100. Implementations differed widely on what they did with intermediate values, so the gradual percentage rollout was less deterministic than it looked.
A reasonable use of t=y is a one- or two-week dry run before flipping to a stricter policy.
The removed tags: pct, rf, ri
-
pct, the percentage tag. Removed. Uset=yfor staged rollouts instead. -
rf, failure report format. Removed. Only one format was ever used. -
ri, requested aggregate report interval. Removed. RFC 9989 §8 instead recommends receivers send reports daily.
If you publish records with any of these tags today, nothing breaks. Receivers continue to parse the records and silently ignore the unknown tags (RFC 9989 §4.7 still says “unknown tags MUST be ignored”). For new records or playbooks, leave them out.
DMARC alignment, briefly
Identifier Alignment is the core DMARC check: the domain in the From: header (the Author Domain, formally RFC5322.From) must match a domain that SPF or DKIM has authenticated. SPF alignment compares the From domain against the MAIL FROM domain that SPF validated. DKIM alignment compares it against the d= signing domain of a valid DKIM signature. Each runs in relaxed mode, where the two domains only need to share an Organizational Domain (RFC 9989 §3.2.10.1), or strict mode, where they must be identical. You set the mode per mechanism with aspf and adkim. A message passes DMARC when at least one of SPF or DKIM both passes and aligns with the From domain.
For the full mechanics, including why SPF alignment so often breaks on forwarded mail, see the alignment guide. If your reports show authentication passing but DMARC still failing, that is an alignment problem. Start with troubleshooting alignment issues.
Public Suffix List out, DNS Tree Walk in
What changed
RFC 7489 described an offline lookup against a “Public Suffix List”, typically the one maintained at publicsuffix.org, to determine where a domain’s “organizational” boundary was. The Organizational Domain is what DMARC uses to find the policy record when no record exists at the Author Domain itself, and to determine relaxed alignment between two domains.
The PSL approach had a few operational issues. Different receivers used different PSL snapshots, so they could disagree about the Organizational Domain of the same address. There was no formal update cadence. And it depended on an out-of-band registry that the protocol itself never specified.
RFC 9989 §4.10 replaces this with a “DNS Tree Walk”: the receiver queries _dmarc.<author-domain>, then _dmarc.<parent>, and so on up the tree. The walk is capped at eight queries (the longest names in observed data have seven labels). When a record carries psd=n, the walker stops and treats that name as the Organizational Domain. When a record carries psd=y, the Organizational Domain is one level below. Otherwise, the policy record at the shortest qualifying name wins.
Why it matters
For a flat domain like example.com → mail.example.com, the Tree Walk produces exactly the same Organizational Domain that the PSL did. No behavior change.
For multi-tier domains, the change is narrower than it first looks. Policy for a message is still read from the record at the message’s own domain first (§4.10.1). The Organizational Domain used for alignment is then chosen by the walk (§4.10.2): with plain records at both _dmarc.email.example.com and _dmarc.example.com and no psd tag, the name with the fewest labels wins, so the Organizational Domain is example.com. That is the same answer the PSL gave, and RFC 9989’s own example spells it out. To make an intermediate node like email.example.com its own Organizational Domain, you publish psd=n on it. That psd=n declaration is what the Tree Walk genuinely adds here, not a free-for-all where every subtree runs its own independent policy.
In practice, the more common effect will be the inverse. Receivers that previously fell back to a single parent policy will now see whatever subdomain records exist, including misconfigured ones nobody knew were live. If you start seeing failures in your aggregate reports attributed to a subdomain policy domain such as bounce.something.example.com, that is the Tree Walk surfacing a record you forgot about.
Reports now tell you which method was used
RFC 9990 §3.1.1.5 adds a discovery_method element to policy_published with the value psl or treewalk. This lets you see, per report, whether the sending receiver has migrated to the new algorithm. Expect a long mixed period. Large receivers will move at different paces.
Aggregate report format changes (RFC 9990)
The XML schema is the same shape, with a handful of additions and one tightening. Drawn from Appendix C of RFC 9990:
- A new
discovery_methodelement inpolicy_published, with the valuepslortreewalk(above). - A new
npelement inpolicy_published, carrying the non-existent subdomain policy. - A new
testingelement inpolicy_published, carrying the value of thettag. - DKIM
selectoris now required when a DKIM signature is reported. Under RFC 7489 it was implicitly optional, and many receivers left it out. That made it hard to correlate failures with key rotations. - Namespaced extensions are allowed in exactly two places: a top-level
<extension>element right after<policy_published>, and a wildcard at the end of each record after<auth_results>. Nothing extra is permitted inside<policy_published>itself. - The report identifier has more structure to reduce duplicate-detection ambiguity.
The new elements are additive. The DKIM selector requirement is the only tightening. Existing parsers that ignore unknown elements keep working, but fixtures and validators should include selectors when they report DKIM signatures under RFC 9990.
Failure report changes (RFC 9991)
Minor. The main additions formalize the ARF (Abuse Reporting Format) header fields a DMARC failure report must include:
-
Identity-Alignmentis now REQUIRED and carries the comma-separated list of mechanisms (dkim,spf) that failed to authenticate an aligned identifier. -
DKIM-Domain,DKIM-Identity,DKIM-Selectorare REQUIRED when reporting a DKIM failure of an aligned identifier. -
SPF-DNSis REQUIRED for SPF failure of an aligned identifier. - A
dmarcAuthentication Failure Type is defined for the Auth-Failure field, used when failure reports cover DMARC alignment failures rather than the underlying SPF or DKIM checks.
RFC 9991 §7 also adds a privacy section, acknowledging that failure reports can leak PII from message bodies and headers, and recommending that report generators redact local-parts where possible. Many large providers already cap or disable failure reporting for this reason.
The conformance section
RFC 9989 §8 lists explicit MUSTs for “Full DMARC Participation”. This is the first time the spec has stated, in normative language, what counts as compliance.
A Domain Owner that fully participates:
- MUST send mail that produces an SPF-Authenticated Identifier aligned with the Author Domain.
- MUST send mail with a DKIM Signing Domain that produces a DKIM-Authenticated Identifier aligned with the Author Domain.
- MUST set up a mailbox to receive aggregate reports, and collect and analyze them.
- MUST publish a DMARC Policy Record for the Author Domain, and for the Organizational Domain if it differs.
- MUST NOT rely solely on SPF for a DMARC pass if the policy is
p=reject.
A Mail Receiver that fully participates:
- MUST check for a DMARC Policy Record on inbound mail.
- MUST determine if Authenticated Identifiers exist and preserve the results for reporting.
- MUST conduct Identifier Alignment checks when applicable.
- MUST support the
mailto:URI for reports. - SHOULD send aggregate reports at least daily.
- MUST NOT reject messages solely on the basis of
p=reject.
The list is short enough to use as an audit checklist. Most established DMARC monitoring deployments already satisfy the domain-owner side.
What you should actually do
A short, honest list:
- Revisit your current
p=rejectdecision. If you host user mailboxes, the spec now recommendsp=quarantineas the end state. This is the one change worth taking time over. - Stop using
pct=in new records or playbooks. If you currently publishp=reject; pct=100, drop thepct. If you are mid-rollout withpct=25, that policy is non-deterministic in practice; uset=yinstead for the test-mode period. - Consider
np=rejectif you do not intentionally send from subdomains that have no DNS records of their own. It only applies to names that do not exist in DNS, so real subdomain senders are unaffected. - Watch for
discovery_method=treewalkappearing in your aggregate reports. It tells you which receivers have migrated. - Audit subdomain DMARC records if you have a complex DNS tree. The Tree Walk can expose intermediate records that RFC 7489-era PSL discovery skipped, especially when an intermediate node is marked with
psd=nor when no higher parent policy exists. - Nothing else is urgent.
v=DMARC1keeps working. The transition is gradual and backward-compatible.
DMARCTrust ingests aggregate reports exactly as before. Our DMARC checker and dashboard already surface the data you need to make these decisions. We have updated the DMARC tags guide, the alignment guide, and the migration playbook to match the new spec.
For the authoritative text, read the RFCs directly. They are clearer than most IETF specifications: