RFC 9989 removes the pct tag entirely (now marked historic in the IANA registry) and replaces it with a binary t (testing) tag:
t=y -- testing/monitoring mode; analogous to the old pct=0. Receivers should apply monitoring but not enforce policy.
t=n (or absent) -- full enforcement; analogous to the old pct=100.
The rationale (RFC 9989 Appendix A.6) is that intermediate pct values were implemented inconsistently across receivers and rarely applied correctly in practice. RFC 9989 explicitly notes that only pct=0 and pct=100 were ever reliably implemented.
Backward compatibility with pct=
OpenDMARC will still parse pct= when present in a DMARC record, but:
pct=0 -> treated as t=y (monitoring only, no enforcement)
pct=100 -> treated as t=n (full enforcement; no behavior change)
pct=1-99 -> discarded; treat as full enforcement (t=n)
t= takes precedence over pct= when both are present
This means domain owners running pct=1-99 with p=reject or p=quarantine will see full enforcement from OpenDMARC once this is implemented. Per RFC 9989, those values were already being applied inconsistently across the ecosystem.
Required changes
Filter (opendmarc.c):
- Remove the
random() % 100 < pct sampling blocks in the DMARC_POLICY_REJECT and DMARC_POLICY_QUARANTINE cases
- Parse and honor the
t tag: when t=y, skip enforcement regardless of p= value (treat as monitoring-only)
- Map
pct=0 -> t=y during the transition
Library (libopendmarc):
opendmarc_policy_query_dmarc() needs to parse the t= tag from the DNS record and expose it via a new accessor (or extend an existing one)
- Continue parsing
pct= but normalize it to the two meaningful values (0 and 100) before exposing it; discard 1-99
Aggregate reports (opendmarc-reports.in):
- RFC 9990
<policy_published> block: replace <pct> element with whatever RFC 9990 specifies for the t tag
Related
- RFC 9989 Appendix A.6 (removal rationale)
- Supersedes the old
pct handling throughout the codebase
RFC 9989 removes the
pcttag entirely (now marked historic in the IANA registry) and replaces it with a binaryt(testing) tag:t=y-- testing/monitoring mode; analogous to the oldpct=0. Receivers should apply monitoring but not enforce policy.t=n(or absent) -- full enforcement; analogous to the oldpct=100.The rationale (RFC 9989 Appendix A.6) is that intermediate
pctvalues were implemented inconsistently across receivers and rarely applied correctly in practice. RFC 9989 explicitly notes that onlypct=0andpct=100were ever reliably implemented.Backward compatibility with
pct=OpenDMARC will still parse
pct=when present in a DMARC record, but:pct=0-> treated ast=y(monitoring only, no enforcement)pct=100-> treated ast=n(full enforcement; no behavior change)pct=1-99-> discarded; treat as full enforcement (t=n)t=takes precedence overpct=when both are presentThis means domain owners running
pct=1-99withp=rejectorp=quarantinewill see full enforcement from OpenDMARC once this is implemented. Per RFC 9989, those values were already being applied inconsistently across the ecosystem.Required changes
Filter (
opendmarc.c):random() % 100 < pctsampling blocks in theDMARC_POLICY_REJECTandDMARC_POLICY_QUARANTINEcasesttag: whent=y, skip enforcement regardless ofp=value (treat as monitoring-only)pct=0->t=yduring the transitionLibrary (
libopendmarc):opendmarc_policy_query_dmarc()needs to parse thet=tag from the DNS record and expose it via a new accessor (or extend an existing one)pct=but normalize it to the two meaningful values (0 and 100) before exposing it; discard 1-99Aggregate reports (
opendmarc-reports.in):<policy_published>block: replace<pct>element with whatever RFC 9990 specifies for thettagRelated
pcthandling throughout the codebase