If you've ever set up a mail server, migrated email providers, or debugged why your transactional emails land in spam, you've bumped into SPF, DKIM, and DMARC. These three protocols form the backbone of email authentication — but most explanations either oversimplify them or drown you in RFCs.

This post is the guide I wish I'd had. We'll cover what each protocol does, how they interact, the most common misconfigurations, and how to verify them programmatically.

The Problem: Email Has No Built-in Identity

SMTP was designed in 1982. It has no concept of verifying who's sending a message. Anyone can set the From: header to ceo@yourcompany.com and send mail from any server. That's not a bug — it's how the protocol works.

SPF, DKIM, and DMARC were bolted on over the years to fix this. Each solves a different part of the problem:

  • SPF — "Which servers are allowed to send mail for this domain?"
  • DKIM — "Was this message actually sent by the domain it claims, and is it unmodified?"
  • DMARC — "What should receivers do when SPF or DKIM fail, and where should they report it?"
How a receiving mail server authenticates an email: ┌─────────────┐ ┌──────────────────┐ ┌───────────────┐ │ Sender MTA │─────▶│ Receiving MTA │─────▶│ Recipient │ │ (smtp.ex..)│ │ │ │ Inbox │ └─────────────┘ │ 1. Check SPF │ └───────────────┘ │ 2. Verify DKIM │ │ 3. Apply DMARC │ │ 4. Deliver / 🗑 │ └──────────────────┘ SPF: DNS TXT on envelope domain → "Is this IP allowed?" DKIM: DNS TXT on d= domain → "Does the signature verify?" DMARC: DNS TXT on From: domain → "Do SPF/DKIM align? What's the policy?"

SPF: Sender Policy Framework

SPF lets a domain owner publish a DNS TXT record listing which IP addresses and servers are authorized to send mail on their behalf. When a mail server receives a message, it checks the envelope sender (the MAIL FROM in the SMTP conversation, not the visible From: header) against the domain's SPF record.

What an SPF record looks like

v=spf1 include:_spf.google.com include:sendgrid.net ip4:203.0.113.5 -all

Breaking this down:

  • v=spf1 — version identifier (required)
  • include:_spf.google.com — allow any IP that Google Workspace authorizes
  • include:sendgrid.net — also allow SendGrid's IPs
  • ip4:203.0.113.5 — allow this specific IP
  • -all — reject everything else (~all = softfail, ?all = neutral)

Common SPF mistakes

  • Too many DNS lookups. SPF has a 10-lookup limit. Each include: and a: counts. Exceed it and SPF returns permerror — effectively no protection. This is the #1 misconfiguration on domains using multiple SaaS providers.
  • Using ~all instead of -all. Softfail (~all) tells receivers "this probably shouldn't pass but don't reject it." It's training wheels — fine while testing, bad in production.
  • Forgetting subdomains. SPF only covers the exact domain. If you have SPF on example.com but send from mail.example.com, you need a separate record.

DKIM: DomainKeys Identified Mail

DKIM adds a cryptographic signature to every outgoing email. The sending server signs specific headers and the message body with a private key; the corresponding public key is published in DNS. The receiving server fetches the public key and verifies the signature.

How it works in practice

When you send an email, your mail server adds a header like this:

DKIM-Signature: v=1; a=rsa-sha256; d=example.com; s=selector1;
  h=from:to:subject:date:message-id;
  bh=2jUSOH9NhtVGCQWNr9BrIAPreKQjO6Sn7XIkfJVOzv8=;
  b=AuUoFEfDxTDkHlLXSZEpZj79LICEps6eda7W3deTVFOk...

Key fields:

  • d=example.com — the signing domain
  • s=selector1 — which public key to look up (selector1._domainkey.example.com)
  • h= — which headers were signed
  • b= — the actual signature
  • bh= — hash of the body

The receiving server queries selector1._domainkey.example.com for a TXT record containing the public key, then verifies the signature. If it matches, the message hasn't been tampered with and genuinely came from (or through) that domain's infrastructure.

Common DKIM mistakes

  • Key rotation neglect. Old 1024-bit keys are increasingly crackable. Use 2048-bit and rotate annually.
  • Signing with a third-party domain. If your ESP signs with d=sendgrid.net instead of d=yourdomain.com, DMARC alignment fails — the signature doesn't match the From: domain. Always configure custom DKIM.
  • Not signing enough headers. At minimum, sign from, to, subject, date, and message-id.

DMARC: Domain-based Message Authentication, Reporting & Conformance

DMARC ties SPF and DKIM together. It tells receiving servers: "Check if SPF or DKIM passes and aligns with the From: domain. If neither aligns, here's what to do."

What a DMARC record looks like

