Domain Health · DMARC · Reading reports

SPF fail, DMARC pass: reading a DMARC report row

Your first DMARC aggregate report arrives, and one line says SPF fail but DMARC pass. That's usually fine, sometimes a clue, and occasionally a sender you need to fix. Here's how to tell which, field by field.

Try an example:

The row

Aggregate reports are XML files (usually zipped) that receivers such as Gmail send to the address in your DMARC record's rua= tag, normally once a day. Each <record> groups messages from one sending IP. Here's the pattern, simplified (the IP is a documentation example):

<row>
  <source_ip>192.0.2.25</source_ip> <count>1</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><result>pass</result></dkim>
  <spf><domain>example.com</domain><result>softfail</result></spf>
</auth_results>

Our own first report from Google, for mistermarbi.com, had exactly this shape: one message, DKIM pass, SPF fail, DMARC pass, disposition none.

Which fields matter

  • policy_evaluated is the DMARC verdict. Its dkim and spf values mean "passed and aligned with the From domain", not just "passed".
  • auth_results is the raw result: which domain SPF and DKIM were checked for, and what they returned. Compare the domains there with header_from.
  • disposition is what the receiver did: none, quarantine or reject. With p=none it's always none, whatever the result.

Why DMARC passed

DMARC needs one of SPF or DKIM to pass and align with the From domain, not both. In the row above, DKIM passed for example.com, the same domain as header_from, so DMARC passes even though SPF failed. That's DKIM doing its job.

The four combinations

  • DKIM pass, SPF pass → healthy.
  • DKIM pass, SPF fail (this page) → DMARC passes. Usually forwarding, or a sender that isn't in your SPF record but signs with your key. See below to tell them apart.
  • DKIM fail, SPF pass → DMARC passes on SPF alone. Fragile: it'll fail as soon as the message is forwarded. Turn on DKIM signing for that sender.
  • DKIM fail, SPF fail → DMARC fails. Either a real sender of yours is misconfigured, or someone is spoofing your domain. This is the row to act on.

Forwarding, or a sender you forgot?

Look up who owns source_ip (a reverse DNS lookup, or a WHOIS/IP lookup tool), then:

  • It belongs to a mailbox or list provider, the count is small, and DKIM passes → almost certainly forwarding: a recipient's mailbox automatically forwards to Gmail, or a mailing list re-sends your message. The forwarder's IP isn't in your SPF record (and shouldn't be), so SPF fails; your DKIM signature survives the trip. Nothing to fix.
  • It belongs to a service you use (a newsletter tool, store platform, help desk), or the count is large and recurring → it's one of your senders. If DKIM already passes, you can leave SPF alone or add the service's include, but watch the 10-lookup limit. If it later shows DKIM fail too, fix it before you tighten your policy.
  • You don't recognise it and DKIM fails as well → likely spoofing. That's what p=quarantine or p=reject stops, once your real senders all pass.

Before you tighten your policy

Rows like this one are why you collect reports for a few weeks on p=none first. When every source you recognise shows DMARC pass (with DKIM pass on each), moving to p=quarantine won't affect your real mail, including forwarded copies, because those pass on DKIM. If a recognised sender passes only on SPF, add DKIM for it first.

Quick reference

  • policy_evaluated = aligned result (what DMARC used). auth_results = raw result.
  • DMARC pass needs one aligned pass: SPF or DKIM.
  • SPF fail + DKIM pass from a provider IP, at low volume: forwarding, usually harmless.
  • Both fail: investigate before moving past p=none.

Related: DMARC checker · SPF checker · DKIM checker · Pre-holiday sending check