SPF, DKIM, and DMARC explained for developers
What SPF, DKIM, and DMARC each do, how they fit together to stop email spoofing, and how to read the DNS records and authentication headers to tell whether they're working.

Open a terminal, and you can send an email claiming to be from support@stripe.com.
SMTP won't stop you. The protocol was designed in the early 1980s with no built-in way to verify
the sender, and that gap is still there. Three standards have been retrofitted on top to close it:
SPF, DKIM, and DMARC. Together they let a receiving server decide whether mail
claiming to be from a domain actually came from someone the domain authorized.
SPF: declaring who can send for you
SPF is the first record receivers check, and the easiest to get right, so it's the place to start. It's a single DNS TXT record listing the IP addresses allowed to send email on behalf of your
domain. When a receiving server gets a message claiming to be from yourapp.com, it
looks up that record and checks whether the sending IP is on the list.
v=spf1 include:spf.lettr.com include:_spf.google.com ~allThe include mechanisms pull in the IP ranges of each service that sends for you. The trailing ~all is the catch-all verdict for everything
else: ~all soft-fails (accept but mark suspicious), -all hard-fails. Stick
with ~all.
Check the record with dig, and confirm there's exactly one:
dig TXT yourapp.com +shortUnfortunately, SPF has one structural weakness worth remembering: it checks the envelope sender (the MAIL FROM in the SMTP conversation), not the From header users see. A phishing email can fail SPF
on its real domain and still display any From address it likes. SPF also breaks under forwarding,
because the forwarder's IP isn't in your record. Both gaps are why DKIM and DMARC exist.
DKIM: signing each message cryptographically
Where SPF authorizes servers, DKIM authenticates the message itself, which is what lets it survive
the forwarding that breaks SPF. The provider computes a hash of selected headers and the body,
signs it with a private key, and attaches the result as a DKIM-Signature header:
DKIM-Signature: v=1; a=rsa-sha256; d=yourapp.com; s=lettr;
h=from:to:subject:date:message-id;
bh=2jUSOH9NhtVGCQWNr9BrIAPreKQjO6Sn7XIkfJVOzv8=;
b=AuUoFEfDxTDkHlLXSZEpZj79LICEps6eda7W3deTVFOk2...Two fields carry the meaning. d=yourapp.com is the domain claiming responsibility, and s=lettr is the selector that tells receivers where to find the public key: <selector>._domainkey.<domain>. The receiver fetches that key, recomputes
the hash, and compares. Change a single character in the subject or body in transit and the
signature no longer matches. Fetch the public key yourself:
dig TXT lettr._domainkey.yourapp.com +shortThe selector exists to enable key rotation: providers publish a new key under a new selector, start
signing with it, and retire the old one once mail in flight has cleared. Because DKIM signs the
visible From header and travels with the message, it protects the address users
actually see far better than SPF does.
DMARC: policy and reporting
SPF and DKIM each produce a pass or fail, but neither says what a receiver should do with a failure,
and neither guarantees the check covers the domain in the visible From header. DMARC fills both gaps. It's a DNS TXT record at _dmarc.yourapp.com that publishes a policy
and a reporting address:
v=DMARC1; p=none; rua=mailto:dmarc-reports@yourapp.comp=none is the policy (what to do with failing mail), and rua is where aggregate
reports go. Check it with dig TXT _dmarc.yourapp.com.
Alignment is the part that matters
Alignment is the mechanism that actually closes the spoofing gap, so it's the one DMARC concept
worth internalizing. A message passes DMARC only if it passes SPF or DKIM and the
domain that passed matches the From header domain the user sees. An attacker who passes
SPF on their own domain while displaying yours in the From header now fails, because
the authenticated domain and the visible one don't line up.
Roll out enforcement in stages
Jumping straight to p=reject drops entire categories of legitimate mail overnight:
forwarded messages, mail from services you forgot about, misconfigured subdomains. The policy moves
through three values, and the first one must be the a starting point.
Aggregate reports arrive as gzipped XML at the rua address. One record looks like this:
<record>
<row>
<source_ip>198.51.100.42</source_ip>
<count>1523</count>
<policy_evaluated>
<disposition>none</disposition>
<dkim>pass</dkim>
<spf>fail</spf>
</policy_evaluated>
</row>
<identifiers>
<header_from>yourapp.com</header_from>
</identifiers>
</record>That says 1,523 messages from 198.51.100.42 claimed to be from yourapp.com; DKIM passed, SPF failed. Looking up the IP tells you whether it's a legitimate forwarder or
someone spoofing you. Once every legitimate sender passes, move to p=quarantine (failing
mail routed to spam), then to p=reject (failing mail refused outright). The full
progression usually takes four to eight weeks.
Verifying everything is working
Two checks confirm the records are live and doing their job. First, read them straight out of DNS
with dig:
# Check SPF
dig TXT yourapp.com +short | grep spf
# Check DKIM (replace 'lettr' with your selector)
dig TXT lettr._domainkey.yourapp.com +short
# Check DMARC
dig TXT _dmarc.yourapp.com +shortThen send yourself a test email and open "Show original" in Gmail. The recipient adds an Authentication-Results header recording all three verdicts:
Authentication-Results: mx.google.com;
dkim=pass header.d=yourapp.com header.s=lettr;
spf=pass smtp.mailfrom=bounce+abc123@mail.yourapp.com;
dmarc=pass (p=REJECT dis=NONE) header.from=yourapp.comThe goal is dkim=pass, spf=pass, and dmarc=pass. A dmarc=fail with SPF and DKIM both passing almost always means an alignment mismatch, so
check the domains first. If you're using Lettr, the domain verification flow runs these
checks and flags missing records before you start sending.
FAQ
Do I need all three of SPF, DKIM, and DMARC?
From address and says what to do on failure. Gmail and Yahoo
require all three for bulk senders.What does DMARC alignment mean?
From header. Without it, a message could pass SPF for one domain while
displaying another in the From line, which is exactly the spoofing DMARC exists to
stop. DMARC passes only when at least one of the two authenticates and aligns with the From domain.Why should I start DMARC at p=none?
p=none reports failures without blocking any mail. The
aggregate reports surface every service sending as your domain, including ones you forgot about,
so you can fix legitimate senders before enforcement could send their mail to spam. Review the
reports for a few weeks, then move to p=quarantine and p=reject.How do I check whether SPF, DKIM, and DMARC are working?
dig, then send a test email and open "Show
original" in Gmail. The receiver records the verdicts in an Authentication-Results header, and the goal is dkim=pass, spf=pass, and dmarc=pass. A dmarc=fail while SPF and DKIM
pass almost always points to an alignment mismatch.Bottom line
Gmail and Yahoo both require all three for bulk senders, and inbox providers increasingly penalize domains that lack them. Without these records, anyone can send mail that appears to come from your domain, and recipients won't inspect the headers. They will assume it was you.
The actual work is a handful of DNS records and a few weeks of monitoring. Set up SPF, let your
provider handle DKIM, deploy DMARC at p=none, read the reports, and ratchet up
enforcement as confidence grows.
Or skip the manual setup entirely. Create a free Lettr account, add your sending domain, and the SPF and DKIM records are generated and verified for you, with DMARC reporting built in.