← Blog/Authentication

How to find and fix SPF, DKIM and DMARC errors

October 15, 2026·7 min read·By MailPilot

Start with the receiving server's own verdict. Send a message to a mailbox on the provider that is filtering you, open it, and read the Authentication-Results header. In Gmail that is the three-dot menu then *Show original*; in Outlook it is *File*, *Properties*, *Internet headers*.

Authentication-Results: mx.google.com;
  spf=softfail (google.com: domain of bounce@mail.example.com does not designate 203.0.113.9 as permitted sender)
  dkim=neutral (body hash did not verify) header.i=@example.com
  dmarc=fail (p=NONE sp=NONE dis=NONE) header.from=example.com

That header names the failing domain, the failing IP and the failing mechanism. Everything below is how to act on each verdict.

Locate the records before you edit anything

dig +short TXT example.com                      # SPF lives in the apex TXT records
dig +short TXT selector1._domainkey.example.com # DKIM, one lookup per selector
dig +short TXT _dmarc.example.com               # DMARC
dig +short MX example.com

Add @8.8.8.8 to query a public resolver instead of your local one — handy when your registrar's changes have not propagated. On Windows, nslookup -type=txt _dmarc.example.com.

SPF failures

No SPF record found. Nothing in the apex TXT set begins with v=spf1. Publish one record naming every service that sends with your domain in the envelope:

v=spf1 include:_spf.google.com include:spf.protection.outlook.com ~all

Use ~all (softfail) while you are still discovering senders and -all (hardfail) once DMARC reports show nothing legitimate is missing.

Two SPF records on the same domain. This is a specification violation, not a merge: receivers return PermError and SPF stops evaluating. It happens when two teams each add a record. The fix is to combine the mechanisms into one TXT record and delete the other:

v=spf1 include:_spf.google.com include:mailgun.org ~all

One v=spf1 record per domain, always. Multiple DKIM selectors are fine; multiple SPF records never are.

PermError: too many DNS lookups. SPF allows a maximum of 10 DNS-querying mechanisms per evaluation. include, a, mx, ptr, exists and redirect each count, and every include recursively counts the lookups inside it — which is why four ESP includes can blow the budget on their own. There is also a limit of two "void" lookups (queries that return nothing), so stale includes pointing at dead services are worse than useless. To fix it:

  • Remove includes for services you no longer use. This is usually enough.
  • Delete a and mx mechanisms if those hosts do not send mail. They are the most common dead weight.
  • Delete any ptr mechanism. It is deprecated, slow and some receivers ignore it.
  • Move a service to its own subdomain (mg.example.com for Mailgun) and give that subdomain its own SPF record, with its own fresh budget.
  • Only then consider flattening includes into IP ranges — and if you do, you have accepted responsibility for tracking a provider's IP changes forever.

Sending service missing from the record. SPF checks the envelope sender (the MAIL FROM/Return-Path), not the visible From. If a provider sends with its own bounce domain, adding its include to your record changes nothing. Confirm what the envelope actually is from the Return-Path header before you edit DNS.

DKIM failures

No DKIM record published. dkim=none means no signature at all. Generate a key in your sending platform, publish the TXT record at <selector>._domainkey.example.com, and turn signing on — the publish-then-enable order matters, because enabling first produces signatures no one can verify.

v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A...

Selector mismatch. dkim=permerror or dkim=temperror with a selector in the header that does not resolve. Read the selector straight out of the DKIM-Signature header's s= tag, then query exactly that name. A rotated key, a copied record placed at the wrong host, or a DNS host that appends the domain twice (giving selector._domainkey.example.com.example.com) all land here.

Weak keys. 1024-bit RSA keys still verify but are below current practice; generate 2048-bit. Some DNS hosts cannot accept the longer value as a single string — split it into quoted chunks of 255 characters or fewer in the same record, and the resolver will reassemble it. Do not create two records.

Body hash did not verify. The message changed in transit. The usual culprits are a mailing list or security appliance appending a footer, or a relay rewriting HTML. Signing fewer headers does not help; the body hash is the body. Either stop the rewriting, or accept that DMARC will have to pass on SPF for that path.

DMARC failures

No DMARC record. Publish at _dmarc.example.com, starting in monitor mode:

v=DMARC1; p=none; rua=mailto:dmarc@example.com; fo=1

Syntax errors. v=DMARC1 must come first, tags are semicolon-separated, and rua values must be full URIs with the mailto: scheme. A bare address silently invalidates the tag.

Alignment failures — the one people miss. SPF and DKIM can both pass while DMARC fails. DMARC requires the authenticated domain to align with the From header domain. Relaxed alignment (aspf=r, adkim=r, the defaults) accepts any subdomain of the same organisational domain, so bounce.example.com aligns with example.com. Strict alignment (aspf=s, adkim=s) requires an exact match and will fail that same case. If you see spf=pass and dmarc=fail, compare the Return-Path domain with header.from — that is your answer.

Subdomains inheriting the wrong policy. Without sp=, subdomains inherit p=. If the apex is at p=reject and a subdomain sends unauthenticated application mail, that mail is now rejected. Set the subdomain policy explicitly:

v=DMARC1; p=reject; sp=quarantine; rua=mailto:dmarc@example.com

What not to do

  • Do not go straight to p=reject. Run p=none with reports until you recognise every sender, then quarantine, then reject.
  • Do not add +all or ?all to make SPF "pass". It tells the world anyone may send as you.
  • Do not keep republishing records every few minutes. Respect the TTL — lower it to 300 seconds *before* a planned change, not during the panic.
  • Do not delete a DKIM selector the moment you rotate. Mail in flight is still signed with the old key; retire it a week later.

MailPilot checks SPF, DKIM and DMARC for every connected mailbox's domain and re-checks as you send, so an expired key or a second SPF record shows up as a failed check instead of as a slow slide into the spam folder.

MailPilot

Ready to reach the inbox every time?

Automated email warmup across real peer mailboxes. Live inbox placement monitoring. Free 7-day trial - no credit card required.

Start free trial
Reach the Inbox, Every Time
Connect unlimited inboxes for warmup. Free 7-day trial, no credit card.
Try for Free
WORKS WITH
OZOHO
inbox placement99.2%
free trial7-day
companies trust us340+

Start for free - no credit card required.

Get started freeBook a demo