dotvitals

Common DMARC failures explained

Updated ·12 min read

The commonest DMARC question is why a message with spf=pass and dkim=pass in its headers was still reported as a DMARC failure. The answer is that DMARC does not ask whether SPF or DKIM passed. It asks whether either of them passed for a domain that matches the one a recipient actually sees in the From header. That extra question is called alignment, and nearly every DMARC failure is an alignment failure rather than an authentication failure.

Once you know that, the failures sort into a small number of classes with different owners. Some are yours to fix in DNS or on a sending platform. Some belong to a forwarder or a mailing list and are not fixable from your side at all — and knowing which is which is the difference between a clean rollout and an indefinite one.

DMARC was re-specified in May 2026 as RFC 9989, which obsoletes RFC 7489, with aggregate reporting split into RFC 9990 and failure reporting into RFC 9991. Two things changed that matter for diagnosis: the organizational domain is now found by walking the DNS tree rather than by consulting a public suffix list, and the pct tag was removed. Older articles describing either will lead you astray.

Check yours now

A pass is not a DMARC pass

SPF and DKIM each validate a domain, but not the domain a human reads.

SPF is evaluated against the envelope sender, the address in the SMTP MAIL FROM command, which is also called the return-path or RFC5321.MailFrom. A recipient never sees it. On most hosted sending platforms it belongs to the platform, not to you: a bounce address at the provider's own domain is the default, and it is the domain SPF passes for.

DKIM validates the domain in the d= tag of the signature it verified. That is whichever domain holds the private key that signed the message, which again is often the provider's rather than yours unless you have set up a key for your own domain.

DMARC then compares the domain that passed against the domain in the visible From header, the RFC5322.From. If at least one of the two matches under the alignment rules, DMARC passes. If both passed for someone else's domain, DMARC fails, and everything in the message headers still says pass.

This is the header pattern to recognise. Read it as: SPF passed, for a domain that is not yours; DKIM passed, for a domain that is not yours; therefore DMARC failed.

Both mechanisms pass and DMARC still fails
Authentication-Results: mx.receiver.example;
        spf=pass (sender IP is 192.0.2.25)
            smtp.mailfrom=bounces.sender-platform.example;
        dkim=pass header.d=sender-platform.example header.s=s1;
        dmarc=fail (p=REJECT) header.from=example.com

From: Billing <billing@example.com>

# spf passed for bounces.sender-platform.example
# dkim passed for sender-platform.example
# the From domain is example.com
# nothing aligned, so DMARC failed

Relaxed and strict alignment, and where the organizational domain comes from

Two tags control how exact the match has to be, and both default to relaxed. adkim governs DKIM alignment, aspf governs SPF alignment, and each takes r for relaxed or s for strict.

Strict alignment requires the two domains to be identical. Relaxed alignment requires them to have the same organizational domain, which is what lets mail From billing@example.com be aligned when it was signed by mail.example.com.

The important change in RFC 9989 is how the organizational domain is found. Section 4.10 defines a DNS tree walk: the receiver starts at the author domain and works up the tree, querying for policy records as it goes, capped at eight DNS queries — an author domain with more than eight labels has labels removed in groups so that the budget is not exceeded, which exists to stop a domain with hundreds of labels being used as a denial-of-service lever. Section 4.10.2 then picks the organizational domain from what the walk found: a valid policy record carrying psd=n identifies that domain as the organizational domain, and a record carrying psd=y identifies the domain one level below it.

This is not a public suffix list lookup and it is not a fallback to one. RFC 7489 mandated no particular list and left the choice to the receiver, which meant two receivers could disagree about where your organizational boundary was; RFC 9989 replaced that with a discovery technique defined in DNS. The practical consequence is that your organizational domain now depends on what is published under your name and above it, which is something you can observe rather than something you have to infer from a list.

You can also see which method a given receiver used. Aggregate reports under RFC 9990 carry a discovery_method element in policy_published whose value is psl or treewalk, so a mixed estate of receivers is visible rather than guesswork.

Leave both tags relaxed unless you have a specific reason not to. Strict alignment breaks the standard and useful pattern of giving each bulk sender its own subdomain, and turning it on is a frequent cause of legitimate mail failing after a rollout that had otherwise been clean.

Forwarding breaks SPF and leaves DKIM alone

A plain forward — a university alias, a role address that redirects, a personal rule that sends everything to another mailbox — takes your message and re-sends it from the forwarder's server. RFC 7960 documents what that does to each mechanism.

