How to read a DMARC report
Publish a DMARC record with a rua address and, within a day or two, the reports start arriving: emails with compressed XML attachments, one per reporting operator, most days, forever. Open one and you meet a wall of angle brackets.
The good news: the format is rigid, the vocabulary is small, and once you have walked through one report you can read them all. This article dissects a realistic aggregate report line by line — and shows you the two failing-row patterns that matter: the spoofer, and your own misconfigured sender. (Aggregate reports are one of two DMARC report types; the other, forensic ruf reports, is covered in RUA vs RUF.)
What is a DMARC report?
A DMARC report is an XML summary that a receiving mail provider sends daily, listing every server that sent mail using your domain and whether each one passed authentication.
Reports come from the receiving side, not from you. Any mail operator that evaluates DMARC — Google, Microsoft, Yahoo and many others — counts the messages it received claiming to be your domain, records how each one authenticated, and posts a summary back to the address in your record's rua= tag. Reporting is voluntary and not every operator does it, but the large mailbox providers do, which covers most real-world delivery for a typical domain.
Almost every reporter uses a 24-hour window, so you receive roughly one report per operator per day: a handful of emails each morning for a single domain, more as you add domains and reach more providers. The date_range block inside the file states the exact window it covers, so you are never guessing which day you are looking at.
Each report arrives as an email with a compressed attachment — a gzip or ZIP file wrapping one XML document — with the reporter, your domain and the window's epoch timestamps packed into the filename. That is the entire delivery mechanism: no portal, no API, no login.
There is very little to be uneasy about in the contents. An aggregate report carries counts and authentication results only: no message bodies, no subject lines, no recipient addresses. It tells you that 142 messages came from one IP address and passed both checks; it never tells you who they were sent to.
All of that describes aggregate (rua) reports, which is what people mean by "a DMARC report" in almost every case. A second, far rarer type — forensic (ruf) reports — does carry per-message detail, and most large providers decline to send it at all. RUA vs RUF covers the difference and why you can safely build your rollout on aggregate data alone.
Where do DMARC reports go?
Reports go where the rua= tag of your published DMARC record points, and nowhere else. There is no account to open with a reporter and nothing to switch on at their end — publishing the tag is the whole subscription mechanism, and removing it stops the flow. A record with no rua= produces no reports at all — one of the causes worked through in No reports yet? What to check, which starts with the likeliest one of all: the daily reporting cycle simply not having turned over yet.
You can list more than one address, separated by commas, and reporters send a copy to each. That is how you keep reports arriving in an existing mailbox while pointing a second copy at a monitoring service. One constraint catches people out: when the reporting address sits on a different domain from the one being reported on, that other domain has to publish a short TXT record authorising it. Hosted reporting services publish that record for their own domain, so it is not usually something you configure.
A plain mailbox works well for about a week. Then the arithmetic catches up. Several reporters, one report each per day, per domain — even a small estate accumulates a few hundred compressed attachments a month, and every question worth asking spans a lot of them. "Is that new sender aligned yet?" means opening a fortnight of files from four reporters and adding up rows by hand. "When did the failures start?" means doing it again in date order. Nothing about a single report is difficult; the volume is what defeats reading them by hand, and it is why most people eventually point the rua address at something that collects and aggregates the stream for them.
How to open a DMARC report file
The attachment is compressed, so nothing useful happens if you double-click it in your mail client. Save it first, then handle it by extension.
| Extension | What it is | How to open it |
|---|---|---|
.xml | The report itself, uncompressed | Any text editor, or a browser |
.xml.gz | gzip-compressed XML — much the most common form | gunzip on macOS and Linux, 7-Zip or similar on Windows |
.zip | A ZIP archive containing one XML file | Built-in unzip on every operating system |
On macOS and Linux, one command handles the usual case:
gunzip google.com!example.com!1783382400!1783468800.xml.gz
Windows unzips .zip files natively but has no built-in gzip support, so .xml.gz needs 7-Zip or an equivalent. Keep the original filename where you can: the two long numbers in it are the epoch start and end of the reporting window, which is the quickest way to sort a folder of reports into order. Once expanded, the .xml file opens in any text editor, and most browsers render the element tree with collapsible nodes.
If you would rather skip the unpacking entirely, drop the file straight into the free DMARC report analyzer. It takes .xml, .xml.gz and .zip files exactly as your mailbox delivers them, decompresses and parses them in the browser using the browser's own DecompressionStream API, and never uploads them anywhere — which matters when you are handling reports for a domain that is not yours to share. It reads several files in one go, so a whole morning's attachments can be dropped in together. The in-browser decompression needs a reasonably current browser; on an older one the page says so and asks for an uncompressed .xml file instead.
With a report open in front of you, the rest of this article is a guided tour of what is inside it.
A real report, in full
Here is a complete aggregate report of the kind Google sends — shortened only in the obvious places. Suppose it landed in the reporting mailbox for example.com:
<?xml version="1.0" encoding="UTF-8"?>
<feedback>
<report_metadata>
<org_name>google.com</org_name>
<email>[email protected]</email>
<report_id>8391457200973462518</report_id>
<date_range>
<begin>1783382400</begin>
<end>1783468800</end>
</date_range>
</report_metadata>
<policy_published>
<domain>example.com</domain>
<adkim>r</adkim>
<aspf>r</aspf>
<p>quarantine</p>
<sp>quarantine</sp>
<pct>100</pct>
</policy_published>
<record>
<row>
<source_ip>209.85.220.41</source_ip>
<count>142</count>
<policy_evaluated>
<disposition>none</disposition>
<dkim>pass</dkim>
<spf>pass</spf>
</policy_evaluated>
</row>
<identifiers>
<header_from>example.com</header_from>
<envelope_from>example.com</envelope_from>
</identifiers>
<auth_results>
<dkim>
<domain>example.com</domain>
<selector>google</selector>
<result>pass</result>
</dkim>
<spf>
<domain>example.com</domain>
<scope>mfrom</scope>
<result>pass</result>
</spf>
</auth_results>
</record>
<record>
<row>
<source_ip>198.51.100.7</source_ip>
<count>9</count>
<policy_evaluated>
<disposition>quarantine</disposition>
<dkim>fail</dkim>
<spf>fail</spf>
</policy_evaluated>
</row>
<identifiers>
<header_from>example.com</header_from>
<envelope_from>bulk.cheap-relay.example.net</envelope_from>
</identifiers>
<auth_results>
<spf>
<domain>bulk.cheap-relay.example.net</domain>
<scope>mfrom</scope>
<result>softfail</result>
</spf>
</auth_results>
</record>
</feedback>
Everything lives inside a root feedback element, with three kinds of children: one report_metadata, one policy_published, and one or more record blocks. Let's take them in order.
report_metadata: who is telling you this, and when
org_name and email identify the reporting operator — here Google, reporting on mail it received claiming to be your domain. report_id is a unique identifier (useful for de-duplicating, since reports are occasionally re-sent). date_range gives the reporting window as Unix epoch seconds in begin and end — almost always a 24-hour slice.
One mental adjustment matters here: each report shows one operator's viewpoint. To see your whole picture, you combine reports from every reporter, every day.
policy_published: your DMARC record, as the receiver saw it
This block echoes back the policy the reporter applied: your domain, the alignment modes (adkim, aspf — here both r, relaxed), your policy p, subdomain policy sp, and the rollout percentage pct. It is worth a glance every time — if it does not match the record you think you published, you have a DNS problem or a propagation delay to investigate.
record, row and policy_evaluated: the verdicts
Each record summarises one source: source_ip is the sending server, count is how many of its messages this reporter saw in the window.
policy_evaluated is where DMARC's judgement lands, and it repays careful reading:
dkimandspfhere are DMARC-level results — after alignment, not the raw check results. A raw SPF pass for someone else's domain shows up here asfail.dispositionis what the receiver actually did:none(delivered),quarantineorreject. Because a policy is a request rather than a command, this can differ from your publishedp=— the disposition is the ground truth.
identifiers: the From address in question
header_from is the domain your recipients saw in the From line — the domain alignment is measured against. envelope_from, when present, is the hidden bounce domain. In our first record both are example.com; in the second, the visible From says example.com but the envelope points somewhere else entirely. That gap is your first clue.
auth_results: the raw evidence
Here are the underlying check results with the domains they ran against — which is exactly what you need to diagnose alignment. In the first record, DKIM verified for example.com (selector google) and SPF passed for example.com: aligned on both counts, hence the clean policy_evaluated. In the second record there is no DKIM signature at all, and SPF soft-failed for an unrelated relay domain.
Spoofing or misconfiguration? Reading a failing row
Every failing record you will ever see is one of two stories, and auth_results plus envelope_from almost always tells you which.
The spoofer looks like our second record: an IP you do not recognise, no DKIM signature (or one from an unrelated domain), an envelope domain with no connection to your business, and modest counts — often single digits, probing. Nothing here is yours to fix; the policy is doing its job (disposition was quarantine). This row is the reason you deployed DMARC.
The misconfigured legitimate sender looks different: a recognisable provider's IP range, healthy steady volume, and — the giveaway — partial authentication. Typically SPF passes but for the provider's own bounce domain (unaligned), and DKIM either fails or signs as the provider's domain rather than yours. The message is genuine; the setup is unfinished. The fix is on your side: complete the provider's custom-domain DKIM so an aligned check passes — see DMARC alignment explained for exactly why that one change flips the verdict.
Misreading these two stories in each other's direction is the costly mistake: "fixing" a spoofer wastes your time, while enforcing your policy on top of an unfixed legitimate sender loses real mail.
Doing this every day — or not
You can absolutely read reports by hand, and doing it once is genuinely worth it — it is the fastest way to understand what DMARC actually measures. But the arithmetic is against you: several reporters, daily reports, multiple records each, across every domain you run. The XML above was one operator's view of one day.
That daily grind is the part worth automating — collecting the reports, parsing them, and aggregating every reporter's view into one per-sender picture with alignment verdicts. To parse a single report right now without signing up, drop the file into the free DMARC report analyzer and see every row decoded on screen. If you'd rather let DMARCmetric handle the ongoing collection, create a free account and point your rua address at us — or poke around the live demo first to see parsed reports without signing up.