Skip to content

tutorials

How to check SPF, DKIM, and DMARC are actually passing

August 19, 2026

#deliverability#spf#dkim#dmarc#email-authentication
How to check SPF, DKIM, and DMARC are actually passing
Visual: Karton Braunfeld on okslop

You added the SPF, DKIM, and DMARC records your provider's wizard told you to add. The dashboard shows green checkmarks. Then a customer forwards you a screenshot of your receipt landing in their spam folder, and you have no idea whether your authentication is the problem.

The only way to know SPF, DKIM, and DMARC are actually passing is to read the Authentication-Results header on a message that a real receiver accepted. Send a test email to an account you control at Gmail or Outlook, open the raw source, and read the spf=, dkim=, and dmarc= verdicts the receiver wrote. A green checkmark in a vendor dashboard tells you a DNS record exists. The Authentication-Results header tells you the record evaluated to pass against the IP you actually sent from, with the keys you actually signed with. Those are different facts, and the gap between them is where mail quietly goes to spam.

This tutorial does it the careful way: confirm the records resolve with dig, send a live test, and read the header that mailbox providers write. It is honest about the parts that are confusing (the difference between "passing" and "aligned" trips up almost everyone). At the end I will show how a Cloudflare-native stack surfaces these checks for you, but the method below works no matter what you send through.

What does it mean for SPF, DKIM, and DMARC to pass?

Passing means a real receiving mail server evaluated your message and recorded a positive verdict, not that you published a DNS record. There are three independent checks, and they fail for different reasons.

  • SPF (Sender Policy Framework) asks: is the IP that connected authorized to send for the envelope-from (Return-Path) domain? The receiver looks up your SPF TXT record, compares the connecting IP to the authorized list, and records spf=pass or spf=fail.
  • DKIM (DomainKeys Identified Mail) asks: was this message signed by a private key whose public half is published in DNS, and is the signed content unchanged? The receiver fetches the public key at selector._domainkey.yourdomain, verifies the cryptographic signature in the DKIM-Signature header, and records dkim=pass or dkim=fail.
  • DMARC (Domain-based Message Authentication, Reporting and Conformance) asks: did SPF or DKIM pass and align with the visible From domain? It ties the other two to the address your recipient actually sees, then applies your published policy (none, quarantine, or reject).

If the surrounding alphabet soup (DKIM, BIMI, ARC, MTA-STS) is what is tripping you up, every email authentication acronym in one sentence each is a fast reference.

The trap is that each check can show green in isolation and still fail in combination. A new sender can have a valid SPF record, a valid DKIM key, and still get dmarc=fail because neither passing mechanism aligns with the From domain. If you want the conceptual foundation before you start testing, the complete SPF, DKIM, and DMARC setup guide walks through publishing the records correctly in the first place. This article assumes you have records and want to verify them.

How do I confirm the DNS records exist with dig?

Use dig to fetch each record directly from DNS. This is the cheap first pass: it catches typos, missing records, and the most common SPF misconfiguration before you spend a test email on them.

Check SPF (it lives on the root domain as a TXT record):

dig +short TXT example.com | grep spf1

Expected output for a domain that sends through Cloudflare and Google Workspace:

"v=spf1 include:_spf.google.com include:_spf.mx.cloudflare.net ~all"

If you get nothing back, there is no SPF record. If you get two lines that both start with v=spf1, you have two SPF records, which is itself a misconfiguration: SPF permits exactly one record per domain, and most receivers treat duplicates as a permerror (permanent error), which fails.

Check DMARC (it lives on the _dmarc subdomain):

dig +short TXT _dmarc.example.com

Expected:

"v=DMARC1; p=quarantine; rua=mailto:dmarc@example.com; adkim=s; aspf=s"

Check DKIM. This one requires the selector, a label your sending service chose. You can find it in the DKIM-Signature header of a sent message (the s= tag), or in your provider's DNS instructions. A common Google selector is google; Cloudflare and other services use their own:

dig +short TXT google._domainkey.example.com

Expected (truncated):

"v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQ..."

If the DKIM lookup returns nothing, the most likely causes are: you guessed the wrong selector, the record was published on the wrong name (a frequent CNAME-vs-TXT mistake), or it has not propagated yet.

Catch the SPF 10-lookup limit early

SPF has a hard limit: evaluating your record must not require more than 10 DNS lookups. Each include:, a, mx, ptr, and exists mechanism counts, and nested includes count too. Blow past 10 and receivers return a permerror, which fails SPF even though every IP in your record is legitimate. You cannot see this with a single dig; you have to expand the includes. A quick manual count:

# count the include: mechanisms in your record as a starting estimate
dig +short TXT example.com | grep -o 'include:[^ "]*' | wc -l

