RFC 9989 DMARCbis: What Actually Changed for Your Domain
RFC 9989 (DMARCbis) published on May 20, 2026 and your existing v=DMARC1 record still works without modification. The three new RFCs (9989 core, 9990 aggregate reporting, 9991 failure reports) replace the original RFC 7489 from 2015, but backward compatibility was the explicit design goal. Two changes are worth acting on now: pct= is deprecated and may no longer be honored by receivers, and the new np= tag lets you set an explicit policy for non-existent subdomains.
What didn't change
The core authentication mechanism is identical. v=DMARC1 remains valid. p=none, p=quarantine, and p=reject work exactly as before. rua= and ruf= addresses are unchanged. sp= for subdomain policy is still there.
Receiving servers will continue to process your existing records without modification. The IETF was deliberate about backward compatibility. This revision codifies a decade of implementation experience without breaking deployed infrastructure.
If you're at p=none still working toward full alignment, nothing about DMARCbis changes your immediate priorities. Focus on alignment first.
What changed: pct= is deprecated
pct= allowed you to apply your DMARC policy to only a percentage of failing messages. pct=50 meant: enforce policy on 50% of failures, let the other 50% through. It was designed as a gradual rollout mechanism.
RFC 9989 removes pct= from the spec.
The reason is that pct= was inconsistently implemented across receiving mail servers: some honored it, others ignored it entirely, and many interpreted it differently. As a sampling mechanism for staged enforcement, it was unreliable in practice, you couldn't actually depend on it to behave the way you expected.
The right approach to staged rollout is to use policy levels themselves: start at p=none, move to p=quarantine once your alignment rate is solid, then move to p=reject. Each step is unambiguous and universally implemented.
Receivers are still required to parse pct= for backward compatibility, but they are no longer required to honor it. That's the key shift: if you have pct=50 or pct=25 in your record, you cannot assume it's doing what you think it's doing. Some receivers may now enforce your full policy on 100% of failing messages regardless.
If you have pct= at anything below 100 and you're at p=quarantine or p=reject, remove it. Figure out what policy level you actually want and set it explicitly. If you're not ready for full enforcement, drop back to a lower policy level rather than relying on sampling.
What changed: np= is new
RFC 9989 introduces np=, the non-existent subdomain policy tag. This is the most substantive new feature in DMARCbis.
Previously, sp= covered all subdomains that didn't have their own DMARC record. "All subdomains" turned out to be ambiguous: does it include subdomains that don't exist in DNS at all?
np= resolves this. It applies specifically to subdomains that return NXDOMAIN, meaning they don't exist. sp= continues to apply to subdomains that exist in DNS but don't have their own DMARC policy.
Without an explicit np= tag, non-existent subdomains fall back to the organizational domain's p= policy. For domains at p=reject, that means NXDOMAIN subdomains already get reject behavior, but the intent isn't explicit in the record.
Adding np=reject makes your intent unambiguous and ensures consistent handling across receivers that implement DMARCbis. For most domains at p=reject, the update is straightforward:
v=DMARC1; p=reject; sp=reject; np=reject; rua=mailto:...
This matters if you have an attacker trying to spoof definitely-not-real.yourdomain.com, a subdomain that doesn't exist. With np=reject explicit, there's no ambiguity about what receivers should do.
What changed: the t= testing tag
RFC 9989 formalizes t=y as an official testing tag. A record with t=y signals that the domain owner is testing their DMARC setup and receivers should not enforce policy.
This is conceptually similar to p=none but allows a domain to publish an enforcement-level policy (like p=reject) while indicating it shouldn't be acted on yet. The idea is that p=reject with t=y communicates "this is our intended posture" more clearly than p=none, which could mean "we're testing" or "we've given up."
In practice, t=y is niche. Receiver support will lag the spec. If you need to test, p=none still works and is universally understood.
What changed under the hood: DNS Tree Walk
RFC 9989 replaces the Public Suffix List (PSL) with a DNS Tree Walk algorithm for determining organizational domain boundaries. This affects how receivers decide which DMARC record applies to a given From domain.
The PSL approach required receivers to maintain an up-to-date copy of a third-party list. The DNS Tree Walk replaces this with a deterministic algorithm that walks up the DNS hierarchy directly. It's more accurate for edge cases and removes a maintenance dependency.
As a domain owner, this is invisible to you. Receiving mail servers handle the lookup. It only becomes relevant if you have complex multi-level subdomain structures and were relying on PSL-specific behavior, which is rare.
What to do now
If you have pct= below 100 at p=quarantine or p=reject: Remove it. Decide what policy level you actually want and set it explicitly. pct=50 is not a safe middle ground anymore.
If you're at p=reject: Consider adding np=reject to make your non-existent subdomain policy explicit. It's a one-line change that closes an ambiguity.
If you're at p=none: No changes needed from DMARCbis. Your immediate job is still getting alignment above 98% before you tighten policy.
If you have sp= set: Review whether you want np= to match. If sp=reject, you almost certainly want np=reject too.
DMARCdrift's domain health checker now flags pct= values below 100 as a configuration warning and surfaces missing np= for domains at p=reject. If you want to see how your current record stacks up against the DMARCbis spec, run a check on your domain.
The short version: DMARCbis doesn't break anything. But if you've been using pct= as a deployment safety net, that net has a hole in it now. Time to remove it and own the policy level you're actually at.