If the forwarder keeps your envelope sender, SPF is evaluated for your domain against the forwarder's IP address, which is not in your SPF record, so SPF fails. If the forwarder rewrites the envelope sender to its own domain, which is what Sender Rewriting Scheme implementations do, SPF passes — but for the forwarder's domain, which does not align with your From header. Either way, the SPF route to a DMARC pass is closed by an ordinary, entirely legitimate forward.

DKIM is unaffected. The signature covers header fields and the message body, not the path the message took, so a forwarded message that was not modified still verifies against your key and still aligns with your From domain. DMARC passes on the DKIM side and the SPF failure is irrelevant, because DMARC needs only one of the two.

That asymmetry is the strongest practical argument for DKIM. A domain that relies on SPF alone will see legitimate mail fail DMARC whenever a recipient forwards it, and there is nothing in your own configuration that can prevent that. A domain that signs everything with an aligned key does not care.

In an aggregate report this class looks like a source you do not recognise, with spf fail, dkim pass, and disposition none. That is a DMARC pass. It is not a problem and it does not need fixing.

Mailing lists, where DKIM breaks too

Mailing lists are the hard case, because they modify the message. RFC 7960 enumerates the modifications that break a DKIM signature: prepending a tag to the subject line, appending a footer with subscription information, converting MIME parts or flattening to text, stripping attachments, and rewriting content. Any one of them changes a signed header or the body hash, and the signature no longer verifies.

So a list breaks DKIM by altering the message and breaks SPF by re-sending it. Both routes to a DMARC pass are gone at once, which is why list mail from a domain at p=reject used to be rejected outright for every subscriber.

The response most list software adopted is to rewrite the From header to the list's own domain, putting the original author into Reply-To or into the display name. Once it does that, the message is From the list, the list signs it with its own key, and DMARC passes for the list. Your policy is no longer involved. This is ugly, it breaks threading conventions and it annoys people, and it is the reason list mail works at all for enforcing domains.

What you should not do is weaken your own policy because of a mailing list. If the list rewrites From, your policy is already irrelevant to it. If the list does not rewrite From, the mail will fail for its subscribers, and the thing that changes that is the list's configuration, not yours — raise it with the list operator. Publishing p=none so that one list works leaves every one of your users exposed to impersonation in exchange.

In reports, list traffic shows as a source that is the list's server, with dkim fail and spf either failing or passing unaligned. Where a receiver has recognised the traffic as list mail it may record a policy override, and the reason value defined for that case is mailing_list.

Subdomains: sp, np, and records that override each other

A subdomain with no DMARC record of its own is covered by the tree walk, which finds the first policy record above it. Which value applies depends on the tags in that record: sp, when present, sets the policy for subdomains and overrides p for them; np, introduced in RFC 9989, sets the policy for subdomains that do not exist at all.

Three failure patterns come out of this.

  • sp=none inherited from a copied template. The apex reads as enforcing, every subdomain is exempt, and nothing about the record looks wrong. Our own checker reports this combination, because it reads as protection and is not.
  • A subdomain publishing its own record. A record at the subdomain replaces the inherited policy completely, including the reporting addresses. A forgotten v=DMARC1; p=none on a subdomain from an old rollout exempts it from the apex policy and sends its reports nowhere, and it will not appear in the apex domain's reports.
  • No np tag on a domain whose subdomains are not used for mail. A non-existent subdomain can never have legitimate mail, so np=reject is safe to publish immediately, at any stage of a rollout, and it closes the most common spoofing pattern of all.

Relaxed alignment is what makes the per-sender subdomain pattern work: mail From your apex, signed by or sent from a subdomain, is aligned. That is the standard way of keeping bulk senders inside the SPF ten-lookup limit, and it is also the reason strict alignment breaks so much.

Third-party senders that send as your domain

The invoicing system, the helpdesk, the CRM, the marketing platform, the survey tool, the monitoring system, the e-commerce back end. Each of them sends mail with your domain in the From header, and each is its own alignment problem.

There are two ways to make one align, and they are not equivalent.

  • Give the platform a DKIM key for your domain, with its own selector, and have it sign with d= set to your domain. This is the route that survives forwarding, it costs no SPF lookups, and it is the one to prefer wherever the platform supports it.
  • Add the platform to your SPF record and configure it to use an envelope sender inside your domain. Both halves are needed. Many platforms will happily be added to your SPF record while continuing to use their own bounce domain, in which case SPF passes for them and aligns with nothing.