That undercounts (each include can pull in more), so treat anything above 6-7 top-level includes as a warning to flatten your record. This is one of the most common reasons a "correct-looking" SPF record fails in production.

How do I read the Authentication-Results header?

The Authentication-Results header is the receiver's signed verdict, and it is the most trustworthy check you can run. DNS records tell you what should happen; this header tells you what did happen when a real mailbox provider evaluated your message.

Send a plain test email from your production sender to an account you control. Use at least Gmail and one Microsoft account (Outlook.com or a Microsoft 365 address), because they implement the checks slightly differently and you want to catch a failure that only shows up on one.

In Gmail, open the message, click the three-dot overflow menu, and choose Show original. In the raw source, find the header (Gmail labels its own copy ARC-Authentication-Results, but the Authentication-Results line is what you want):

Authentication-Results: mx.google.com;
       dkim=pass header.i=@example.com header.s=google header.b=Nl9vR2kP;
       spf=pass (google.com: domain of bounce@example.com designates
            104.18.32.7 as permitted sender) smtp.mailfrom=bounce@example.com;
       dmarc=pass (p=QUARANTINE sp=QUARANTINE dis=NONE) header.from=example.com

Read it field by field:

  • dkim=pass header.i=@example.com header.s=google means DKIM verified and was signed with selector google for example.com. The header.i (or in other receivers, the d= value) is the signing domain. It must match your From domain for DKIM to align.
  • spf=pass ... smtp.mailfrom=bounce@example.com means the connecting IP 104.18.32.7 is authorized for the Return-Path domain example.com. Note smtp.mailfrom: that is the envelope-from, not the visible From. SPF aligns only when that domain matches your From domain.
  • dmarc=pass ... header.from=example.com means at least one of SPF/DKIM passed AND aligned with the From domain example.com. This is the verdict that actually determines spam-folder placement under your policy.

If you instead see something like this, you have a problem the dashboard would never show you:

Authentication-Results: mx.google.com;
       dkim=pass header.i=@mailservice.net header.s=s1 header.b=aB3xY;
       spf=pass smtp.mailfrom=bounce@mailservice.net;
       dmarc=fail (p=QUARANTINE sp=QUARANTINE dis=QUARANTINE) header.from=example.com

Here SPF and DKIM both pass, but both pass for mailservice.net, the sending service's domain, not yours. The visible From is example.com. Nothing aligns, so dmarc=fail, and with p=quarantine the message is quarantined (dis=QUARANTINE). This is the single most common "but my records are green" failure, and you can only diagnose it by reading this header. The fix is to set up domain authentication so DKIM signs with d=example.com (usually by adding a CNAME the provider gives you) and/or to use a custom Return-Path on your own domain.

How do I verify alignment, not just pass?

Alignment is the part that gets skipped, and it is what DMARC actually grades. A check can pass for the wrong domain; alignment confirms it passed for your domain.

There are two alignment relationships, and DMARC needs at least one of them to hold:

MechanismPasses whenAligns when
SPFConnecting IP is authorized for the Return-Path domainReturn-Path (smtp.mailfrom) domain matches the From domain
DKIMSignature verifies against the published public keyThe d= / header.i signing domain matches the From domain

Strict vs. relaxed alignment is controlled by the adkim and aspf tags in your DMARC record. With relaxed alignment (the default), a subdomain like mail.example.com aligns with example.com. With strict (adkim=s, aspf=s), they must match exactly. If your DMARC record sets strict alignment and your sending service signs with a subdomain, you will see dmarc=fail despite dkim=pass. Read the adkim/aspf values in your DMARC record (from the dig step) and compare them against the actual d= and smtp.mailfrom domains in the header.

A practical rule: get DKIM alignment working, because it survives forwarding. SPF breaks when a message is forwarded (the forwarder becomes the new connecting IP, so SPF fails at the next hop), but a DKIM signature travels with the message body and keeps verifying. If both align you are in the best shape, but if you can only get one, make it DKIM.

What free tools check email authentication end to end?

Beyond dig and reading headers by hand, two free tools give you a faster full picture. They do not replace reading the header, but they catch things quickly.

mail-tester.com gives you a throwaway address. Send your real message to it and it returns a scored report covering SPF, DKIM, DMARC, reverse DNS, blocklist status, and content. It is the fastest way to get a broad signal, and it flags problems (like a missing PTR record or spam-trigger content) that pure authentication checks miss. For a wider sweep of what to keep bookmarked, see the roundup of free tools to test your deliverability and DNS.

Gmail's "Show original" panel summarizes the three verdicts at the top before the raw headers:

SPF:    PASS with IP 104.18.32.7
DKIM:   'PASS' with domain example.com
DMARC:  'PASS'

