RFC 9989, 9990, 9991 (May 2026) explained
Back to The Rejected Folder
The IETF working group spent more than seven years on what most of us were calling DMARCbis. In May 2026 it landed 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 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: May 23, 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 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=reject guidance is reversed. The new spec discourages p=reject for domains that host general user mailboxes, and tells receivers to treat p=reject as p=quarantine by 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, and testing elements in policy_published. DKIM selector is 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=reject may still be appropriate.
If your domain hosts employee or customer mailboxes, where a human might subscribe to an external mailing list, p=quarantine is the spec’s recommended end state. Not a stop on the way to reject.
For subdomains that never send mail, use sp=reject on 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.
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...