That second half is the trap. A platform's dashboard showing a green authentication tick is reporting its own authentication, which is a different question from whether anything aligns with your From domain. The only way to settle it is to send a message and read the Authentication-Results header at the receiving end, or to wait for the source to appear in an aggregate report.

Keep an eye on the SPF budget while you do this. Every include costs at least one DNS lookup and the cost is transitive, so a provider whose own record contains three includes costs you four. RFC 7208 caps the total at ten and requires a permerror beyond it, which removes SPF entirely. If adding a sender would take you near the limit, give it a subdomain with its own SPF record instead; relaxed alignment means it still aligns with your apex From address.

ARC: what it does, and what it does not buy you

Authenticated Received Chain, RFC 8617, exists because of the forwarding and mailing-list problem above. It lets an intermediary record what it saw before it modified the message, and seal that record so a later receiver can check it has not been tampered with.

Each participating hop adds three header fields. ARC-Authentication-Results records the authentication assessment as the intermediary determined it at message arrival. ARC-Message-Signature lets the intermediary convey some responsibility, or custodianship, for the message as it passes it on. ARC-Seal lets later handlers verify the integrity of the preceding chain.

A valid chain is evidence of three things, in the RFC's own framing: that the sealing domain emitted the message with this body, that the authentication assessment reported in the ARC-Authentication-Results was determined on receipt, and that the preceding chain existed on the message.

Now the part that gets overstated. ARC obliges nobody to deliver anything. RFC 8617 states that a message with a chain validation status of fail must be treated the same as a message with no chain at all, and where it describes a handler using a chain to inform a DMARC decision it says the handler may choose to accept the assessments — may, not must. A chain is trustworthy to the exact extent the receiver already trusts the domain that sealed it, which means a large receiver honouring a large mailing list's chain is common and a small forwarder's chain being honoured is not.

For a domain owner the practical position is simple: you do not deploy ARC. Intermediaries do. Your contribution is to DKIM-sign every message with an aligned key, so that when an intermediary records what it saw on arrival, there is a pass for it to record. ARC does not override your policy, does not repair a broken signature, and is not a reason to delay enforcement.

Reading an aggregate report to find the failing source

Aggregate reports are the only view you get of mail claiming to be your domain, and RFC 9990 defines their shape. They arrive roughly daily, one per reporting receiver, as gzipped XML attached to a message whose subject begins Report Domain: followed by your domain and the submitter.

Each report opens with report_metadata, naming the reporting organisation, a report identifier and the date range, and then policy_published, which is the receiver telling you what it found when it looked up your record: domain, p, sp, np, adkim, aspf, and the discovery_method it used to locate your organizational domain.

Then one record per sending source. The diagnostic technique is to read two places and compare them. policy_evaluated gives you the verdict — disposition, and dkim and spf results that are the alignment results, not the raw mechanism results. auth_results gives you the raw evidence: for DKIM the signing domain and selector and the verification result, for SPF the domain checked and its result.

The comparison is what identifies the class of failure. A dkim result of pass in auth_results alongside a dkim result of fail in policy_evaluated means the signature verified perfectly for the wrong domain: an alignment problem, not a key problem, and looking at your DKIM record will tell you nothing. An spf pass in auth_results with a domain belonging to a sending platform, and spf fail in policy_evaluated, is the third-party sender case above.

policy_evaluated can also carry a reason element, present when the receiver applied something other than your published policy. RFC 9990 defines five values: local_policy, mailing_list, other, policy_test_mode and trusted_forwarder. Note what is not in that list. The forwarded and sampled_out reasons that older documentation describes are not defined in the current specification, and sampled_out went with the pct tag it existed to explain.

For a small domain the reports are readable by hand, and doing that once is worth it because it builds the model. Beyond a handful of receivers, use a report processor; the volume becomes unmanageable quickly.

One record from an aggregate report, annotated
<record>
  <row>
    <source_ip>192.0.2.25</source_ip>
    <count>418</count>
    <policy_evaluated>
      <disposition>none</disposition>
      <dkim>fail</dkim>   <!-- ALIGNMENT result, not the signature result -->
      <spf>fail</spf>     <!-- ALIGNMENT result, not the SPF result -->
    </policy_evaluated>
  </row>
  <identifiers>
    <header_from>example.com</header_from>
  </identifiers>
  <auth_results>
    <dkim>
      <domain>sender-platform.example</domain>
      <selector>s1</selector>
      <result>pass</result>       <!-- the signature verified... -->
    </dkim>
    <spf>
      <domain>bounces.sender-platform.example</domain>
      <result>pass</result>       <!-- ...and so did SPF... -->
    </spf>
  </auth_results>
