SPF, DKIM, DMARC Alignment: The Technical Setup Guide for Developers

hangrydev ·

Your Email Passes SPF. It Still Fails DMARC. Now What?

You added SendGrid’s include: to your SPF record. You turned on DKIM signing. You published a DMARC record. Gmail still rejects your password reset emails with a 5xx error.

The problem isn’t authentication. It’s alignment. SPF can pass and DKIM can pass, but if neither one aligns with your From: header domain, DMARC fails. And since Gmail, Yahoo, and Microsoft now enforce DMARC for bulk senders, a DMARC failure means your email bounces back with a 550 5.7.26 error. It never reaches the inbox or the spam folder.

This guide covers the exact DNS records, the common configuration mistakes, and the terminal commands to verify everything works. If you’ve never set up email authentication before, start here. If your setup is broken and you can’t figure out why, skip to the troubleshooting section.

SPF: Authorizing Your Sending IPs

SPF (Sender Policy Framework) tells receiving servers which IP addresses can send email for your domain. It’s a single TXT record at your domain root.

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

Each include: directive triggers a DNS lookup that pulls in the IP ranges for that service. The ip4: mechanism adds a specific IP without a lookup. The -all at the end means “reject everything else.” Use ~all (soft fail) during testing, -all (hard fail) in production.

The 10-Lookup Limit

RFC 7208 Section 4.6.4 caps SPF at 10 DNS lookups. Go over and SPF returns permerror, which DMARC treats as a failure. Six mechanism types count toward the limit: include, a, mx, ptr, exists, and the redirect modifier. The mechanisms ip4, ip6, and all don’t count.

Here’s the trap. Each include: can trigger nested lookups. Google flattened _spf.google.com in late 2025, so it now costs just 1 lookup. Before the change it cost 4. But other providers still nest heavily. Add SendGrid (1), Mailgun (1), Freshdesk (2), and a CRM (2), and you’re at 7 before counting your own mx mechanism.

Check your current count:

# Count SPF lookups (each include/a/mx/ptr/exists/redirect = 1+)
dig TXT yourdomain.com +short | grep spf
# Then recursively check each include
dig TXT _spf.google.com +short

If you’re past 10, you’ve got two options: consolidate services onto fewer sending platforms, or flatten your SPF record by replacing include: directives with the actual IP ranges. Flattening works but requires maintenance when providers change their IPs.

DKIM: Cryptographic Signing

DKIM (DomainKeys Identified Mail) attaches a cryptographic signature to each outgoing message. The receiving server looks up the public key in DNS and verifies the signature. If the message was altered in transit, verification fails.

The public key lives at a specific DNS location: selector._domainkey.yourdomain.com. The selector is an arbitrary string that lets you rotate keys or use different keys for different services.

s1._domainkey.yourdomain.com TXT "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQ..."

The s= value in the DKIM-Signature header of each outgoing email tells the receiver which selector to query. So if your message header says s=s1; d=yourdomain.com, the receiver looks up s1._domainkey.yourdomain.com.

Verify your DKIM record exists:

# Replace "s1" with your actual selector
dig TXT s1._domainkey.yourdomain.com +short

Don’t know your selector? Send yourself an email and check the raw headers. Look for the DKIM-Signature header:

DKIM-Signature: v=1; a=rsa-sha256; d=yourdomain.com; s=s1;
  h=from:to:subject:date;
  bh=abc123...;
  b=xyz789...

The s=s1 tells you the selector. The d=yourdomain.com tells you the signing domain. That d= value is what matters for alignment.

Key Rotation

DKIM keys should be rotated periodically. The standard approach: generate a new key pair, publish the new public key under a new selector (e.g., s2), configure your mail server to sign with the new key, then remove the old selector after a transition period. Most teams rotate every 6-12 months. Some never do, which works until it doesn’t.

DMARC: The Policy Layer

DMARC (Domain-based Message Authentication, Reporting, and Conformance) ties SPF and DKIM together with a policy. It lives at _dmarc.yourdomain.com.

_dmarc.yourdomain.com TXT "v=DMARC1; p=reject; rua=mailto:[email protected]; adkim=r; aspf=r"

The p= tag is your policy: none (monitor), quarantine (send to spam), or reject (bounce with a 5xx error). The rua= tag specifies where aggregate reports get sent. adkim and aspf control alignment mode.

Here’s the part most developers miss: DMARC doesn’t check SPF and DKIM independently. It checks alignment. The domain that passed SPF or DKIM must match the domain in your From: header.

