RFC 7489 was replaced by three documents published in May 2026: RFC 9989 (core protocol), RFC 9990 (aggregate reporting), and RFC 9991 (failure reporting). None of those documents include a single consolidated list of behavioral changes, so this issue collects them in one place along with a column showing where OpenDMARC currently stands.
References: rfc9989.txt, rfc9990.txt, rfc9991.txt (copies in repo root).
RFC 9989: Core protocol changes
Tags removed from the DMARC Policy Record
| Removed tag |
What it did |
Current OpenDMARC state |
pct (sec. 4.7, Appendix A.6, Appendix C.5.2) |
Apply policy to a percentage (0-100) of failing messages |
Fully implemented: DMARC_POLICY_T.pct, parsed in opendmarc_policy.c, applied in opendmarc.c. Code needs to learn to ignore pct from incoming records rather than act on it. |
rf (sec. C.5.2) |
Requested failure report format |
Parsed into DMARC_POLICY_T.rf; field can be removed from the struct and parser. |
ri (sec. C.5.2) |
Requested interval between aggregate reports |
Parsed into DMARC_POLICY_T.ri; field can be removed from the struct and parser. |
Receivers that encounter these tags MUST ignore them (per RFC 9989 sec. 4.8: "unknown tags MUST be ignored"). Since pct was a known tag in RFC 7489, receivers that still act on it will diverge from compliant behavior.
Tags added to the DMARC Policy Record
| New tag |
Meaning |
Current OpenDMARC state |
t (sec. 4.7, Appendix A.6, Appendix C.5.1) |
Testing mode; replaces pct=0/100. t=y means apply one level below the stated policy (reject -> quarantine; quarantine -> none); t=n (default) means apply as stated. Has no effect on p=none. |
Not implemented. No field in DMARC_POLICY_T, no parser support, no enforcement logic. The pct-based random sampling in opendmarc.c needs to be replaced with this deterministic one-level downgrade. |
np (sec. 4.7, Appendix C.5.1) |
Non-existent subdomain policy; distinct from sp (existing subdomains). Falls back to sp then p if absent. |
Not implemented. No field in DMARC_POLICY_T, no parser support. The policy selection logic needs a third branch after the existing p/sp check. |
psd (sec. 4.7, Appendix C.5.1) |
Flags whether the publishing domain is a Public Suffix Domain (y/n/u). Used by the DNS Tree Walk to identify Organizational Domain boundaries. |
Not implemented. Required for correct Tree Walk behavior (see below). |
Policy discovery: DNS Tree Walk replaces PSL
RFC 7489 required a Public Suffix List (PSL) to determine the Organizational Domain. RFC 9989 replaces this with a "DNS Tree Walk" (sec. 4.10, sec. 4.10.1): starting from _dmarc.<author-domain>, query progressively shorter labels until a valid DMARC Policy Record is found or the walk is exhausted. The psd tag in a discovered record signals whether to stop.
| Aspect |
RFC 7489 behavior |
RFC 9989 behavior |
OpenDMARC state |
| Org domain discovery (sec. 4.10) |
PSL lookup (configurable via PublicSuffixList) |
DNS Tree Walk from author domain upward |
PSL path fixed and working. When no PSL is configured, a label-walk fallback now queries successive parent labels until a DMARC record is found, stopping before bare TLDs (#378, issue #54). This is structurally similar to the RFC 9989 Tree Walk but uses a dot-count heuristic as the stopping condition rather than psd= tags. The full RFC 9989 Tree Walk (stopping on psd=n/psd=y, querying through TLDs, 8-label jump for long domains) is not yet implemented and will replace this fallback before release. |
| PSD DMARC (sec. 5.2) |
Not supported |
Supported via psd=y in the record and the Tree Walk |
Not implemented. Depends on psd tag support and full Tree Walk. |
| Interoperability during transition (sec. C.3) |
N/A |
RFC 9989 notes that strict alignment + per-domain records avoids interop issues |
No code changes needed for this note, but worth documenting for operators. |
Note: in practice, the RFC 9989 Tree Walk's psd= stopping condition provides no benefit until PSO operators publish psd=y in their DMARC records -- adoption of which is expected to be slow. Until then, the label-walk fallback and the RFC 9989 Tree Walk produce equivalent results for the overwhelming majority of real mail.
The Tree Walk involves multiple DNS queries and has different failure modes than a PSL lookup. Implementing it requires changes in libopendmarc/opendmarc_policy.c (the DNS query and record fetch logic) and possibly a new config option to select PSL vs. Tree Walk behavior during a transition period.
Authentication-Results header
RFC 9989 sec. 9.1 formally registers policy.dmarc as an AR result property. This was implemented in OpenDMARC in PR #365 and is already present.
RFC 9990: Aggregate reporting changes
RFC 9990 sec. 6.1 formally registers urn:ietf:params:xml:ns:dmarc-2.0 as the XML namespace for DMARC aggregate reports. RFC 7489 did not define or register an XML namespace; reports generated under RFC 7489 used bare <feedback> with no namespace declaration. Reports not using the correct namespace may be rejected by consumers.
| Change |
Details |
OpenDMARC state |
| XML namespace (sec. 6.1) |
dmarc-2.0 |
Currently generates <feedback> with no namespace declaration. Needs update in opendmarc-reports. |
selector element in DKIM results now REQUIRED (sec. 3.1.1.12) |
Was optional in RFC 7489 |
Already emitted unconditionally in opendmarc-reports (line 1335). A NULL selector from the DB produces <selector></selector> rather than a missing element, which satisfies the REQUIRED constraint. No code change needed. |
testing element in policy_published (sec. 3.1.1.5) |
Reports the value of the t tag |
Not present; added once t tag support is implemented. |
np element in policy_published (sec. 3.1.1.5) |
Reports the non-existent subdomain policy |
Not present; added once np tag support is implemented. |
discovery_method element in policy_published (sec. 3.1.1.5) |
Reports how the record was found: psl or treewalk |
Not present; added once Tree Walk is implemented. |
pass disposition value (sec. 3.1.1.9) |
ActionDispositionType now includes pass (message passed DMARC with an enforcing policy) alongside none, quarantine, reject |
Not implemented. Current code emits only none/quarantine/reject. |
generator element in metadata (sec. 3.1.1.3) |
Optional; identifies the report-generating software |
Not present. Low-priority addition. |
error element in metadata (sec. 3.1.1.3, sec. 3.1.5) |
Describes processing errors encountered while evaluating the DMARC Policy Record |
Not present. |
| Extension mechanism (sec. 3.2, sec. 5) |
<extension> element at file level and namespaced elements at record level |
Not present. Low-priority; only needed if extensions are adopted. |
| Max report size in URI removed (sec. C) |
The !NNNk syntax on rua URIs is now obsolete; receivers MUST ignore it |
The parser likely ignores unknown URI suffixes already; needs verification. |
| DKIM signature priority in reports (sec. 3.1.3) |
Defines a priority order for which signatures to include (strict-aligned pass first, then relaxed, then others) and caps at 100 signatures per row |
Not implemented as specified. |
policy_test_mode override reason (sec. 3.1.6) |
New value for <reason><type> when t=y caused a downgrade |
Not present; added once t tag support is implemented. |
RFC 9991: Failure reporting changes
Failure reports use the ARF format (RFC 6591) with DMARC-specific fields. RFC 9991 clarifies and tightens a few requirements.
| Change |
Details |
OpenDMARC state |
Identity-Alignment ARF field is REQUIRED (sec. 4) |
Must be present in every DMARC failure report |
Needs verification in opendmarc.c failure report generation. |
| Rate-limiting of outgoing failure reports is REQUIRED (sec. 2) |
Generators MUST implement a rate limit to prevent DoS toward report consumers |
Not implemented. No rate-limit logic in failure report generation. |
ruf MUST be ignored for PSD records (sec. 2) |
If the DMARC Policy Record contains psd=y, failure reports MUST NOT be generated for it |
Not implemented; depends on psd tag support being added first. |
| External destination verification (sec. 5) |
Same procedure as RFC 9990 sec. 4 (substituting ruf for rua) |
Existing external destination verification should cover this; needs verification. |
Summary of implementation priority
The changes fall into roughly three tiers:
Required for basic RFC 9989 conformance: t tag (replaces pct), np tag, stop acting on pct/rf/ri from incoming records.
Required for full conformance including PSD DMARC: DNS Tree Walk (replacing the current label-walk fallback), psd tag, PSD policy handling.
Required for updated reporting: XML namespace update, pass disposition value, t/np/discovery_method elements in policy_published, failure report rate-limiting.
RFC 7489 was replaced by three documents published in May 2026: RFC 9989 (core protocol), RFC 9990 (aggregate reporting), and RFC 9991 (failure reporting). None of those documents include a single consolidated list of behavioral changes, so this issue collects them in one place along with a column showing where OpenDMARC currently stands.
References: rfc9989.txt, rfc9990.txt, rfc9991.txt (copies in repo root).
RFC 9989: Core protocol changes
Tags removed from the DMARC Policy Record
pct(sec. 4.7, Appendix A.6, Appendix C.5.2)DMARC_POLICY_T.pct, parsed inopendmarc_policy.c, applied inopendmarc.c. Code needs to learn to ignorepctfrom incoming records rather than act on it.rf(sec. C.5.2)DMARC_POLICY_T.rf; field can be removed from the struct and parser.ri(sec. C.5.2)DMARC_POLICY_T.ri; field can be removed from the struct and parser.Receivers that encounter these tags MUST ignore them (per RFC 9989 sec. 4.8: "unknown tags MUST be ignored"). Since
pctwas a known tag in RFC 7489, receivers that still act on it will diverge from compliant behavior.Tags added to the DMARC Policy Record
t(sec. 4.7, Appendix A.6, Appendix C.5.1)pct=0/100.t=ymeans apply one level below the stated policy (reject->quarantine;quarantine->none);t=n(default) means apply as stated. Has no effect onp=none.DMARC_POLICY_T, no parser support, no enforcement logic. Thepct-based random sampling inopendmarc.cneeds to be replaced with this deterministic one-level downgrade.np(sec. 4.7, Appendix C.5.1)sp(existing subdomains). Falls back tospthenpif absent.DMARC_POLICY_T, no parser support. The policy selection logic needs a third branch after the existingp/spcheck.psd(sec. 4.7, Appendix C.5.1)y/n/u). Used by the DNS Tree Walk to identify Organizational Domain boundaries.Policy discovery: DNS Tree Walk replaces PSL
RFC 7489 required a Public Suffix List (PSL) to determine the Organizational Domain. RFC 9989 replaces this with a "DNS Tree Walk" (sec. 4.10, sec. 4.10.1): starting from
_dmarc.<author-domain>, query progressively shorter labels until a valid DMARC Policy Record is found or the walk is exhausted. Thepsdtag in a discovered record signals whether to stop.PublicSuffixList)psd=tags. The full RFC 9989 Tree Walk (stopping onpsd=n/psd=y, querying through TLDs, 8-label jump for long domains) is not yet implemented and will replace this fallback before release.psd=yin the record and the Tree Walkpsdtag support and full Tree Walk.Note: in practice, the RFC 9989 Tree Walk's
psd=stopping condition provides no benefit until PSO operators publishpsd=yin their DMARC records -- adoption of which is expected to be slow. Until then, the label-walk fallback and the RFC 9989 Tree Walk produce equivalent results for the overwhelming majority of real mail.The Tree Walk involves multiple DNS queries and has different failure modes than a PSL lookup. Implementing it requires changes in
libopendmarc/opendmarc_policy.c(the DNS query and record fetch logic) and possibly a new config option to select PSL vs. Tree Walk behavior during a transition period.Authentication-Results header
RFC 9989 sec. 9.1 formally registers
policy.dmarcas an AR result property. This was implemented in OpenDMARC in PR #365 and is already present.RFC 9990: Aggregate reporting changes
RFC 9990 sec. 6.1 formally registers
urn:ietf:params:xml:ns:dmarc-2.0as the XML namespace for DMARC aggregate reports. RFC 7489 did not define or register an XML namespace; reports generated under RFC 7489 used bare<feedback>with no namespace declaration. Reports not using the correct namespace may be rejected by consumers.dmarc-2.0<feedback>with no namespace declaration. Needs update inopendmarc-reports.selectorelement in DKIM results now REQUIRED (sec. 3.1.1.12)opendmarc-reports(line 1335). ANULLselector from the DB produces<selector></selector>rather than a missing element, which satisfies the REQUIRED constraint. No code change needed.testingelement inpolicy_published(sec. 3.1.1.5)ttagttag support is implemented.npelement inpolicy_published(sec. 3.1.1.5)nptag support is implemented.discovery_methodelement inpolicy_published(sec. 3.1.1.5)pslortreewalkpassdisposition value (sec. 3.1.1.9)ActionDispositionTypenow includespass(message passed DMARC with an enforcing policy) alongsidenone,quarantine,rejectnone/quarantine/reject.generatorelement in metadata (sec. 3.1.1.3)errorelement in metadata (sec. 3.1.1.3, sec. 3.1.5)<extension>element at file level and namespaced elements at record level!NNNksyntax onruaURIs is now obsolete; receivers MUST ignore itpolicy_test_modeoverride reason (sec. 3.1.6)<reason><type>whent=ycaused a downgradettag support is implemented.RFC 9991: Failure reporting changes
Failure reports use the ARF format (RFC 6591) with DMARC-specific fields. RFC 9991 clarifies and tightens a few requirements.
Identity-AlignmentARF field is REQUIRED (sec. 4)opendmarc.cfailure report generation.rufMUST be ignored for PSD records (sec. 2)psd=y, failure reports MUST NOT be generated for itpsdtag support being added first.rufforrua)Summary of implementation priority
The changes fall into roughly three tiers:
Required for basic RFC 9989 conformance:
ttag (replacespct),nptag, stop acting onpct/rf/rifrom incoming records.Required for full conformance including PSD DMARC: DNS Tree Walk (replacing the current label-walk fallback),
psdtag, PSD policy handling.Required for updated reporting: XML namespace update,
passdisposition value,t/np/discovery_methodelements inpolicy_published, failure report rate-limiting.