Skip to main content
email·digit

SPF and DKIM pass but DMARC fails

DMARC fails when SPF and DKIM pass because they passed for someone else's domain: DMARC also requires the domain that passed to match the domain in your From: address, which is called alignment. The fix is a custom Return-Path (MAIL FROM) on a subdomain of yours, DKIM signing with your own domain, or both.

Three domains in every message

Every email carries up to three domains that matter here, and they do not have to be the same:

DomainWhere it livesWho checks it
Header FromThe From: line people seeDMARC, which compares the other two against it
Envelope FromThe Return-Path, where bounces goSPF
DKIM signing domainThe d= tag in the DKIM-Signature headerDKIM

SPF only tells you the sending server was allowed to send for the Return-Path domain. DKIM only tells you the message was signed by the d= domain and not changed. Neither looks at the From: line. DMARC is the check that ties them to the From: line: a message passes DMARC if SPF passes for a domain aligned with the From: domain, or DKIM passes for one.

Relaxed vs strict alignment

Your DMARC record sets how closely the domains must match, with aspf for SPF and adkim for DKIM. Both default to relaxed.

  • Relaxed (r): the domains must share the same organizational domain. bounce.example.com aligns with example.com.
  • Strict (s): the domains must be identical. bounce.example.com does not align with example.com.
_dmarc.example.com.  TXT  "v=DMARC1; p=quarantine; adkim=r; aspf=r; rua=mailto:dmarc@example.com"

Unless you have a specific reason, leave both on relaxed. It is what most setups need.

A worked example: pass, pass, fail

You send from news@example.com through an email service that runs on example.net. Out of the box, the service uses its own bounce domain and signs with its own DKIM domain:

CheckDomain checkedResultAligned with example.com?
SPFbounces.example.netpassNo
DKIMexample.netpassNo
DMARCexample.comfailNeither check aligned

Both checks are genuine passes, for example.net. Nothing proves example.com authorized the message, so DMARC fails. Under p=none it still delivers, and the failure appears in your DMARC reports. Under p=quarantine or p=reject it goes to spam or bounces, and Gmail can answer with 550 5.7.26.

Why services default to their own domains

A sending service needs the bounces to come back to it, so out of the box it puts its own domain in the Return-Path. It also cannot sign with your domain until you publish a key for it in your DNS, so until then it signs with its own. Neither default is a mistake on the service's part. They only stop working for you once a receiver checks DMARC, which since February 2024 Gmail and Yahoo require from bulk senders.

How to fix it

1. Sign DKIM with your own domain

This is the more important fix. In the sending service's settings, add your domain for DKIM. It gives you a record to publish, usually a CNAME or TXT under _domainkey. Once it is live, the service signs with d=example.com, which aligns.

s1._domainkey.example.com.  CNAME  s1.dkim.example.net.

DKIM alignment is the one that survives forwarding, because a forwarder changes the sending IP but an unmodified message keeps its signature.

2. Set a custom Return-Path (custom MAIL FROM)

Most services let you use a subdomain of yours as the bounce domain, often called a custom Return-Path or custom MAIL FROM domain. You publish the records they give you on that subdomain, typically an MX for bounces and an SPF record, or a single CNAME that points to them:

bounce.example.com.  MX   10 feedback.example.net.
bounce.example.com.  TXT  "v=spf1 include:_spf.example.net -all"

SPF is now checked against bounce.example.com, which aligns with example.com under relaxed alignment. Under strict SPF alignment it would not, which is one more reason to keep aspf=r.

One aligned pass is enough for DMARC, but doing both means a message still passes if one of them breaks.

How to confirm it worked

Send a test to an inbox you control and open the full headers. The receiving server records its verdict in the Authentication-Results header. Before the fix:

Authentication-Results: mx.example.org;
  dkim=pass header.d=example.net header.s=s1;        <- passed, but not your domain
  spf=pass smtp.mailfrom=bounces.example.net;        <- passed, but not your domain
  dmarc=fail header.from=example.com                 <- nothing aligned

After the fix:

Authentication-Results: mx.example.org;
  dkim=pass header.d=example.com header.s=s1;        <- d= matches From: exactly
  spf=pass smtp.mailfrom=bounce.example.com;         <- same organizational domain
  dmarc=pass header.from=example.com                 <- aligned, passes

Read it field by field: header.from is the From: domain, header.d is the DKIM signing domain, and smtp.mailfrom is the Return-Path domain SPF checked. If either of the last two shares the organizational domain of header.from and passed, DMARC passes. The arrows above are annotations for this guide; real headers do not include them.

To check your records in one place, run your domain through the free domain checker. It checks SPF, DKIM, DMARC and MX plus five blocklists, with no signup, and shows the record to publish for a missing or broken SPF or DMARC record. To see alignment across all the mail sent as you, read your aggregate reports: start with how to read a DMARC report. If you send through Email Digit, we manage authentication for you: you publish four DNS records once, then rotate keys in one click and step up DMARC when your reports say it is safe, without editing DNS again.