DMARC passes if either SPF or DKIM is both valid and aligned. You don’t need both. But you want both, because forwarding breaks SPF and content modification breaks DKIM. Having both means one can compensate when the other fails. For more on how forwarding affects authentication, see the ARC authenticated received chain explainer.

Relaxed vs. Strict Alignment

The adkim= and aspf= tags control how strict the domain match needs to be.

Relaxed (r). The organizational domain must match. mail.yourdomain.com aligns with yourdomain.com. This is the default and what most setups should use.

Strict (s). Exact domain match required. mail.yourdomain.com doesn’t align with yourdomain.com. Only use strict if you have a specific reason, like preventing subdomain spoofing.

If you send from subdomains (app.yourdomain.com, billing.yourdomain.com), relaxed alignment means your root domain’s DKIM key covers all of them. Strict alignment means each subdomain needs its own signing configuration.

Configuring Third-Party Senders

Most authentication setups break right here. You use an ESP (Email Service Provider) to send email. That ESP’s servers have their own IPs and their own envelope sender domains. Without proper configuration, SPF passes for the ESP’s domain, not yours. Alignment fails.

SendGrid

SPF: Add include:sendgrid.net to your domain’s SPF record.

DKIM: In your SendGrid dashboard, go to Settings > Sender Authentication > Authenticate Your Domain. SendGrid gives you three CNAME records to add:

s1._domainkey.yourdomain.com CNAME s1.domainkey.u12345.wl.sendgrid.net
s2._domainkey.yourdomain.com CNAME s2.domainkey.u12345.wl.sendgrid.net
em1234.yourdomain.com CNAME u12345.wl.sendgrid.net

The third CNAME handles envelope sender alignment for SPF. Without it, your envelope sender stays [email protected], and SPF alignment fails even though SPF itself passes.

Mailgun

SPF: Add include:mailgun.org to your SPF record.

DKIM: Mailgun provides the DKIM public key as a TXT record. The selector varies per account. With Automatic Sender Security enabled, Mailgun gives you two CNAME records for DKIM key rotation:

pdk1._domainkey.yourdomain.com CNAME pdk1._domainkey.a1b2c3.dkim1.mailgun.com
pdk2._domainkey.yourdomain.com CNAME pdk2._domainkey.a1b2c3.dkim1.mailgun.com

Without Automatic Sender Security, you get a single TXT record at the selector Mailgun assigns. Either way, use the exact values from your Mailgun dashboard.

SPF alignment: Mailgun automatically uses your domain in the envelope sender (Return-Path), so SPF alignment works without extra CNAME records. The optional email.yourdomain.com CNAME mailgun.org record is for open and click tracking, not for SPF alignment.

Verify the setup:

# Replace selector with the one from your Mailgun dashboard
dig TXT pdk1._domainkey.yourdomain.com +short

AWS SES

SPF: SES handles SPF automatically through its own envelope sender domain. For SPF alignment, configure a custom MAIL FROM domain:

mail.yourdomain.com MX 10 feedback-smtp.us-east-1.amazonses.com
mail.yourdomain.com TXT "v=spf1 include:amazonses.com -all"

Note the MX record. SES requires it for bounce handling on the custom MAIL FROM domain.

DKIM: SES uses Easy DKIM. It gives you three CNAME records:

abc123._domainkey.yourdomain.com CNAME abc123.dkim.amazonses.com
def456._domainkey.yourdomain.com CNAME def456.dkim.amazonses.com
ghi789._domainkey.yourdomain.com CNAME ghi789.dkim.amazonses.com

Three selectors. SES rotates between them automatically.

Verifying Your Full Setup

Don’t trust your configuration until you’ve verified it from the receiving end. Here’s the full diagnostic sequence.

DNS record checks

# SPF
dig TXT yourdomain.com +short | grep spf

# DKIM (replace selector)
dig TXT s1._domainkey.yourdomain.com +short

# DMARC
dig TXT _dmarc.yourdomain.com +short

On Windows, use nslookup:

nslookup -type=TXT yourdomain.com
nslookup -type=TXT s1._domainkey.yourdomain.com
nslookup -type=TXT _dmarc.yourdomain.com

Reading Authentication-Results headers

Send a test email from your app to a Gmail address. Open the message, click the three dots, “Show original.” You’re looking for this:

Authentication-Results: mx.google.com;
  dkim=pass header.d=yourdomain.com header.s=s1;
  spf=pass (google.com: domain of [email protected] designates 198.51.100.1 as permitted sender) [email protected];
  dmarc=pass (p=REJECT sp=REJECT dis=NONE) header.from=yourdomain.com

