RCWRCW IT TrainingFree hands-on labs & simulators← Back to home
Microsoft 365 · Detailed troubleshooting guide

SPF, DKIM and DMARC: Diagnosing Why Legitimate Mail Is Being Rejected

SPF, DKIM and DMARC fail in distinct and identifiable ways, and the header of a rejected message usually names which. This guide covers reading those headers, the alignment rule that catches everyone out, and the limits that silently break a correct-looking SPF record.

Published October 6, 2026 · By , Enterprise Infrastructure Architect

Read the Authentication-Results header first

Every receiving system that performs these checks records the outcome in a header. It is the fastest route to a diagnosis and it removes all guesswork about which mechanism failed.

Authentication-Results: mx.google.com;
  spf=fail (google.com: domain of bounce@marketing.example.com does not
      designate 198.51.100.7 as permitted sender) smtp.mailfrom=marketing.example.com;
  dkim=pass header.i=@example.com header.s=selector1;
  dmarc=fail (p=REJECT sp=REJECT dis=REJECT) header.from=example.com

Three separate verdicts, and they can disagree. Here SPF failed, DKIM passed, and DMARC still failed — which seems contradictory until alignment is understood.

In Microsoft 365, the equivalent summary appears in Authentication-Results plus a composite header:

X-Forefront-Antispam-Report: CIP:198.51.100.7;CTRY:US;SFV:SPM;
  SCL:5;SRV:;IPV:NLI;H:mail.example.com;DIR:INB;

Obtain the full headers before theorising: in Outlook, File → Properties → Internet headers; in Gmail, Show original.

SPF: the ten-lookup limit breaks correct records

SPF publishes which hosts may send for a domain, and it validates the envelope sender (MAIL FROM), not the visible From: address. This distinction is the root of much confusion.

dig +short TXT example.com | grep spf
# "v=spf1 include:spf.protection.outlook.com include:_spf.google.com
#   include:sendgrid.net include:mail.zendesk.com ~all"

Each include, a, mx, ptr and exists consumes DNS lookups, and the specification caps the total at ten. Crucially, includes nest: include:spf.protection.outlook.com may itself perform several. Exceed ten and the result is permerror, which most receivers treat as a failure — the record looks perfect and does not work.

# count the real total, including nested includes
dig +short TXT spf.protection.outlook.com
dig +short TXT _spf.google.com

Remedies, in order of preference: remove services no longer used; move sources onto subdomains with their own records (mail.example.com); use SPF flattening only with automation, since flattened records break silently when a provider changes its IP ranges.

The qualifier at the end sets the policy for everything not listed:

QualifierResultTypical handling
-allfail (hard)Reject
~allsoftfailAccept, mark suspicious
?allneutralNo opinion — effectively no protection
+allpass everythingNever use; authorises the entire internet

Publish exactly one SPF record. Two TXT records beginning v=spf1 produce permerror under the specification — a frequent outcome when a second mail provider is onboarded by a different team.

DKIM: selectors, key length and body hash

DKIM signs headers and body with a private key; the receiver fetches the public key from DNS at <selector>._domainkey.<domain>. The selector is named in the signature itself:

DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=example.com;
  s=selector1; h=from:to:subject:date; bh=47DEQpj8HBSa...; b=Xk3p...
dig +short TXT selector1._domainkey.example.com
# "v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A..."

Distinguish the two hashes, because they fail for different reasons:

  • bh= is the body hash. A bodyhash did not verify error means the body changed in transit — a mailing list footer, a scanning gateway rewriting links, or a disclaimer appliance.
  • b= is the signature over the headers. Failure here means a signed header was altered or the key does not match.

Use relaxed canonicalisation (c=relaxed/relaxed) unless you have a specific reason not to; strict canonicalisation fails on trivial whitespace changes that intermediate systems make routinely.

In Microsoft 365, DKIM uses two selectors with CNAMEs so keys can be rotated without a DNS change:

dig +short CNAME selector1._domainkey.example.com
dig +short CNAME selector2._domainkey.example.com
# selector1-example-com._domainkey.tenant.onmicrosoft.com
Get-DkimSigningConfig -Identity example.com | Format-List
Rotate-DkimSigningConfig -Identity example.com -KeySize 2048