</record>

<!-- ...but both for the platform's domain, and header_from is
     example.com, so neither aligned and DMARC failed. The fix is a
     DKIM key for example.com on that platform, not a DNS change. -->

Fixing each class

Once a source is identified, the remedy follows from which class it falls into.

  • Your own mail server, failing both. Confirm the address is in your SPF record and that DKIM signing is enabled with a selector published in your DNS. This is the ordinary case and it is a configuration error.
  • A third-party platform, both passing for the platform's domain. Set up a DKIM key for your domain on that platform. If the platform cannot do that, add it to SPF and configure it to use an envelope sender in your domain, and verify with a test message that it actually does.
  • A forwarder, spf fail and dkim pass. Nothing to do. This is a DMARC pass and it is what DKIM is for.
  • A forwarder, both failing, where the mail was modified in transit. Check whether your own outbound path is altering messages after signing — a gateway appending a disclaimer, a virus scanner rewriting MIME parts, a Sieve rule adding a header. This is an own-goal that looks identical to a third-party modification in the report.
  • A mailing list. Not yours to fix. If the list rewrites From, it is already handled; if it does not, raise it with the list operator rather than lowering your policy.
  • A source you do not recognise, sending volume, failing everything, from addresses in no way connected to you. That is the spoofing DMARC exists to stop, and the correct response is to finish moving to enforcement rather than to investigate the sender.

When you move the policy up, move the policy. Do not reach for the pct tag to stage it: RFC 9989 removed the tag, deployed receivers are split between honouring it and ignoring it, and a record carrying it has no predictable meaning. The staging tools that exist now are the policy level itself, sp and np to split the apex from subdomains, and t=y as a deliberately dated step in which receivers apply one level below what you publish.

Verifying that a fix worked

Three checks, in order, and none of them substitutes for the others.

Send a message from the sender you changed to a mailbox you control at an external provider, and read the Authentication-Results header at the receiving end. It names the DMARC result and the From domain it evaluated against, which is the single fastest confirmation that alignment now holds for that sender.

Run the record through the DMARC checker, which resolves every tag including the defaults you did not write. Reading back the effective policy, defaults included, catches the class of mistake where the record is valid and does something other than what you meant — and where a t=y tag is present, it reports the policy receivers will actually apply rather than the one the record appears to state.

Then wait for the reports. One clean test message proves one path; the reports prove the estate. A record is correct when the aggregate reports from several receivers show every legitimate source passing in alignment, and the only remaining failures are forwarders passing on DKIM and sources you do not recognise. That is the evidence you need before raising the policy, and there is no substitute for it.

Reading back what you published, defaults included
dig +short TXT _dmarc.example.com

# expect exactly one record starting v=DMARC1
# two records means receivers apply no policy at all

# and check the subdomain separately: a record here replaces the
# inherited policy completely, reporting addresses included
dig +short TXT _dmarc.mail.example.com

What commonly goes wrong

  • Reading spf=pass in a header and concluding DMARC will pass. SPF passes for the envelope sender, which on a hosted platform is usually the platform's own bounce domain, and a pass for someone else's domain aligns with nothing.
  • Adding a platform to SPF and stopping there. Half the change authorises the platform's servers; the other half, making the platform use an envelope sender in your domain, is the half that produces alignment, and many platforms do not do it by default.
  • Turning on strict alignment as a hardening step. It breaks per-sender subdomains, which are the standard way out of the SPF lookup limit, and it is a common cause of a clean rollout failing a week later.
  • Lowering the policy because a mailing list fails. If the list rewrites the From header your policy is not involved; if it does not, you have traded protection for every user against one list's configuration.
  • Expecting ARC to rescue a failing source. A chain with a validation status of fail must be treated as no chain at all, and even a valid one only lets a receiver choose to accept an intermediary's assessment. It overrides nothing.
  • Leaving sp=none in a record copied from a template, or leaving an old p=none record on a subdomain. Both read as protection and provide none, and neither shows up in the apex domain's reports.
  • Moving to p=reject without ever reading an aggregate report. The reports exist to tell you what will break; skipping them does not remove the risk, it moves the discovery to after the mail has stopped.

Check your domain with the dmarc checker