The weekly email has arrived from noreply@dmarcreport.com, and its attachment is an XML file that you open and see nothing but a few nested tags and hundreds of rows of IPs. The real question is: which of these IPs is ours, and which one is sending email on behalf of our domain? If you don't know the answer to this question, you can never move past p=none.
What a DMARC report actually tells you
Any _dmarc record configured with rua=mailto:dmarc@example.com causes major receivers like Gmail and Outlook to send you an XML file once or several times a day. This file has two parts: <report_metadata>, which says which server generated the report and over what time range, and <record>, which creates a separate block for each combination of "sender IP + From domain."
Inside each <record>, three things matter. First, <source_ip>, which is the sender's address. Second, <count>, which gives the number of messages in that range. Third, <policy_evaluated>, which shows the result of the SPF and DKIM evaluation with <spf>pass</spf> or <dkim>fail</dkim>. If both fail, that record is a spoofed message.
How to read raw XML without heavy tools
You don't need to install a graphical panel first. With xmllint, which is available on most distributions, you'll get your answer in seconds. Suppose you've extracted the file from the email and it's named report.xml:
xmllint --xpath '//record[policy_evaluated/spf="fail" and policy_evaluated/dkim="fail"]/row/source_ip/text()' report.xml
This command prints only the IPs that failed both SPF and DKIM. If the output is empty, that's good news. If a few IPs come back, those are the real suspects. To see the message count for each one:
xmllint --xpath '//record/row[source_ip="203.0.113.45"]/count/text()' report.xml
A practical tip: reports are usually gzipped. First run gunzip report.xml.gz and then the command above. If you receive several reports a day, run a simple loop over the folder and aggregate the output into a single file; opening XML by hand only works the first time.
Separating authorized senders from spoofed ones
The criterion is simple and doesn't require guessing. Any IP that appears in the report with spf=pass and dkim=pass is an authorized sender; meaning it's either your own email server or a transactional service like email marketing for which you've configured SPF and DKIM. Any IP that fails both is spoofing. The ambiguous case is when one passes and one fails; for example, SPF passed but DKIM didn't. Here there's usually a forwarder or mailing list in the middle that forwarded the message and broke the DKIM signature.
To make sure an authorized IP is really yours, ask for its reverse lookup:
dig -x 203.0.113.45 +short
If the output points to your own email service's domain, it's authorized. If it points to an unknown host or a foreign ISP, that's the spoofer. To check your own SPF and DKIM records, you can also use DNS and network lookup tools to make sure the records are published correctly.
This is where people make mistakes
The most common mistake I've seen is this: the site admin looks at the report, finds a few unknown IPs, and immediately moves the policy from p=none to p=reject. Two weeks later, a complaint comes in that the site's contact form isn't sending emails and customers aren't receiving anything.
The reason is that the unknown IP was the company's own email server sending from a subdomain or from a cloud service, and someone forgot to update SPF for it. The sign is exactly this: sending from inside the organization stops, but sending from external services stays healthy. Before any change, check the list of authorized IPs with the infrastructure team, not just with the report.
When can you set p=reject
It has a numerical criterion, not a gut feeling. If over a period of at least two weeks, more than 99% of reported messages pass SPF and DKIM, and all passing IPs are in your authorized list, the time has come. The safe path is: p=none to collect data, then p=quarantine with pct=25 for a while, and finally p=reject.
_dmarc.example.com. 3600 IN TXT "v=DMARC1; p=quarantine; pct=25; rua=mailto:dmarc@example.com; ruf=mailto:forensic@example.com; fo=1"
Don't underestimate the pct parameter. With pct=25, only a quarter of spoofed messages are quarantined, and if you've made a mistake, three-quarters of your healthy traffic still isn't rejected. This is a real safety net, not a formality.
The cost you have to accept: p=reject isn't reversible in the moment. If tomorrow you add a new service for sending and don't update SPF, its emails will be silently rejected and you'll only find out from user complaints. So every change in email infrastructure must be accompanied by a simultaneous change to the DMARC record.
Where to keep the reports
Don't throw away the raw XML. Reports are your only historical record when you want to figure out where an intrusion started. Keep them in the same place you keep your other logs and make sure they stay intact; the method is explained in What are audit logs and how do we keep them intact? If you lose your server and the reports are on that same server, you have no evidence.
Frequently asked questions
Where do I get a DMARC report from?
By adding rua=mailto:address to the domain's TXT record at _dmarc.example.com. Major receivers like Gmail and Outlook send the report to that address themselves, and you don't need to sign up anywhere. You just have to actually read that inbox, otherwise the reports pile up uselessly.
What's the difference between SPF, DKIM, and DMARC?
SPF says which IPs are allowed to send and is checked on the SMTP envelope. DKIM places a cryptographic signature on the headers and proves the message wasn't tampered with along the way. DMARC sits on top of those two and determines what the receiver should do if both fail: nothing, quarantine, or reject.
Why are my own emails rejected after p=reject?
It's almost always an authorized sender that got overlooked. Either SPF wasn't updated for a new service, or DKIM isn't configured correctly on a subdomain. First read the ruf reports, check the rejected IP with dig -x, and then temporarily revert the policy to p=quarantine until the problem is resolved.
How often should I check the reports?
Weekly in the first weeks, and after the policy stabilizes, once a month is enough. But every time your email infrastructure changes, look at the report again that same week. Changing the sending service without checking the report is the most common reason transactional emails stop working.
If you're still on p=none and don't know where to start, first collect reports for a week, list the passing IPs in a file, and then go for the policy change. If you'd prefer to hand off your email infrastructure and domain security to another team, ServerNet's security services cover this same path. But in any case, as long as you don't have your list of authorized IPs on paper, don't touch p=reject.
Comments 0
No comments yet — be the first!