Most deliverability problems are not spam filters being unfair. They are three DNS records that exist, look right, and do not do what their owner thinks.
The three answer different questions. SPF says which servers may send for your domain. DKIM signs the message so a receiver can prove it was not altered. DMARC ties both back to the address your recipient actually sees, and tells receivers what to do when neither ties.
That last part is where working setups fail, so it is worth taking first.
Alignment is the whole game
DMARC does not ask "did SPF pass". It asks whether the domain that passed is the same domain as the one in the From: header, the only address the recipient ever sees.
A message passes DMARC when either of these holds:
- SPF passed, and the domain it authenticated aligns with the From domain.
- DKIM passed, and the
d=domain in the signature aligns with the From domain.
The SPF check authenticates the envelope sender, the Return-Path, not the From header. Send through a marketing platform that uses its own bounce domain and SPF passes for that platform and aligns with nothing. The mail is authenticated and still fails DMARC.
This is why DKIM matters more than it looks. It is also why the fix for a failing platform is usually to sign with your own domain, not to add another include: to SPF.
SPF, and the limit that voids it
One TXT record at the apex, listing who may send.
v=spf1 include:_spf.example-provider.com ip4:203.0.113.10 -all
Two things to get right.
The ten lookup limit. SPF allows at most ten DNS-querying mechanisms while evaluating a record. Every include, a, mx, ptr, exists and redirect counts, and every include drags in whatever that provider's record expands to. Cross the limit and the result is a permanent error, which most receivers treat as no SPF at all. Four or five providers is enough to cross it without a single obvious mistake in your record.
Check the expansion, do not count the words in your own record.
-all or ~all. Hard fail says anything else is forged. Soft fail says treat it as suspicious. Start on ~all while you are still discovering who sends on your behalf, and move to -all once the reports are quiet. Leaving it on ~all forever is the common half-measure.
SPF also breaks on forwarding. A message forwarded by a mailing list arrives from the list's server, which is not in your record, and SPF fails through no fault of anyone. DKIM survives that trip, which is the second reason to sign.
DKIM
Your mail server signs outgoing messages with a private key. The public half goes in DNS under a selector you choose.
selector1._domainkey.example.com TXT "v=DKIM1; k=rsa; p=MIIBIjANBgkq..."
Use 2048-bit keys. Publish a second selector before you rotate, switch signing to it, and remove the old one a few days later, so no message is ever signed with a key that is not published.
Sign with your own domain wherever a provider allows it. Every platform that sends on your behalf, invoicing, marketing, ticketing, should sign as d=example.com. That single change fixes more alignment failures than any SPF edit.
DMARC, starting at none
_dmarc.example.com TXT "v=DMARC1; p=none; rua=mailto:dmarc@example.com; fo=1"
p=none changes nothing about delivery. It asks receivers to send you aggregate reports about what they saw. That is the point: you cannot safely enforce a policy over senders you have not enumerated, and almost nobody can list them correctly from memory. Invoicing, the CRM, a monitoring tool, a form on the website, an old server somebody set up in 2019.
Run it for two to four weeks and read the reports. They will show you sources you forgot.
Then tighten in two steps: p=quarantine, watch, then p=reject. Use pct= to ramp a percentage of mail into the new policy if the volume makes you nervous.
By default both alignment modes are relaxed, which means a subdomain aligns with its parent. aspf=s and adkim=s demand an exact match. Relaxed is the right default; strict is for people who know why they want it.
The order that works
- Publish SPF with
~alland check the expansion stays under ten lookups. - Turn on DKIM signing everywhere, with your own domain in
d=. - Publish DMARC at
p=nonewithrua. - Read reports until nothing surprising appears.
- Move SPF to
-all, DMARC toquarantine, thenreject.
Skipping step 3 and going straight to p=reject is how a company discovers, on a Monday, that its invoices have been rejected since Friday.
If mail is currently landing in spam and you would rather have it diagnosed than described, that is part of our security and compliance work, and our free mail tools will check the three records for you first.
Talk to the engineer who will own your stack.
No account managers, no offshore handoff. Senior DevOps, direct. Tell us what you are dealing with and you get a straight answer.
Related Articles
How to Detect and Respond to a Compromised Linux Server
A practical incident response guide for Linux servers: identifying signs of compromise, initial triage, evidence preservation, containment, rootkit detection, and writing an incident report.
SecurityEleven npm Packages Compromised in a 53 Minute Attack That Steals Every Credential Your Build Host Can Reach
On August 4, 2026 a worm pushed malicious versions of eleven npm caching packages inside a 53 minute window, harvesting npm tokens, GitHub PATs, AWS credentials, Kubernetes service account tokens and SSH keys. The headline was keyv and its 604 million monthly downloads, but keyv was the safest package on the list: its malicious release was a major version bump that no caret range accepts. The other ten were patch bumps, silently eligible for every dependency range in the ecosystem. That distinction, not the download count, decided who got hit. This is a practical guide to the defenses that actually change the outcome: what your semver range really grants, why npm install and npm ci are not interchangeable, when to disable install scripts and what breaks when you do, and how to check a tree you already have.
SecurityAWS WAF Configuration for Web Application Security
Deploy and configure AWS WAF with managed rule groups, custom rules, rate limiting, and bot control to protect web applications from common threats.