← blog

authentication · 13 Sept 2026 · 4 min read

Reading DMARC aggregate reports, and getting from p=none to p=reject

A DMARC policy at p=none protects nothing, but it is the only way to get the reports that tell you what is being sent as your domain. What is inside an aggregate report, how to read it, and the sequence that gets a domain to enforcement without losing legitimate mail.

DMARC aggregate reports are XML files receivers send daily listing every source IP that sent mail as your domain, how many messages, and whether SPF and DKIM passed and aligned. Read them at p=none for two to four weeks to inventory your senders, fix each source that fails alignment, then move to p=quarantine and p=reject. The waiting is for the inventory, not for its own sake.

Three signal beams converging; two meet the target, the third misses below it.

Publishing a DMARC record at p=none is where most domains stop, and it is understandable: the record exists, the checkers go green, and nothing broke. But p=none asks receivers to do nothing when a message fails. Anyone can still send as your domain, and receivers have been told not to mind.

The reason to publish p=none anyway is the reports. They are the only view you will ever get of what the world is sending with your name on it, and they are how you get to a policy that actually protects the domain without breaking your own mail on the way.

What the record needs

_dmarc.yourapp.com.   TXT   "v=DMARC1; p=none; rua=mailto:dmarc-reports@yourapp.com"

rua is the address aggregate reports go to. Make it a mailbox that can receive attachments from strangers and that somebody, or something, reads. If the address is on a different domain from the one publishing the record, that domain must publish a small record authorising it, which is a detail that silently stops reports arriving when it is missed:

yourapp.com._report._dmarc.reports.example.   TXT   "v=DMARC1"

Reports start arriving within about a day from every large receiver that saw your mail.

What is inside a report

Each one is a compressed XML file covering roughly 24 hours from one receiver. Strip away the wrapping and the useful part is a list of records, one per source IP and outcome:

<record>
  <row>
    <source_ip>203.0.113.10</source_ip>
    <count>1840</count>
    <policy_evaluated>
      <disposition>none</disposition>
      <dkim>pass</dkim>
      <spf>fail</spf>
    </policy_evaluated>
  </row>
  <identifiers>
    <header_from>yourapp.com</header_from>
  </identifiers>
  <auth_results>
    <dkim><domain>yourapp.com</domain><result>pass</result></dkim>
    <spf><domain>bounce.provider.example</domain><result>pass</result></spf>
  </auth_results>
</record>

Read it from the bottom up. auth_results shows what raw SPF and DKIM said and which domain each authenticated. policy_evaluated shows the DMARC view of the same message: whether each one passed and aligned with header_from.

That record above is the most common pattern you will see. SPF passed in auth_results, because the provider’s bounce domain is authorised, but policy_evaluated says spf=fail, because bounce.provider.example does not align with yourapp.com. DKIM aligned, so the message passed DMARC overall. It is fine today. It is also one broken DKIM key away from failing, which is worth fixing while nothing is on fire.

A record where both policy_evaluated values are fail is either a sender you have not configured or someone else entirely. The source_ip and its reverse DNS tell you which.

Do not read the XML by hand

One receiver, one day, one file is readable. Twenty receivers over a month is not. Feed the reports into any DMARC report parser, of which there are several free ones, and look at the aggregated view: source by source, volume, pass rate for SPF alignment and DKIM alignment separately.

What you are building is an inventory. For every source that sends as your domain:

  1. What is it? Resolve the IP, look at the reverse DNS, match it to a service.
  2. Is it yours? If not, it goes in the “will be rejected” column, which is the whole point.
  3. If it is yours, does DKIM align? If not, configure the service to sign with your domain. This is usually a CNAME they give you.
  4. Does SPF align? If not, set up the service’s custom return-path if it offers one. If it does not, DKIM alignment alone is sufficient, but note it.

Two to four weeks of reports catches the senders that only run monthly. That is what the waiting period is for. If your inventory is complete in a week, you do not need to wait three more.

Moving to enforcement

Once every legitimate source shows DKIM aligned, change the policy in steps:

p=quarantine; pct=25

pct applies the policy to a fraction of failing mail, so a source you missed shows up as a partial dip rather than a total loss. Watch the reports for a few days, raise pct, and then:

p=quarantine; pct=100

then

p=reject

At p=reject, receivers discard mail that fails, and the reports keep telling you what was discarded. The spoofing attempts that were reaching inboxes at p=none now do not, and you can see how many there were.

Subdomains

By default the policy on yourapp.com also covers mail.yourapp.com and every other subdomain that does not publish its own record. If you want subdomains treated differently, sp= sets a separate policy for them. The common use is the opposite of lenient: sp=reject on the root with p=quarantine while the root itself is still being cleaned up, because no subdomain should be sending unauthenticated mail at all.

Two settings to leave alone

adkim=s and aspf=s switch alignment to strict mode, requiring an exact domain match rather than allowing subdomains. The default relaxed mode is right for almost everyone. Strict mode is a way to make a correctly configured mail.yourapp.com fail alignment with yourapp.com, and there are few situations where that is the goal.

Where Pharos fits

Mail sent through Pharos signs with your sending domain and bounces to a return path on your sending domain, so in the aggregate reports it shows as both DKIM and SPF aligned. Your inventory work is finding the other sources, and the reports are where you find them. A DMARC record is one of the four Pharos verifies before a domain can send, so a domain with no record at all does not get as far as the first message.

Questions this raises

What is the difference between rua and ruf?

rua is the address for aggregate reports, daily XML summaries by source IP that every major receiver sends. ruf is for forensic reports, copies of individual failing messages, which most receivers no longer send for privacy reasons. Set rua. Setting ruf costs nothing but expect little.

How long should I stay at p=none?

Long enough to see every sender you have, which includes the monthly invoice run and the quarterly statement. Two to four weeks covers most organisations. If the reports show every source aligning, there is no reason to wait longer.

Will p=reject stop forwarded mail from arriving?

Not if DKIM is aligned. Forwarding breaks SPF because the forwarder's server is not in your record, but a DKIM signature survives an unmodified forward, and DMARC passes on either. This is why DKIM alignment comes first. Mailing lists that rewrite the body are the exception, and they generally rewrite the From address to compensate.

Sources

Written by the Pharos team · 13 Sept 2026 rss

Check your own headers first.

If authentication is failing, Pharos fixes it as a side effect of onboarding a domain. Free plan, 3,000 emails a month.

start free