Skip to content
praneeth.me
Back to log

DMARC just had its first real rewrite since 2015 — here's what DMARCbis actually changes

Updated:

In May 2026 the IETF published RFC 9989, RFC 9990 and RFC 9991 — three documents that, between them, replace RFC 7489, the spec DMARC has run on since 2015. Unofficially this is being called “DMARCbis,” and if you manage DNS for a domain that sends mail — which, if you’re reading this, you probably do — it’s worth twenty minutes to understand what actually changed, because some of it changes how you should be writing your DMARC record today.

I went looking into this while tightening up the DNS hygiene on this site’s own domain (praneeth.me runs on Cloudflare, DNSSEC on, the whole checklist), and ended up down a rabbit hole that’s more interesting than “here’s a new DNS tag.” So: what changed, why it changed, and one gotcha that most of the coverage I’ve read doesn’t mention.

Why this update matters more than it sounds

The detail that actually matters isn’t in the technical changes — it’s in the paperwork. RFC 7489 was published via the IETF’s Independent Submission Stream, tagged “Informational.” That’s a real distinction: it means DMARC, despite running a meaningful chunk of the world’s anti-phishing infrastructure for a decade, was never formally endorsed by the IETF. It was a widely-adopted spec that happened to live outside the standards process.

RFC 9989 (the core protocol), RFC 9990 (aggregate reporting) and RFC 9991 (failure reporting) fix that. All three are Proposed Standards on the IETF Standards Track, the product of years of work by the IETF’s DMARC Working Group that picked up pace once the related ARC protocol was published (as Experimental RFC 8617) in 2019. It’s a bureaucratic upgrade more than a technical one — but it’s also the excuse the working group used to clean up a decade of accumulated cruft in the spec, which is where the actual changes come in.

What changed

Organisational domain discovery got a real algorithm. The old spec leaned on the Public Suffix List — a community-maintained, not-quite-authoritative text file — to work out which domain “owns” a DMARC policy. DMARCbis replaces that with a DNS Tree Walk: receivers query DNS hierarchically upward from the From: address looking for a _dmarc record, with built-in limits so a malicious domain can’t force a receiver into an unbounded chain of lookups — however many labels the From: domain has, the walk never issues more than eight queries. Less reliance on an external list nobody formally governs.

A handful of tags are gone, a few are new.

TagStatusWhat happened to it
pctRemovedPercentage-based partial enforcement is gone entirely
rf, riRemovedReport format and interval are no longer sender-configurable
tNewA boolean testing flag, replacing what pct used to do
psdNewLets a domain declare itself a public suffix domain (RFC 9091’s experiment, standardised)
npNewSets policy specifically for non-existent subdomains

SPF’s role got clarified, not changed. RFC 7489 already keyed DMARC’s SPF check to the MAIL FROM: identity, but its wording left room for receivers to also count a standalone HELO/EHLO SPF pass toward DMARC. The new text closes that reading: DMARC “relies solely on SPF validation of the MAIL FROM identity.” Bounce handling is unaffected — an empty MAIL FROM is still evaluated as postmaster@ the HELO domain, exactly as RFC 7208 has always defined it.

Reporting got simpler and more private. Aggregate reports were already XML in practice; RFC 9990 makes that a MUST. The removed rf tag — which let senders request alternate failure-report formats — is gone, and with ri gone too, receivers now control their own reporting schedule rather than honouring a sender’s requested interval. RFC 9990 is also explicit that aggregate reports contain no identifying information about individual users — no personal mail addresses, no individual IPs, no message content — which matters given how many of them get forwarded to third-party analysis tools.

The tag actually worth your attention: np=

Of everything in DMARCbis, np= is the one with real security value — though the gap it fills is subtler than most of the coverage suggests.

Under the old spec, your sp= tag (or p=, if you never set one) applied to every subdomain — ones you’d created and ones you hadn’t alike. So billing.yourdomain.com and payroll.yourdomain.com were never actually policy-free: mail forged from them fell back to your organisational domain’s record like anything else. The real gap was granularity. A domain mid-rollout at p=none or sp=quarantine — the standard cautious path — was forced to be exactly that lenient about subdomains that don’t exist and can never send legitimate mail. Phishing infrastructure lived happily in that leniency window.

DMARCbis splits the two cases: sp= now covers only subdomains that exist, and np= covers the ones that don’t (if you don’t set it, non-existent subdomains fall back to sp, then p):

_dmarc.example.com.  TXT  "v=DMARC1; p=quarantine; sp=quarantine; np=reject; rua=mailto:[email protected]"

That’s the record where np= earns its keep: your real subdomains stay at quarantine while you finish rolling out, but mail claiming to come from a subdomain that doesn’t exist gets rejected outright, today. (Already at sp=reject? Then np=reject is harmless but redundant — the fallback gets you the same outcome.)

The gotcha: np= and DNSSEC don’t always agree on what “non-existent” means

Here’s the part I haven’t seen covered much, and it’s directly relevant if you’re on Cloudflare, Route 53, Azure DNS or NS1 — which between them is a large share of everyone.

np= is defined against a DNS receiver detecting an NXDOMAIN response — the standard “this name doesn’t exist” answer. But a lot of DNSSEC-signed zones don’t actually return NXDOMAIN anymore. RFC 9824 defines “compact denial of existence,” a technique where the authoritative nameserver instead returns NOERROR, with a single NSEC record carrying an NXNAME bit to signal non-existence — cheaper to compute and smaller to transmit than the multi-record proof a traditional NXDOMAIN response needs under DNSSEC.

The problem: a DMARC implementation that strictly checks for NXDOMAIN (as the spec assumes) will see NOERROR from a compact-denial provider and conclude the subdomain exists — silently skipping your np= policy for every non-existent subdomain, on every domain hosted there.

And this isn’t a niche configuration. A December 2024 measurement of the top million domains found compact denial in use on roughly 44% of the ~73,000 domains with valid DNSSEC — it’s the default at Cloudflare (since 2016), NS1 (2019), AWS Route 53 (2020) and Azure DNS (2025). If your DNS is DNSSEC-signed on any of those, np=reject may not actually be doing anything until receivers catch up.

The fix has to happen on the receiving side: implementations need to also recognise the NXNAME bit on NSEC/NSEC3 records as proof of non-existence, not just a literal NXDOMAIN response code. Some already do. Plenty don’t yet. There’s no sender-side workaround — you can’t opt out of your DNS provider’s denial-of-existence method — so the honest answer right now is: treat np=reject as “probably helping,” not “definitely enforced,” until you’ve confirmed your receiving mail providers handle compact denial correctly. Worth re-checking every few months rather than setting and forgetting.

What I’d actually do with this

None of this requires touching a record you’re happy with today — existing records keep parsing fine, since unknown tags have always been ignored by design and no surviving tag changed meaning. (The one caveat is pct, above.) But if you’re setting up DMARC fresh, or tightening an existing policy, it’s worth writing the record against the new spec rather than a decade-old one that just got formally retired.


Share this post:

Next Post
This site isn't vibe coded — here's what I'd call it instead