Changing a DMARC record in DNS does not make every receiving system use the new policy at the same instant. Receivers may have cached different answers, may evaluate messages at different times, and may finish assembling reports later. A single reporting period can therefore contain observations made before and after the change.
That can make an aggregate report look contradictory. You may see a p=none policy in policy_published while some rows show dispositions associated with a stronger policy. You may also receive two reports with the same date range. These are not automatically parser errors or evidence that the DNS record is being ignored.
The current DMARC aggregate reporting specification, RFC 9990, explicitly describes both outcomes. A receiver may put old and new policy observations into one report, or it may send separate reports for each policy configuration observed during the period.
Suppose example.com starts with:
_dmarc.example.com. TXT "v=DMARC1; p=none; rua=mailto:dmarc-reports@example.org"Later, the record changes to:
_dmarc.example.com. TXT "v=DMARC1; p=quarantine; rua=mailto:dmarc-reports@example.org"The change affects future DNS lookups, but it does not rewrite the result of messages already evaluated. A receiver might have looked up p=none early in the day, cached it, and processed those messages under that policy. After its cache refreshes, it might process later messages under p=quarantine.
The reporting interval is a collection window, not a promise that every message was evaluated using one instantaneous DNS answer. Report generation can also happen after the collection period, which adds another timing boundary. DNS TTLs influence when cached data can expire, but receiver implementations, local caches, retries, and report scheduling all affect what appears in practice.
This is why a policy change should be treated as a historical boundary. Do not expect a report covering the changeover day to describe only the record visible in DNS when the report arrives.
policy_published may look too simpleAn aggregate report contains one policy_published element. It describes the policy configuration the reporting system chose to publish for that report. It is not necessarily a complete timeline of every policy answer used for every message in the reporting interval.
RFC 9990 allows a report to contain dispositions based on the old policy, the new policy, or a mix of both, even though the XML has only one policy_published element. In another valid implementation, the receiver can send multiple reports for the same interval, with one report for each configuration it observed.
For example, a report might contain:
<policy_published>
<domain>example.com</domain>
<p>quarantine</p>
</policy_published>That does not prove that every message counted in the report was processed under quarantine. The record rows tell you the observed traffic and DMARC outcomes, while the policy section provides the report-level policy context. During a DNS transition, that context has limits.
There is a second source of apparent mixing: one report can contain records for several Author Domains. A policy discovered at example.com can cover example.com and subdomains that do not have their own applicable record. The policy_published/domain value is the DMARC Policy Domain, while identifiers/header_from identifies the message's Author Domain. Keep those fields separate when interpreting the report. RFC 9990 explains this distinction.
The normal reporting window is commonly one UTC day, but the reporting specification does not require one report per domain per day in every situation. During a policy change, a receiver may generate multiple reports for the same period because it observed more than one published configuration.
That is different from a mail system accidentally sending the same report twice. The two cases require different handling:
| Situation | What it means | Safe response |
|---|---|---|
| Same report delivered twice | A retransmission or duplicate delivery | Ingest once using report identity |
| Same interval, different policy context | Separate observations during a DNS change | Keep both and analyze both |
| Partially overlapping intervals | Collection or retry windows overlap | Flag the overlap before adding counts |
| Same policy, different records | Different IP, alignment, or Author Domain tuples | Keep both rows; they are not duplicates |
The date range alone is not a unique key. A report's report_metadata/report_id, reporting organization, policy domain, and attachment identity are more useful signals. RFC 9990 requires a Report-ID to be unique among reports sent to the same domain and requires a retransmission to use the same identifiers and filename.
Store the original report as an immutable ingestion object before normalizing its rows. At minimum, retain:
report_metadata/org_namepolicy_published/domainreport_metadata/report_iddate_range/begin and date_range/endUse the Report-ID as the primary duplicate signal, scoped to the report recipient and policy domain. Use the filename and checksum as secondary signals for retransmissions or malformed messages. Do not discard a report simply because its date range matches an existing report.
Two reports with the same interval but different Report-IDs may both be valid. If their policy values differ, preserve both policy contexts and mark the interval as having a policy transition. If the policy values match but the records differ, compare the reporting organization, IDs, and checksums before deciding whether the data is duplicated.
When a retransmission has the same identity, do not add its counts again. Mark it as a duplicate delivery and retain the raw message or attachment for audit purposes. When two reports partially overlap, do not blindly sum them either. There is no universal way to subtract an overlapping subset unless the receiver provides enough information to establish that relationship.
A matching date range is not proof of duplicate data. A matching Report-ID is strong evidence of retransmission; different IDs may represent separate policy observations.
Start with the DNS history, not just the current record. Record the old and new DMARC values, the change time, TTL, authoritative nameservers, and any staged DNS deployment. The current answer cannot tell you what a receiver saw several hours earlier.
Next, group reports by reporting organization and policy domain. Within each group, compare:
policy_published values, including p, sp, np, adkim, aspf, t, and discovery_method when presentheader_from, SPF domain, DKIM domain, and dispositionThe discovery_method field is especially useful with newer reports. psl and treewalk can indicate that receivers used different DMARC policy-discovery models during the transition described by RFC 9989. That is not the same as a DNS propagation problem, so keep discovery method separate from the policy values themselves.
For a broader introduction to the XML structure, see DMARC reporting 101. For parser behavior around newer namespaces and report identity, see How to update a DMARC aggregate-report parser for RFC 9990.
A robust processor should not replace historical policy data with the current DNS answer. The record published today is useful for current operations, but it is not evidence of the policy used when an older message was evaluated.
Instead:
This also prevents a common reporting mistake: rewriting every row from a transition day as though it had been evaluated under the final policy. That can turn a temporary reporting ambiguity into a permanent historical error.
When changing p, sp, or np, note the exact time and keep the old value in the change record. Avoid making several unrelated authentication changes in the same window if the reports will be used to measure the result. Leave enough time for the normal reporting cycle to pass before judging the new policy, and expect the first report or two to need extra inspection.
Do not treat a mixed report as proof that enforcement failed. The disposition in an aggregate row is what the receiver reports for the matching messages, and receivers can also apply local policy overrides. A policy change affects what receivers discover and apply going forward; it does not make the reporting history uniform retroactively.
Mixed policies and apparent duplicates after a DNS change are consequences of distributed evaluation, not necessarily broken DMARC. Receivers do not share one cache, one clock, or one report-generation schedule.
The safe approach is to preserve report identity and policy context, distinguish retransmissions from separate observations, and flag overlaps for review. Once the transition window is understood, the reports become useful evidence of when different receivers moved from the old policy to the new one.