DMARC pct Tag: Why Partial Enforcement Leaves a Gap
A record that shows full enforcement while only applying to part of your mail
Warning
The pct tag is set below 100, which means enforcement only applies to a share of failing mail. The rest falls through to the next weaker policy. The record looks enforced but leaves a gap.
The pct tag controls what share of failing mail your policy is applied to. It takes a whole number from 0 to 100, and it defaults to 100 when the tag is absent. A record like v=DMARC1; p=reject; pct=50 reads as full enforcement at a glance, but reject is only applied to a randomly selected half of the messages that fail.
The part people get wrong. Under RFC 7489, receivers do not simply deliver the non-selected mail. They apply the next weaker policy. So p=reject; pct=50 means roughly half of failing mail is rejected and the other half is quarantined. p=quarantine; pct=50 means half is quarantined and half is treated as none, which is delivered normally.
That still leaves a real gap, and it is one that hides well. The record shows reject, dashboards show reject, and the domain looks fully enforced, while in practice a share of spoofed mail is being filed into spam instead of refused. With quarantine, the weaker half is delivered outright.
Why the tag exists
pct was designed as a rollout dial. The intent was that you could move from none to reject gradually, raising the percentage in steps and watching your aggregate reports at each stage to catch a legitimate stream you had missed before it affected all of your mail. Used that way it is a reasonable safety mechanism.
The problem in practice is that the dial gets left where it was set. A value chosen for a two week rollout in a change ticket that was closed long ago becomes the permanent state of the domain, and nobody revisits it because the policy still reads as reject.
What receivers actually do
Selection is random per message, so a given sender is not consistently enforced or consistently exempt. In your aggregate reports this shows up as the same source appearing under different dispositions across the same reporting period, which is a useful signal that a partial percentage is in play.
Implementation is also less consistent than the rest of DMARC. Some receivers apply pct exactly as specified, some ignore it and enforce the stated policy on everything, and behavior around pct=0 in particular varies. That variability is another reason not to depend on a partial value as a long term posture: you do not really know what any individual receiver is doing with it.
One thing pct never affects is reporting. Aggregate reports cover all mail regardless of the percentage, so lowering it does not reduce your visibility.
The severity depends on the parent policy
Partial pct under p=quarantine (Warning)
With p=quarantine, the non-selected mail falls to the next weaker policy: none. It is delivered to the inbox. A share of failing mail reaches recipients even though the record shows an enforcing policy.
v=DMARC1; p=quarantine; pct=5050% quarantined, 50% delivered to inbox.
Partial pct under p=reject (Info)
With p=reject, the non-selected mail falls to quarantine. Nothing failing is delivered to the inbox. The domain keeps meaningful protection, but the posture is one step below what the record advertises.
v=DMARC1; p=reject; pct=5050% rejected, 50% quarantined.
pct=0 under p=quarantine (Warning)
With pct set to 0, no failing mail receives the quarantine policy. All of it falls to none and is delivered, so the record behaves like monitoring while showing an enforcing policy and the domain is spoofable.
v=DMARC1; p=quarantine; pct=0How to fix it
Raise pct to 100 so the policy covers all failing mail, or remove the tag entirely, since 100 is the default when it is absent. Removing it is usually the cleaner outcome because it leaves nothing to drift back.
# Full enforcement - remove pct entirely (defaults to 100)
v=DMARC1; p=reject; rua=mailto:dmarc@example.com
# Or set it explicitly
v=DMARC1; p=reject; pct=100; rua=mailto:dmarc@example.comIf you are mid rollout and using the tag deliberately, set a date to finish. Check your aggregate reports for sources that still fail alignment, resolve those, then go to 100 rather than leaving the domain at an intermediate value.
pct=0 is a special case. The policy applies to nothing, so the record behaves like monitoring while displaying an enforcing policy. If you want monitoring, publish p=none and be honest about it in the record, which is also what any auditor reading your DNS will expect.
Valid range for pct
pct must be a whole number between 0 and 100. Anything outside that range, or a non-numeric value, is a syntax error. Some receivers treat the whole record as invalid rather than ignoring just that tag, so the outcome is inconsistent across receivers.
If you see this finding, correct the value to a whole number between 0 and 100, or remove the tag to use the default of 100.
The tag is being removed
The DMARCbis revision of the standard removes pct entirely. The working group's view was that partial enforcement added complexity and inconsistent receiver behavior for limited benefit, and that the rollout path is better served by moving through none, quarantine, and reject in sequence. The tag remains valid and widely deployed under RFC 7489 today, so it is still worth detecting. But if you are planning a rollout now, plan it as a sequence of policy changes rather than a percentage ramp, and you will not have to unwind anything later.
Related checks
References: RFC 7489 (DMARC) · DMARCbis draft
