Blog · Dmarc

How to Read DMARC Aggregate XML Reports Without a Third Party

The real reason you cannot read your own DMARC reports

You set up DMARC. You published p=reject. Your aggregate reports are arriving every day at the address you specified. And you have no idea what is in them, because the reports are XML files and your first instinct was to forward them to a third-party service to make sense of the mess.

That instinct is understandable. It is also not necessary.

The reason practitioners end up using third-party services is not that the XML is technically opaque. It is verbose, but the structure is logical and the key fields are finite. The real barrier is that reading XML manually does not scale. As soon as you have more than one report, or more than one domain, manual reading stops working.

This article covers both: how to read a DMARC aggregate report XML file yourself, and how to decide when it is worth switching to a tool that handles the parsing for you.

What a DMARC aggregate report actually contains

A DMARC aggregate report is an XML file generated by receivers (Gmail, Microsoft, Fastmail, anyone processing mail on your behalf) and sent to the address in your DMARC record's rua tag. The file covers a reporting window, typically one day, and summarizes every message that passed or failed DMARC evaluation during that period.

The structure is:

xml
<feedback>
  <report_metadata>    -- Who sent the report, time window
  <policy_published>    -- Your DMARC policy as the receiver saw it
  <record>             -- Individual message results
    <row>              -- Source IP, count, policy applied
    <source>           -- Envelope details
    <auth_result>      -- SPF, DKIM, DMARC evaluation
  </record>
</feedback>

Each <record> block represents one sending source IP that handled mail for your domain during the reporting window. A single report typically contains multiple records.

Step-by-step: how to open and read the file yourself

Step 1: Decompress the file if needed. Many receivers send the report as a .gz file. On Linux or macOS:

bash
gunzip report-2026-09-01T0000T0000.xml.gz

On Windows, 7-Zip or WinRAR can open .gz files without a terminal.

Step 2: Open the XML in a text editor. Any plain text editor works. Notepad, TextEdit, VS Code, nano. Do not open the raw XML in a browser -- browsers try to render it as HTML and produce a wall of broken formatting.

Step 3: Read the report metadata. Find <report_metadata> near the top:

xml
<report_metadata>
  <org_name>google.com</org_name>
  <email>no-reply-dmarc-support@google.com</email>
  <date_range>
    <begin>1725148800</begin>
    <end>1725235200</end>
  </date_range>
</report_metadata>

The <begin> and <end> values are Unix timestamps. Convert them (multiply by 1000 and paste into a timestamp converter) to see the exact 24-hour window. This matters because if you changed your DNS mid-window, a spike in failures might be your own change, not an external problem.

Step 4: Check what policy the receiver applied. Look for <policy_published>:

xml
<policy_published>
  <domain>example.com</domain>
  <adkim>r</adkim>
  <aspf>r</aspf>
  <p>reject</p>
  <sp>none</sp>
  <pct>100</pct>
</policy_published>

<p>reject</p> means full enforcement was active during this window. <adkim>r</adkim> and <aspf>r</aspf> mean relaxed alignment. If this does not match what you think you published, your DNS had not fully propagated when this report was generated.

Step 5: Read the individual records. Each <record> block tells you about one source IP:

xml
<record>
  <row>
    <source_ip>203.0.113.45</source_ip>
    <count>1847</count>
    <policy_evaluated>
      <disposition>none</disposition>
      <dkim>pass</dkim>
      <spf>pass</spf>
    </policy_evaluated>
  </row>
  <auth_result>
    <dkim>
      <domain>example.com</domain>
      <result>pass</result>
    </dkim>
    <spf>
      <domain>example.com</domain>
      <result>pass</result>
    </spf>
  </auth_result>
</record>

Source IP 203.0.113.45 sent 1847 messages, all passed DMARC, disposition=none means they were delivered normally.

Look for records where <disposition> shows quarantine or reject, or where <dkim> or <spf> shows fail. Those entries are what you need to investigate.

What to look for in any report

Three things matter most when scanning a report for problems:

1. Failure proportion. A handful of failures from a large total is often legitimate forwarding. A high failure rate from a source you do not recognize is worth investigating immediately.

2. Alignment failures. DMARC requires at least one of DKIM or SPF to align with the From header domain. A record showing dkim=fail and spf=fail on a high-volume source means the receiver saw mail from an unauthorized server claiming to be from your domain.

3. Unknown sending sources. If a source IP is unfamiliar, run a reverse DNS lookup. A marketing platform you no longer use is a common surprise. Forgotten senders are a more frequent cause of DMARC failures than attackers.

How to parse multiple reports with a short script

Manual reading works fine for a one-off check. But if you are reviewing reports weekly across multiple domains, manual reading does not scale. A short Python script extracts the key fields from any number of reports:

python
import xml.etree.ElementTree as ET

tree = ET.parse("report.xml")
ns = {"dmarc": "http://dmarc.org/message/reporting/standard/schema"}
for record in tree.findall(".//dmarc:record", ns):
    row = record.find("dmarc:row", ns)
    count = row.find("dmarc:count", ns).text
    disp = row.find("dmarc:policy_evaluated/dmarc:disposition", ns).text
    ip = row.find("dmarc:source", ns).text
    print(f"{ip}: {count} messages, disposition={disp}")

Run this across all your reports and sort by count descending. You will find the loudest problems first. This approach keeps your report data on your own machine. You are not uploading anything to a vendor.

When a dedicated tool is worth it instead

The threshold for switching from manual reading to a tool is simple: when the time cost becomes real. One domain, occasional checks: read the XML manually. Multiple domains or weekly reviews: the manual approach starts costing real time, and a tool pays for itself.

The practical reason most teams use a tool is not that the XML is unreadable. It is that parsing XML at scale, tracking changes over time, and correlating failures across multiple domains requires something more than a text editor. If you are in that position and you do not want to send your reports to a vendor, DMARCFlow processes aggregate reports in your own infrastructure. It receives reports via your endpoint and parses them locally, which is the practical difference from services that require uploading data to their cloud. The 1 EUR/month entry tier is enough for most small teams.

FAQ

Can I read DMARC reports without internet?

Yes. The XML file is self-contained. As long as you have the file, you can read it offline. The only internet requirement is downloading the attachment from your inbox.

What if my reports are malformed or do not parse?

The most common cause of parsing errors is a non-compliant sender. Some receivers generate reports that do not strictly follow RFC 7489. If a report fails to parse, try opening the raw XML in a text editor first. Some receivers embed the DMARC report in the email body rather than sending it as a clean file attachment.

How often do reports arrive?

Most major receivers (Gmail, Yahoo, Microsoft) send aggregate reports daily. Some aggregate over longer periods for low-volume domains. If you are not receiving reports at all, check that your DMARC record's rua tag points to a valid email address or endpoint that can receive attachments.

Do I need to send reports to a third party to read them?

No. The XML file arrives in your inbox. You can read it yourself with a text editor, parse it with a script, or process it with a local tool that runs on your own infrastructure.