Note the with domain example.com next to DKIM: that is Gmail telling you the alignment domain. If it said with domain mailservice.net while your From was example.com, that is your misalignment, visible at a glance.

For a programmatic check you can script against your own sending, fetch and parse the record yourself in a Worker or a Node script:

// Minimal SPF + DMARC record fetch using DNS-over-HTTPS (works on Cloudflare Workers)
async function getTxt(name: string): Promise<string[]> {
  const url = `https://cloudflare-dns.com/dns-query?name=${name}&type=TXT`;
  const res = await fetch(url, { headers: { accept: "application/dns-json" } });
  const json = (await res.json()) as { Answer?: { data: string }[] };
  // DoH returns TXT data wrapped in quotes; strip them
  return (json.Answer ?? []).map((a) => a.data.replace(/^"|"$/g, ""));
}

const domain = "example.com";
const spf = (await getTxt(domain)).filter((r) => r.startsWith("v=spf1"));
const dmarc = await getTxt(`_dmarc.${domain}`);

if (spf.length === 0) console.error("No SPF record found");
if (spf.length > 1) console.error("Multiple SPF records (permerror): publish exactly one");
if (dmarc.length === 0) console.error("No DMARC record found");
console.log({ spf, dmarc });

That script confirms the records resolve and catches the duplicate-SPF case, but remember its limit: it cannot tell you whether SPF passes for the IP you send from or whether DKIM signatures verify. Only a live test reaching a real receiver does that. Run both, every time you change DNS or switch sending services.

Once you have a passing baseline, the next thing worth doing is reading the DMARC aggregate reports that start arriving at your rua address, which show you authentication results across every receiver, not just the ones you test by hand. That is its own task: see reading DMARC aggregate reports without a paid tool.

How does Tinbox handle authentication checks?

Or skip the manual loop. When you stand up an email stack with Tinbox, the deploy publishes the SPF, DKIM, and DMARC records for your domain and then runs the same live-send verification described above against a real receiver, so you see actual Authentication-Results verdicts in the settings panel rather than a "record exists" checkmark. Because the whole stack runs on your own Cloudflare account (D1, R2, Queues, and Workers), the DKIM signing keys live in your infrastructure and sign with d=yourdomain, which is exactly the alignment condition that the manual method above spends the most effort getting right.

It does not replace understanding what the checks mean. If anything, owning the stack makes the headers easier to read, because there is no third-party sending domain in the way to misalign with. For the broader picture of how authentication fits into inbox placement, the email deliverability guide connects these checks to reputation, warmup, and content. And if you are evaluating where your sender should live, sending email from Cloudflare Workers covers why edge sending needs an HTTPS-based path in the first place (Workers cannot open outbound port 25 for direct-to-MX SMTP).

Deploy this yourself: npx tinbox init <domain>. The init flow walks the DNS records and shows you the live verdicts, and the docs cover re-running the check after any DNS change.

Frequently asked questions

How do I check if SPF, DKIM, and DMARC are passing?

Send a test email to an account you control at Gmail or Outlook, open the raw message source, and read the Authentication-Results header. It contains spf=pass, dkim=pass, and dmarc=pass verdicts written by the receiver that actually evaluated your mail. That header is authoritative because it reflects how a real mailbox provider judged the message, not just whether a DNS record exists. Cross-check with dig to confirm the underlying records resolve.

Why does my SPF record exist but still fail?

An SPF record that exists can still fail if the IP that sent the message is not authorized by the record, if you exceed the 10 DNS-lookup limit, or if the check is against the wrong domain. SPF authenticates the Return-Path (envelope-from) domain, not the visible From address, so a service sending on your behalf with its own Return-Path will pass SPF for its domain and that does nothing for your From-domain alignment under DMARC.

What does DMARC alignment mean and why does it fail when SPF and DKIM pass?

DMARC requires that a passing SPF or DKIM check also aligns with the domain in the visible From header. SPF aligns when the Return-Path domain matches the From domain; DKIM aligns when the d= signing domain matches the From domain. You can have spf=pass and dkim=pass and still get dmarc=fail if both passes were for a third party's domain (a sending service) rather than your own From domain.

Can I check email authentication without sending an email?

Partly. With dig you can confirm that your SPF TXT record, DMARC TXT record, and DKIM public key all resolve and are syntactically valid, which catches typos and missing records. But you cannot confirm SPF passes for the IP you actually send from, or that DKIM signatures verify, without sending a real message and reading the receiver's Authentication-Results header. Do both.

What tools check SPF, DKIM, and DMARC for free?

Free options include the dig command line tool for DNS records, the mail-tester.com web service for a full scored report, and the practice of mailing a test to a Gmail account and reading Show original. Gmail and Outlook both expose the receiver-side Authentication-Results header in the raw source, which is the most trustworthy signal because it is the verdict from a real mailbox provider.