Three things to confirm. First, dkim=pass with header.d=yourdomain.com (not your ESP’s domain). Second, spf=pass with smtp.mailfrom showing your domain or subdomain. Third, dmarc=pass. If DKIM and SPF both show pass but DMARC shows fail, you’ve got an alignment problem. The header.d or smtp.mailfrom domain doesn’t match header.from.

Quick one-liner to check all three

# Check all records in one shot
for record in "yourdomain.com" "s1._domainkey.yourdomain.com" "_dmarc.yourdomain.com"; do
  echo "=== $record ===" && dig TXT "$record" +short
done

Common Pitfalls and How to Fix Them

What breaks isn’t the setup you know about. It’s the edge case you didn’t account for.

SPF lookup limit exceeded

Symptoms: SPF returns permerror in headers. DMARC fails. You’ve got four ESPs, Google Workspace, and a helpdesk tool all in your SPF record.

Fix: Count your lookups. If you’re over 10, move some senders to subdomains. support.yourdomain.com gets its own SPF record with the helpdesk include. marketing.yourdomain.com gets the marketing platform. Each subdomain has its own 10-lookup budget.

DKIM signing domain mismatch

Symptoms: dkim=pass in headers, but header.d=sendgrid.net instead of header.d=yourdomain.com. DKIM alignment fails because the signing domain doesn’t match your From header.

Fix: Complete domain authentication in your ESP’s dashboard. For SendGrid, that’s Sender Authentication. For Mailgun, it’s Add Domain. Until you do this, the ESP signs with its own domain, not yours.

Subdomain alignment failures

Symptoms: Email sent from app.yourdomain.com fails DMARC, even though the root domain has DKIM configured. This happens with strict alignment (adkim=s).

Fix: Switch to relaxed alignment (adkim=r) or configure DKIM signing specifically for the subdomain. Most setups should use relaxed unless you have a policy reason for strict.

Forgotten sending services

You set up DMARC monitoring (p=none), collect reports for two weeks, fix everything you find, then move to p=reject. Two months later, someone in marketing starts using a new email tool that nobody told engineering about. Those emails start bouncing.

Fix: Keep rua reporting active even at p=reject. Review aggregate reports monthly. Set up an alert for any source sending more than 100 messages that fails alignment. Since Gmail rejects non-compliant emails outright, you’ll find out fast, but catching it in reports is better than hearing it from customers.

The Deployment Sequence

Don’t go straight to p=reject. Follow this path.

  1. Publish SPF with all your sending services included. Verify you’re under 10 lookups.
  2. Configure DKIM for each sending service using your domain’s keys. Verify the DNS records propagate (DKIM records can take 24-48 hours).
  3. Publish DMARC at p=none with an rua address you actually check.
  4. Collect reports for 2-4 weeks. Identify any legitimate sender that fails alignment.
  5. Fix every alignment failure.
  6. Move to p=quarantine with pct=10. Increase gradually.
  7. Move to p=reject with pct=100.

The whole process takes 4-8 weeks if you’re methodical about it. Rushing to p=reject on day one is how you block your own order confirmation emails and spend a weekend in incident response.

Before moving to p=reject, validate that your recipient addresses are actually reachable. An email validation API separates authentication problems from bad addresses, so you’re not debugging DMARC failures that are really just invalid recipients.

What Alignment Looks Like When It Works

Here’s the full picture. Your app sends a password reset through SendGrid. The email leaves SendGrid’s server at IP 198.51.100.1.

The receiving server checks SPF. The envelope sender is [email protected] (because you configured the domain authentication CNAME). SPF lookup on em1234.yourdomain.com finds include:sendgrid.net (SendGrid’s automated security publishes this for you). The sending IP is in that range. SPF passes with domain em1234.yourdomain.com.

The receiver checks DKIM. The signature header says d=yourdomain.com; s=s1. The receiver queries s1._domainkey.yourdomain.com, gets the public key, verifies the signature. DKIM passes with domain yourdomain.com.

The receiver checks DMARC. The From: header says [email protected]. SPF passed for em1234.yourdomain.com, which shares the organizational domain yourdomain.com with the From header. Relaxed SPF alignment passes. DKIM passed for yourdomain.com, exact match. DKIM alignment passes.

DMARC passes. Email delivers. That’s the whole chain.

Sound complicated? It’s really just three DNS record types and a handful of CNAME entries for each sending service. The hard part isn’t the setup. It’s knowing where to look when something breaks.