· Updated
pct= is Deprecated in RFC 9989: Migrate Your DMARC Record
RFC 9989, published May 19, 2026, removes pct= from the DMARC policy model. Receivers that implement RFC 9989 don't process it, and the RFC itself notes that pct= values other than 0 and 100 were usually not applied accurately. If your record still contains pct=50 or pct=25, remove it and use the staged policy approach instead: start at p=none to collect reports, move to p=quarantine once alignment is clean, then advance to p=reject when you're above 98% sustained alignment.
What pct= was supposed to do
The pct= tag was designed as a rollout safety valve. It told receiving mail servers to apply your DMARC policy (p=quarantine or p=reject) to only X% of messages that failed alignment. The remaining messages dropped one level: under p=quarantine they were delivered normally, and under p=reject they were quarantined instead. All of them were still reported.
The idea: with pct=25, you could move to p=reject and have only a quarter of failing mail rejected, with the rest quarantined. A way to test enforcement without the full blast radius.
In practice, receivers implemented it inconsistently from the beginning. RFC 9989 Appendix A.6 says pct= "was usually not accurately applied, unless the value specified was either 0 or 100", and that the inaccuracies "varied widely from one implementation to another." The "training wheel" often wasn't engaged the way you thought it was. With pct=10 in your record, you couldn't count on only 10% of failing mail being affected.
Why RFC 9989 removed it
RFC 9989 codified what operational experience showed: only pct=0 and pct=100 behaved predictably. Rather than keep a tag with two reliable values, the IETF working group removed it and replaced the useful one, pct=0, with a new t tag (covered below). This isn't a breaking change in practice for most senders. The behavior at receivers was already inconsistent. The spec is catching up to reality. In our September 2026 scan, only 7 of 1,786 domains with a DMARC record (0.4%) paired p=quarantine or p=reject with pct below 100, the setup whose meaning changes under RFC 9989 (see the DMARCbis readiness data).
Backward compatibility means a pct= tag in your existing record won't cause errors. Receivers that implement RFC 9989 will simply ignore it, while receivers still on RFC 7489 may keep applying it. Either way, you can no longer rely on it to limit enforcement scope during a rollout.
The right rollout approach in 2026
The staged policy approach replaces pct=-based sampling, and it's actually more reliable, because you're testing real enforcement on your full mail stream rather than a sample that receivers may or may not honor.
Step 1: Start at p=none
v=DMARC1; p=none; rua=mailto:your-dmarcdrift-address
This collects aggregate reports without enforcing anything. Run here for 2-4 weeks. You're looking for your alignment rate across all sending sources. Any service that sends email on behalf of your domain should show up in the reports.
Step 2: Move to p=quarantine when alignment is clean
v=DMARC1; p=quarantine; rua=mailto:your-dmarcdrift-address
Once you're seeing 95%+ alignment sustained over 2-4 weeks, move to p=quarantine. Failing messages go to spam instead of inbox. This is enough to make spoofing significantly harder, and it surfaces any legitimate senders you missed at p=none. Watch for a week or two. If nothing breaks, you're ready for the last step.
Step 3: Move to p=reject
v=DMARC1; p=reject; rua=mailto:your-dmarcdrift-address
You're asking receivers to refuse failing messages. This is the finish line. Sustained 98%+ alignment over 30 days before making this move. If people at your domain post to mailing lists, read DMARCbis and mailing lists first: RFC 9989 §7.4 says such domains SHOULD NOT publish p=reject.
This approach is safer than pct= sampling because you see problems across your full mail stream at each stage, not a sampled fraction. If a sending source is misconfigured, it shows up clearly in reports rather than being masked by sampling noise.
Before and after:
# Before (legacy pct= approach)
v=DMARC1; p=reject; pct=25; rua=mailto:...
# After (if you're not ready for full reject yet)
v=DMARC1; p=quarantine; rua=mailto:...
# After (if alignment is already clean)
v=DMARC1; p=reject; rua=mailto:...
The replacement: t=y (testing mode)
RFC 9989 didn't drop the idea of a test step. It added a new tag, t, to replace the part of pct= that worked. Appendix A.6 explains that pct=0 had picked up a useful side effect: some intermediaries and mailbox providers treated it as a signal to rewrite the From header, so comparing your reports before and after showed how much of your mail ran through intermediaries that don't rewrite. t=y keeps that signal under a proper name.
t=y asks receivers to apply your policy one level softer (RFC 9989 §4.7):
| Published policy | With t=y, receivers apply |
|---|---|
p=reject |
quarantine |
p=quarantine |
none |
p=none |
none (t has no effect) |
It doesn't change reporting. You get the same aggregate reports either way. The DMARC t=y testing mode guide covers how it differs from pct in more detail.
During the transition, publish t=y and pct=0 together. RFC 9989 calls t=y "analogous" to pct=0, but it doesn't tell receivers to treat one as the other. Receivers still on RFC 7489 ignore t, because it's an unknown tag to them. RFC 9989 receivers ignore pct. With both tags published, both kinds of receiver land one level below your published policy: an RFC 7489 receiver through its pct rules (RFC 7489 §6.6.4), and an RFC 9989 receiver through t=y. Don't publish pct=0 on its own anymore. An RFC 9989 receiver ignores it and applies your full policy.
A staged rollout with test steps looks like this:
# 1. Monitor
v=DMARC1; p=none; rua=mailto:your-dmarcdrift-address
# 2. Test quarantine (failing mail is handled as none)
v=DMARC1; p=quarantine; t=y; pct=0; rua=mailto:your-dmarcdrift-address
# 3. Enforce quarantine
v=DMARC1; p=quarantine; rua=mailto:your-dmarcdrift-address
# 4. Test reject (failing mail is handled as quarantine)
v=DMARC1; p=reject; t=y; pct=0; rua=mailto:your-dmarcdrift-address
# 5. Enforce reject
v=DMARC1; p=reject; rua=mailto:your-dmarcdrift-address
The test steps are optional. Their value is what Appendix A.6 describes: intermediaries that act on the test signal may rewrite the From header, and comparing your aggregate reports before and after a test step shows how much of your mail depends on that. When you move to full enforcement, remove t=y and pct=0 together.
How to know where you stand
Your aggregate reports tell you your alignment rate. For each report, you have message counts and policy_evaluated results showing whether DKIM and SPF passed alignment for each sending source.
If your alignment has been above 98% for 30 or more days across all senders, you're ready for p=reject. If it's 90-97%, p=quarantine is the right intermediate step, and you should be investigating which sources are pulling the rate down before tightening further.
DMARCdrift's domain health checklist now flags pct= as a deprecated tag. If your domain's health check shows a pct= warning, it means your record includes the tag and you should migrate using the approach above. The dashboard alignment chart shows you whether you're in the safe zone for a policy move.
The 5-minute fix
First, check what you have:
dig TXT _dmarc.yourdomain.com
Then decide:
-
No
pct=tag, orpct=100: Nothing urgent. Your record is already behaving as a full-enforcement record. You may want to removepct=100for cleanliness, but it has no functional effect. -
pct=50or similar withp=reject: You were counting on sampling to limit blast radius. That's gone. Either removepct=and stay atp=rejectif your alignment rate supports it (98%+), or drop back top=quarantinewithoutpct=until you're confident. -
pct=0withp=quarantineorp=reject: You were usingpct=0as a test mode. An RFC 9989 receiver ignores it and applies your full policy. Addt=ynext to it, as described in thet=ysection above, or remove both if you're ready to enforce. -
pct=25withp=quarantine: You were being extra cautious. Now just usep=quarantinedirectly. The staged policy approach gives you the same safety with more visibility.
In all cases, keep your rua= address in place. The reports are what let you know when something breaks.
What about np=?
RFC 9989 brings np=, the non-existent subdomain policy tag, into the core spec. It was first defined in RFC 9091. Once you're at p=reject on your main domain, adding np=reject closes a gap: it explicitly tells receivers what to do with mail claiming to be from subdomains that don't exist (like phishing@nonexistent.yourdomain.com). Without np=, receivers fall back to your sp= value, or to p= if you haven't set sp=. That's usually fine, but explicit is better. The np= tag explainer walks through how np=, sp=, and p= take precedence.
If you're curious about the full set of changes in the 2026 DMARC RFCs, the RFC 9989 post covers the np= tag and what else changed in the spec.
Updated September 28, 2026: corrected the RFC 9989 publication date, sourced the accuracy claims about pct= to RFC 9989 Appendix A.6, corrected the np= fallback, and added the t=y testing-mode section.