v=DMARC1; p=reject; rua=mailto:dmarc@example.com; ruf=mailto:dmarc@example.com; adkim=s; aspf=r; pct=100
  • p=reject — policy: none (monitor), quarantine (spam folder), or reject (drop it)
  • rua= — where to send aggregate reports (XML, daily)
  • ruf= — where to send forensic/failure reports (per-message)
  • adkim=s — DKIM alignment: s = strict (exact domain match), r = relaxed (subdomains OK)
  • aspf=r — SPF alignment mode
  • pct=100 — apply policy to 100% of failing messages

The alignment concept

This is where most people get confused. DMARC doesn't just check if SPF or DKIM pass — it checks if they align with the domain in the visible From: header.

DMARC alignment check: From: header → user@example.com SPF envelope domain → bounce@example.com ✅ aligned (same domain) DKIM d= domain → example.com ✅ aligned From: header → user@example.com SPF envelope domain → bounce@esp.sendgrid.net ❌ not aligned DKIM d= domain → sendgrid.net ❌ not aligned Result: DMARC FAIL — even if SPF and DKIM individually pass!

This is why configuring custom domains on your ESP matters. If SendGrid sends with MAIL FROM: bounce@sendgrid.net and signs with d=sendgrid.net, both SPF and DKIM pass for sendgrid.net, but DMARC fails because the From: says @example.com.

The recommended rollout path

  1. Start with p=none — monitor only. Set up rua= to receive aggregate reports.
  2. Analyze reports for 2-4 weeks — identify legitimate senders that fail alignment.
  3. Fix alignment issues — configure custom SPF/DKIM on all ESPs.
  4. Move to p=quarantine — failing messages go to spam.
  5. Finally, p=reject — full protection.

How They Work Together

Here's the full authentication flow when a message arrives:

Full authentication flow: 1. SMTP connection opens └─ Receiving server notes the sending IP: 198.51.100.42 2. MAIL FROM: bounce@example.com └─ SPF check: query TXT record for example.com └─ Does 198.51.100.42 match? → SPF pass/fail 3. Message body received with DKIM-Signature header └─ DKIM check: query TXT for selector._domainkey.example.com └─ Does signature verify? → DKIM pass/fail 4. From: header says user@example.com └─ DMARC check: query TXT for _dmarc.example.com ├─ SPF passed AND aligned with From:? → DMARC pass ├─ DKIM passed AND aligned with From:? → DMARC pass └─ Neither aligned? → Apply policy (none/quarantine/reject) 5. Deliver, spam-folder, or reject

A message passes DMARC if either SPF or DKIM passes and aligns. You don't need both — but having both provides redundancy. DKIM survives forwarding (SPF doesn't, since the forwarding server's IP won't be in the original SPF record). SPF works even when DKIM signatures break (which can happen with mailing lists that modify headers).

Checking Email Authentication Programmatically

During development, you'll want to verify that your DNS records are correct and that emails from your domain actually pass authentication. Here are a few approaches:

Manual DNS checks

# Check SPF
dig +short TXT example.com | grep spf

# Check DKIM (you need to know the selector)
dig +short TXT selector1._domainkey.example.com

# Check DMARC
dig +short TXT _dmarc.example.com

Using the MailCheck API

If you're building an app that needs to verify email authentication for arbitrary domains — say, checking if a customer's email is properly authenticated before adding them to your platform — the MailCheck Authentication Verification endpoint does this in a single API call:

curl -X POST https://api.mailcheck.dev/v1/verify/auth \
  -H "Authorization: Bearer sk_live_your_key" \
  -H "Content-Type: application/json" \
  -d '{"domain": "example.com"}'

The response includes the full SPF, DKIM, and DMARC configuration for the domain, parsed and validated — including alignment analysis and specific misconfigurations. It's useful for onboarding flows where you need to tell customers exactly what DNS records to fix.

🔧 Try it yourself

Use the free Domain Check tool to inspect any domain's SPF, DKIM, and DMARC configuration instantly. Or grab an API key to integrate it into your app — 100 free checks/day, no credit card required.

Quick Reference: DNS Records Cheat Sheet

Minimum viable email authentication: 1. SPF (TXT on example.com) v=spf1 include:_spf.google.com -all 2. DKIM (TXT on selector._domainkey.example.com) v=DKIM1; k=rsa; p=MIIBIjANBgkqhki... (your public key) 3. DMARC (TXT on _dmarc.example.com) v=DMARC1; p=reject; rua=mailto:dmarc@example.com

Takeaways

  • SPF authorizes sending IPs — watch the 10-lookup limit.
  • DKIM cryptographically signs messages — always use custom signing domains and 2048-bit keys.
  • DMARC enforces alignment between SPF/DKIM and the From: header — roll out gradually from none to reject.
  • Alignment is the key concept. Individual SPF/DKIM passes mean nothing if they don't align with the visible From: domain.
  • Test everything. Use dig, send test emails, check DMARC reports, or use an API like MailCheck's auth verification to catch issues before your users do.

Getting email authentication right isn't glamorous work, but it's the difference between your emails landing in the inbox and disappearing into the void. Set it up once, monitor with DMARC reports, and you're done.