After years of drafts, working group debates, and the word “DMARCbis” being thrown around in every email conversation, the new DMARC specification is now a proposed standard at the IETF. The work was led by Todd Herr and John Levine, and it ships as three separate RFCs:
- RFC 9989 – DMARC protocol (policy, lookup, DNS tree walk, alignment)
- RFC 9990 – Aggregate reporting
- RFC 9991 – Failure reporting
The first thing I want to get out of the way: we don’t call this DMARCbis anymore. It’s just DMARC. RFC 9989 obsoletes RFC 7489, the version number didn’t change, and there’s no parallel protocol. If you were waiting for a clean break, this is it.
Why splitting it into three RFCs actually matters
This sounds like a paperwork detail, but it’s one of the more useful things the working group did. Under RFC 7489, every clarification, every fix, every tweak to reporting meant touching the entire DMARC spec. Now the protocol, aggregate reporting, and failure reporting are independent documents. If aggregate reporting needs an update in two years, only RFC 9990 gets revised. The core protocol can stay stable while the bits around it evolve.
For us as practitioners, this means future updates will be smaller, easier to track, and less likely to ship surprises buried in a 100-page revision.
What actually changed in the protocol
I’m going to focus on the things that change daily work, not every editorial cleanup in the document.
DNS Tree Walk replaces the PSL dependency
Under the old DMARC, finding the right policy for a subdomain meant relying on the Public Suffix List to identify the organizational domain. That worked, but the PSL was never built for DMARC, it’s maintained by volunteers, and the lookup logic was opinionated in ways that caused real-world inconsistencies between receivers.
RFC 9989 introduces the DNS Tree Walk. The receiver walks up the DNS tree from the message’s domain, looking for a DMARC record at each level, with two new tags to control the walk:
- psd= declares that the domain is a Public Suffix Domain. Operators of PSDs (think co.uk, gov.au, similar) can publish a DMARC record with psd=y and the tree walk will recognize it.
- np= defines the policy for non-existent subdomains, separately from sp= which only applies to subdomains that actually exist.
Christophe Dary made a sharp point about np= on LinkedIn that I want to echo: don’t jump straight to np=reject. Misspelled hostnames are everywhere, and some of them are legitimate services nobody documented properly. Before you tighten that screw, watch your reports, find the typos, fix them, then enforce. Otherwise you’re going to silently kill a service somebody actually needs.
His other operational tips are worth repeating:
- Set a long TTL (24h) on your DMARC records once your policy is stable. A DNS timeout during the tree walk can cause receivers to fall back to the organizational domain’s policy instead of yours.
- At your organizational domain apex, add psd=n to block any PSD-level DMARC record above you from interfering with your policy.
Three tags are gone: pct, rf, ri
The pct tag is gone. Appendix A.6 of RFC 9989 walks through the reasoning, and Todd Herr’s take on this is one I agree with: pct was ambiguously defined in RFC 7489 from day one. The original wording said it was the “percentage of messages from the Domain Owner’s mail stream to which the DMARC policy is to be applied” which has two problems. First, DMARC failures by definition mostly come from third parties spoofing the domain, not from the Domain Owner’s own stream. Second, if you set p=quarantine and pct=10, and you see 100 messages of which 50 fail DMARC, do you quarantine 5 (10% of failures) or 10 (10% of total)? Different receivers picked different answers. The tag couldn’t survive that.
In practice, pct had also become a crutch. Domain owners would sit at p=quarantine; pct=10 forever, treating it as a forever-state instead of a ramp. Removing the tag forces a cleaner mental model: you’re either monitoring (p=none), partially enforcing (p=quarantine), or fully enforcing (p=reject).
The rf (report format) and ri (reporting interval) tags are also gone. rf only ever had one valid value in the wild, and ri was widely ignored by receivers anyway. The spec just acknowledges reality here.
The p=reject conversation, accurately
This is the part I want to be careful with, because there’s a misreading of RFC 9989 that’s been circulating and Todd Herr has been correcting it directly.
The misreading goes something like this: “receivers are now required to treat p=reject as p=quarantine for indirect mail flows.” That’s not quite what the RFC says.
What section 7.4 actually says is that receivers MUST NOT reject messages solely on the basis of a p=reject policy. The policy is one input, alongside other knowledge and analysis. Only in the absence of other analysis does the RFC say to treat failing mail as if the policy were p=quarantine.
Todd’s framing of this on LinkedIn is the right one: the p= flag was, is, and always will be a request from the Domain Owner. The receiver has agency. There’s no protocol police. RFC 9989 has strong language about the interoperability damage that blindly honoring p=reject can cause, and it includes guidance on mitigation, but it doesn’t, and can’t, command receivers to do anything specific.
What this means for us in operations is unchanged in spirit but clearer in writing: p=reject is your signal of intent, but how individual receivers act on it varies, and the RFC now codifies that variability.
What this means for rollout
This is a proposed standard. Receivers are going to adopt it on their own schedules. Gmail, Yahoo, and Microsoft will get there, but until they publicly update their published specs, we’re in a transitional zone where any given message might be evaluated against RFC 7489 logic or RFC 9989 logic.
Christophe’s point about catastrophic side effects from variable processing is real. If you’re managing DMARC for a domain right now, the practical move is:
- Stop using pct. If you have it in your record, plan to remove it. New receivers will ignore it, old ones will keep honoring it, and you don’t want your behavior diverging across the receiver population.
- Decide on your np= policy carefully. Start with np=quarantine or even np=none while you watch your reports for typos and ghost services.
- If you operate a PSD, get psd=y published with a sensible policy (typically p=reject; sp=none).
- Read your aggregate reports. The structure is unchanged, but the new tree walk means you may see policy applications at levels you weren’t expecting.
The bigger picture
I’ve been doing this long enough to remember when DMARC adoption itself was the conversation. We’re past that. The conversation now is about getting DMARC right, in a world where receivers, senders, mailing lists, and forwarders all interact in ways the original spec underspecified.
RFC 9989 doesn’t solve every one of those problems. It can’t. But it clears up real ambiguities, removes tags that were causing more confusion than value, and gives us a cleaner foundation to build on. The split into three RFCs means the next round of improvements won’t require another five-year cycle.
Congratulations to Todd Herr and John Levine, and to everyone in the IETF working group who got this across the line. It was worth the wait.





