A DMARC XML report viewer turns the compressed aggregate files receivers send into readable tables of sending IPs, counts and SPF, DKIM and DMARC results. Use a local script, an online parser or a monitoring platform.
This guide shows how to open the files, gives you a short local viewer script, and explains when a simple viewer stops being enough. For a field-by-field walkthrough of the XML, see our existing guide, "How to Read and Interpret Raw DMARC XML Reports"; this article focuses on the viewing options.
What does a DMARC XML report viewer do?
A DMARC XML report viewer parses the aggregate (RUA) files that mailbox providers send and displays them as sortable tables or charts. Instead of nested XML tags, you see each sending IP, message count, aligned SPF and DKIM results and the disposition the receiver applied.
Viewers range from a text editor with XML formatting, to a small script or browser-based parser, to a monitoring platform that stores reports over time. The right choice depends on how many reports you receive and how many domains you manage.
What is inside a DMARC aggregate report?
Aggregate reports are defined in RFC 7489. Each file covers one reporting organisation, one domain and one time window, and usually arrives as a gzip or zip attachment. It has three main parts: report_metadata, policy_published and one or more record blocks that group messages by source IP and outcome.
A trimmed example:
<feedback>
<report_metadata>
<org_name>receiver.example.net</org_name>
<report_id>1234567890</report_id>
<date_range><begin>1700000000</begin><end>1700086399</end></date_range>
</report_metadata>
<policy_published>
<domain>example.com</domain>
<adkim>r</adkim><aspf>r</aspf>
<p>none</p><sp>none</sp><pct>100</pct>
</policy_published>
<record>
<row>
<source_ip>203.0.113.25</source_ip>
<count>42</count>
<policy_evaluated>
<disposition>none</disposition>
<dkim>pass</dkim>
<spf>fail</spf>
</policy_evaluated>
</row>
<identifiers><header_from>example.com</header_from></identifiers>
<auth_results>
<dkim><domain>example.com</domain><selector>s1</selector><result>pass</result></dkim>
<spf><domain>bounce.example.org</domain><result>pass</result></spf>
</auth_results>
</record>
</feedback>
How do you open a DMARC XML report?
Decompress the attachment, then open the resulting .xml file in a text editor or browser. Reports usually arrive as .gz or .zip. An XML-aware editor can fold and indent the tags so the structure is readable, but you still have to add up counts yourself.
Treat attachments as untrusted data. Save each one to a new, empty folder, keep any scripts in a different folder, and never run anything found inside an archive.
How can you view reports locally with a short script?
A few lines of Python can total count by source_ip and by aligned result across every report you have downloaded. Nothing leaves your machine, which makes this a safe viewer for a small domain.
Save this as dmarc_totals.py in a scripts folder, separate from the folder holding the reports:
import gzip
import os
import sys
import zipfile
import xml.etree.ElementTree as ET
from collections import Counter
def load(path):
if path.endswith(".gz"):
with gzip.open(path) as f:
return f.read()
if path.endswith(".zip"):
with zipfile.ZipFile(path) as z:
return z.read(z.namelist()[0])
with open(path, "rb") as f:
return f.read()
def strip_namespaces(root):
# DMARCbis reports declare an XML namespace; drop it from every tag
for el in root.iter():
if isinstance(el.tag, str):
el.tag = el.tag.rsplit("}", 1)[-1]
totals = Counter()
for path in sys.argv[1:]:
if not os.path.isfile(path):
continue # skip unmatched glob patterns
root = ET.fromstring(load(path))
strip_namespaces(root)
for rec in root.iter("record"):
row = rec.find("row")
if row is None:
continue
pe = row.find("policy_evaluated")
if pe is None:
continue
key = (
row.findtext("source_ip"),
pe.findtext("dkim"),
pe.findtext("spf"),
pe.findtext("disposition"),
)
totals[key] += int(row.findtext("count", "0"))
print("count", "source_ip", "dkim", "spf", "disposition", sep="\t")
for (ip, dkim, spf, disp), n in totals.most_common():
print(n, ip, dkim, spf, disp, sep="\t")
Run it in isolated mode and pass the report paths as arguments. The shopt -s nullglob line makes Bash drop a pattern that matches nothing, so a folder with only .gz or only .zip files still works:
shopt -s nullglob
python3 -I /home/you/scripts/dmarc_totals.py /home/you/reports/*.gz /home/you/reports/*.zip
The output lists the busiest sources first, with their aligned DKIM and SPF results. The strip_namespaces step matters because the standard find and iter calls only match tags without a namespace. Without it, a report that declares a default namespace, as the DMARCbis aggregate format does, would produce an empty table and no error.
Python's standard XML parser is not hardened against maliciously crafted files, so if reports come from sources you do not trust, use a hardened parser such as the defusedxml package instead.
How do you read the key fields in a report?
Start with source_ip, count and the policy_evaluated block. They show who sent how much mail for your domain and whether SPF and DKIM passed with alignment. DMARC passes when at least one of them passes and aligns with the visible From domain.
What to check:
policy_evaluatedversusauth_results: insidepolicy_evaluated, thedkimandspfelements give the aligned result for each mechanism, whileauth_resultsholds the raw result for the domains that were checked. In the example, SPF passed forbounce.example.orgbut did not align withexample.com, while DKIM passed and aligned, so DMARC still passed.- Unknown IPs with high counts: these are either an authorised service you have not yet set up, or spoofing.
- Selectors: the DKIM
selectorhelps you identify which service signed the message.
Use the free DMARC lookup tool to confirm which policy receivers see, and the domain scanner to check SPF and DKIM records for the sources you identify.
What do disposition and reason mean in a report?
The disposition element (none, quarantine or reject) records the action the receiver actually took on the messages in that row. It is not always what your published policy alone would produce, because receivers can override it. Overrides are explained in the reason element.
Inside policy_evaluated, a reason element carries a type and an optional comment. RFC 7489 defines these types: forwarded, sampled_out, trusted_forwarder, mailing_list, local_policy and other. For example, if your record has pct=50, some failing mail is expected to be marked sampled_out and treated under a weaker disposition. A mailing_list or forwarded reason often explains failures that are not spoofing, and local_policy means the receiver applied its own rules.
When you see a disposition of none on failing mail, check for a reason before assuming your policy is being ignored.
Why am I not receiving any DMARC reports?
Missing reports usually mean the rua tag is absent or malformed, the mailbox rejects large attachments, or no reporting provider has handled mail from your domain yet. Receivers typically send reports on a daily cycle, so allow a day or two after publishing.
A valid record looks like this:
_dmarc.example.com. IN TXT "v=DMARC1; p=none; rua=mailto:dmarc-reports@example.com"
If the reporting address is on a different domain, that domain must authorise it with a TXT record, as described in RFC 7489:
example.com._report._dmarc.reports-example.net. IN TXT "v=DMARC1"
Also check that the mailbox is not full and that mail filters are not discarding the attachments.
When is a simple XML viewer not enough?
A basic viewer or script works for one small domain over a short period. It gets hard to manage with many reports per day, several domains or a goal of moving to enforcement. Each receiver sends its own file, so you merge counts across providers yourself, and raw IPs do not tell you which service they belong to.
Signs you have outgrown a manual viewer:
- You receive daily reports from multiple providers and cannot keep up.
- You need trends over weeks rather than a single day.
- You manage several domains, or domains for clients.
- You must tell legitimate senders from spoofers before changing
p=nonetop=quarantineorp=reject.
In those cases, DMARC monitoring collects and visualises your aggregate reports so you do not have to parse XML yourself. If you would rather hand off enforcement too, the managed service has the yourDMARC team run the rollout to p=reject. You can compare plans on the pricing page, and MSPs managing client domains can look at the partner programme.
Is it safe to use an online XML report viewer?
It can be, but be careful about what you upload. Aggregate reports contain your sending IP addresses, domains and message volumes. They do not include message bodies, but they still describe your mail infrastructure, so check who operates a viewer and how it handles uploaded files first.
If you prefer to stay local, the script above gives you the same totals without sharing anything.
Key takeaways
- A DMARC XML report viewer converts aggregate reports into readable tables of sources, volumes and authentication results.
- Reports are XML files, usually gzip or zip, defined in RFC 7489.
- In
policy_evaluated,dkimandspfare aligned results, anddispositionis the action the receiver took; raw results sit inauth_results. - Check the
reasonelement for overrides such assampled_out,forwarded,mailing_listandlocal_policybefore assuming a policy problem. - A short local script covers small volumes without uploading anything. Strip XML namespaces and skip unmatched file patterns, or DMARCbis-format reports and single-format folders will give empty output or errors.
- If no reports arrive, check the
ruatag, external-domain authorisation and mailbox capacity, and allow a day or two. - For many domains or an enforcement rollout, use a monitoring tool, and be cautious with online viewers.