Use 2048-bit keys. 1024-bit keys are still accepted but are increasingly treated as weak, and some receivers now discount them.

DMARC alignment — the rule that explains the contradictions

DMARC does not simply ask whether SPF or DKIM passed. It asks whether either passed for a domain that matches the From: header domain. That is alignment, and it is where otherwise-correct configurations fail.

dig +short TXT _dmarc.example.com
# "v=DMARC1; p=reject; rua=mailto:dmarc@example.com; pct=100; adkim=s; aspf=s"
TagMeaning
p=Policy: none, quarantine or reject
sp=Policy for subdomains
adkim=DKIM alignment: r relaxed (default) or s strict
aspf=SPF alignment: r relaxed or s strict
pct=Percentage of mail the policy applies to
rua=Address for aggregate XML reports

Relaxed alignment accepts an organisational-domain match: mail.example.com aligns with example.com. Strict alignment requires an exact match. Setting aspf=s while a bulk provider sends with an envelope domain of bounce.provider.net guarantees DMARC failure however valid the SPF record is.

Work through the earlier example with alignment in mind:

  • SPF checked marketing.example.com (envelope) — failed.
  • DKIM passed for d=example.com.
  • From: header is example.com, so DKIM is aligned and DMARC should pass.

If DMARC nonetheless reports failure, the usual cause is that the DKIM signature broke in transit while the header still records the attempt — check for bodyhash did not verify rather than assuming the policy is wrong.

Forwarding and mailing lists break SPF by design

When a message is forwarded, the forwarding server becomes the sender. SPF then evaluates that server against the original domain's record and fails — correctly, by the specification's own logic. Nothing is misconfigured.

This is the principal reason to rely on DKIM for DMARC alignment: the signature travels with the message and survives forwarding, provided the body is not altered.

  • Simple forwarding — SPF fails, DKIM survives, DMARC passes on DKIM alignment.
  • Mailing lists — subject prefixes and footers are added, breaking the body hash, so DKIM fails too. Well-behaved lists rewrite the From: header to their own domain to avoid this.
  • Sender Rewriting Scheme (SRS) — rewrites the envelope sender so SPF passes at the next hop; supported by Microsoft 365 for mail forwarded out of the service.

Before moving to p=reject, confirm from aggregate reports that legitimate forwarded mail still aligns on DKIM. Deploying reject while list traffic depends on SPF is the classic way to lose real mail.

Deploy DMARC in stages

Never publish p=reject first. The aggregate reports are the point of the exercise — they reveal legitimate senders nobody remembered.

  1. Monitor. v=DMARC1; p=none; rua=mailto:dmarc@example.com. Collect for at least two to four weeks.
  2. Read the reports. They are XML; use a parser or a reporting service. Identify every source sending as your domain.
  3. Fix or remove each source. Add legitimate ones to SPF, enable DKIM signing, decommission the rest.
  4. Quarantine gradually. p=quarantine; pct=10, then raise the percentage as reports stay clean.
  5. Reject. p=reject; pct=100, with sp=reject so subdomains are covered.
# publish a null MX and DMARC reject on domains that never send mail
dig +short MX parked.example.com
# 0 .
dig +short TXT _dmarc.parked.example.com
# "v=DMARC1; p=reject;"

Non-sending domains are a favourite target for spoofing precisely because they are forgotten. A reject policy and a null MX cost nothing and remove the opportunity.

Checklist

  1. Read Authentication-Results before changing any DNS record.
  2. Count SPF DNS lookups including nested includes; over ten is permerror.
  3. Confirm exactly one v=spf1 TXT record exists.
  4. Check the DKIM selector named in the signature actually resolves.
  5. Distinguish bh= failures (body altered) from b= failures (headers or key).
  6. Verify DMARC alignment, not just pass — compare the From: domain with the SPF and DKIM domains.
  7. Expect SPF to fail on forwarded mail; rely on DKIM alignment there.
  8. Move through none → quarantine → reject on evidence from aggregate reports.
Key takeaway: DMARC requires alignment, not merely a pass: the domain that passed SPF or DKIM must match the From: header domain. That single rule explains most failures where SPF and DKIM both show pass and DMARC still fails. Watch the ten-lookup SPF limit, and prefer DKIM, which survives forwarding.