SPF, DKIM, and DMARC setup: a step-by-step guide
Three DNS records. Without them Gmail, Yahoo, and Mail.ru will treat your mail with suspicion — or reject it outright. Since February 2024, Google requires SPF, DKIM, and DMARC from anyone sending more than 5,000 messages per day. Yahoo followed a month later.
What SPF, DKIM, and DMARC actually do
The short version: they prove that a message came from you, not from someone who spoofed the From header. Each protocol covers a different part of that proof.
SPF (Sender Policy Framework) is a list of servers permitted to send mail on behalf of your domain. When a message arrives, the receiving server checks the sender's IP against your SPF record in DNS. Match — fine. No match — suspicious.
DKIM (DomainKeys Identified Mail) is a cryptographic signature. Each message is signed with a private key on the sending server. The receiving server fetches the corresponding public key from DNS using a selector, then verifies the signature. If the body or headers were changed in transit, the signature won't match.
DMARC (Domain-based Message Authentication, Reporting & Conformance) ties SPF and DKIM together into a single policy. It tells receiving servers what to do with messages that fail authentication, and sends you reports about who is sending from your domain.
Step 1. Configuring SPF
An SPF record is a TXT record in your domain's DNS that lists the legitimate sources of your outbound mail.
Minimal example
yourdomain.com. IN TXT "v=spf1 include:_spf.google.com ~all"
Breaking it down:
v=spf1— the version tag. Always spf1; no other version exists.include:_spf.google.com— authorizes Google Workspace servers to send on your behalf.~all— soft fail for everything else. The message isn't rejected, but it gets flagged.
A more realistic example
Say you use Google Workspace for corporate mail, Mailgun for transactional messages, and Brevo (formerly Sendinblue) for marketing campaigns.
yourdomain.com. IN TXT "v=spf1 include:_spf.google.com include:mailgun.org include:sendinblue.com -all"
Notice -all instead of ~all. Hard fail. Anything not on the list gets rejected. Stricter, but safer. Use -all in production once you're confident you know every sending source.
Verification
dig TXT yourdomain.com +short | grep spf
You should get exactly one line starting with v=spf1. Exactly one. Two SPF records is an invalid configuration — some providers will ignore both.
Common SPF mistakes
- More than 10 DNS lookups. SPF caps at 10 lookups (include, a, mx, and redirect all count). Exceed the limit and the record is invalid. Check with
dmarcian.com/spf-surveyormxtoolbox.com/spf.aspx. - Two TXT records with v=spf1. Merge everything into one record. RFC 7208 requires exactly one SPF record per domain.
- Forgetting to add your ESP. You connect Mailchimp but don't add
include:servers.mcsv.netto SPF. Those messages fail SPF checks. - Using +all. This authorizes anyone to send from your domain — a pointless record that we've seen in production, and it never ends well.
Step 2. Configuring DKIM
DKIM uses a key pair: a private key on the sending server and a public key in DNS. The server signs the headers and body of every message. The receiving side looks up the public key by selector and verifies the signature.
Generating keys
If you use an external ESP (Mailgun, SendGrid, Postmark), they generate the keys and you add a CNAME or TXT record. For a self-hosted server (Postfix, Exim, Haraka):
# Generate a 2048-bit RSA key openssl genrsa -out dkim_private.pem 2048 openssl rsa -in dkim_private.pem -pubout -out dkim_public.pem # Extract the public key as a single line (for DNS) grep -v "^-" dkim_public.pem | tr -d '\n'
DNS record
Publish the public key as a TXT record at selector._domainkey.yourdomain.com. The selector is any string you choose. Common choices are s1, mail, google, or the ESP name.
s1._domainkey.yourdomain.com. IN TXT "v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2K...your_key..."
A 2048-bit key exceeds 255 characters, which is the TXT record limit. Most DNS providers split it into chunks automatically. Cloudflare, Route 53, and DNSimple handle this transparently. If your provider doesn't, split the string manually into 255-character quoted segments.
Verification
dig TXT s1._domainkey.yourdomain.com +short
The response should contain v=DKIM1 and the public key. No response means either the selector is wrong, or the record hasn't propagated yet (TTL is usually 300–3600 seconds).
Common DKIM mistakes
- 1024-bit key. It works, but Google has recommended 2048 bits since 2023. Some providers already penalize shorter keys.
- Mismatched selector. Your Postfix config says
selector=mail, but the DNS record is unders1._domainkey. The signature will fail — selectors must match exactly. - Spaces or line breaks in the key. The public key in DNS must be a continuous base64 string inside quotes. An extra newline makes the key invalid.
- No key rotation plan. Few people think about this until something goes wrong. Rotate every 6–12 months: create a new selector (s2), publish it in DNS, switch the server, then remove the old one. Zero downtime if you do it in that order.
Step 3. Configuring DMARC
DMARC combines SPF and DKIM into a single policy. Without it, receiving providers decide on their own what to do with failed messages. With it, you tell them.
Starting record
_dmarc.yourdomain.com. IN TXT "v=DMARC1; p=none; rua=mailto:dmarc-reports@yourdomain.com; ruf=mailto:dmarc-forensic@yourdomain.com; pct=100"
Breaking it down:
v=DMARC1— the only version.p=none— take no action, just collect reports. Start here.rua— address for aggregate reports (XML files, sent once daily).ruf— address for forensic reports (per-failure details). Gmail doesn't send these; Yahoo does.pct=100— percentage of messages the policy applies to.
The path to p=reject
In practice, this rollout takes 4–8 weeks. Don't rush it. One forgotten integration and your legitimate mail goes straight to trash.
The sequence:
- Weeks 1–2:
p=none. Collect reports. Look at who is sending from your domain. You will almost always find services you forgot about: a CRM, a ticketing system, a survey tool. - Weeks 3–4:
p=quarantine; pct=25. A quarter of failing messages go to spam. Watch complaint and bounce rates. - Weeks 5–6:
p=quarantine; pct=100. All failing messages go to spam. If it stays quiet, you're clean. - Weeks 7–8:
p=reject. Full enforcement. Spoofed messages are rejected at the SMTP level.
Verification
dig TXT _dmarc.yourdomain.com +short
You should see a string starting with v=DMARC1. Empty response means either the record name is wrong (missing the _dmarc. prefix) or DNS hasn't updated yet.
Common DMARC mistakes
- Jumping straight to p=reject. We checked: 40% of domains that skip the monitoring phase discover at least one forgotten sending service after the fact. The result is lost legitimate mail.
- No rua address. DMARC without reports is like monitoring without alerts. Use a free parser: DMARC Analyzer, Postmark DMARC, or dmarcian's free tier.
- Alignment mismatch. DMARC checks that the domain in From matches the domain in SPF (envelope-from) or DKIM (the d= tag). If your ESP uses its own domain in the envelope-from, SPF alignment fails. Fix it with custom DKIM (d=yourdomain.com) or a custom return-path subdomain.
Alignment: the detail that breaks everything
DMARC requires at least one protocol (SPF or DKIM) to both pass and align with the From header domain. Without alignment, DMARC fails even if SPF and DKIM individually pass.
Example. You send from team@yourdomain.com. Your ESP uses bounce-12345@esp.com as the envelope-from. SPF is checked against esp.com, but From is yourdomain.com. Different domains. SPF alignment fails.
The fix: configure DKIM with d=yourdomain.com (most ESPs support this via custom DKIM). DKIM alignment then passes and DMARC is satisfied. Alternatively, set up a custom return-path subdomain like mail.yourdomain.com, which makes SPF alignment pass too.
Verifying everything together
After setup, send a test message to a Gmail account. Open it, click "Show original." In the headers you'll see:
Authentication-Results: mx.google.com;
dkim=pass header.i=@yourdomain.com header.s=s1;
spf=pass (google.com: domain of team@yourdomain.com designates 198.51.100.42 as permitted sender);
dmarc=pass (p=QUARANTINE sp=QUARANTINE dis=NONE) header.from=yourdomain.comThree passes means everything works. If you see fail or softfail, go back to that specific step.
Diagnostic tools
- mail-tester.com — send a message to the generated address, get a score from 0 to 10 with a breakdown of SPF, DKIM, and DMARC.
- mxtoolbox.com — DNS record checks, blacklist lookup, SMTP diagnostics. The most widely used tool in the industry.
- Google Postmaster Tools — if you send to Gmail, this shows domain and IP reputation, spam rates, and authentication errors.
- dig / nslookup — quick checks from the terminal. No GUI, but instant results.
Full dig checklist:
# SPF dig TXT yourdomain.com +short | grep spf # DKIM (substitute your selector) dig TXT s1._domainkey.yourdomain.com +short # DMARC dig TXT _dmarc.yourdomain.com +short # MX (confirm the domain accepts mail at all) dig MX yourdomain.com +short
What about subdomains
Your root domain's DMARC policy applies to subdomains by default (via the sp= tag). But SPF and DKIM need to be configured separately for each subdomain you actually send from.
A typical setup for a mid-size company:
yourdomain.com— corporate mail (Google Workspace, Exchange)mail.yourdomain.com— marketing campaigns (ESP)notify.yourdomain.com— transactional notifications (Postmark, SES)
Each subdomain gets its own SPF record and its own DKIM selector. More work upfront, but it means the reputation of your marketing sends can't drag down your transactional mail. A password-reset email should never land in spam because a promo campaign picked up complaints.
SPF, DKIM, DMARC are the foundation
Without authentication, even a perfectly clean list won't save your deliverability. But authentication without a clean list is only half the job. Hard bounces, spam traps, and dead addresses damage domain reputation just as much as a missing DKIM record.
Records configured? Next step: make sure your list is clean. Upload your list to uChecker and within minutes you'll see invalid addresses, spam traps, and risky contacts. Download the results and clean before your next send.
