Email Deliverability: SPF, DKIM, DMARC Setup
Why Deliverability Matters
Running your own email server means nothing if your messages land in recipients’ spam folders. Major providers — Gmail, Outlook, Yahoo — reject or flag mail from servers that lack proper authentication records. Three DNS-based mechanisms prove your emails are legitimate: SPF, DKIM, and DMARC. Without all three, expect 50-90% of your outgoing mail to be flagged as spam.
This guide covers what each mechanism does, how to configure the DNS records, and how to verify everything works.
Prerequisites
- A domain with DNS access (Cloudflare, Route 53, or any DNS provider)
- A self-hosted email server (mailcow, Mailu, Stalwart, or Docker Mailserver)
- A static IP address (not shared hosting)
- Reverse DNS (PTR record) configured for your server IP
The Three Pillars
| Mechanism | Purpose | Record Type | Required? |
|---|---|---|---|
| SPF | Declares which IPs can send mail for your domain | TXT | Yes |
| DKIM | Cryptographically signs outgoing messages | TXT | Yes |
| DMARC | Tells receivers what to do when SPF/DKIM fail | TXT | Yes |
| PTR | Maps your IP back to your hostname | PTR | Yes |
| MTA-STS | Enforces TLS for incoming connections | TXT + HTTPS | Recommended |
| DANE/TLSA | Pins TLS certificates via DNS | TLSA | Optional |
Step 1: Reverse DNS (PTR Record)
Before anything else, set up a PTR record. This maps your server’s IP address back to your mail hostname. Without it, most major providers reject your mail outright.
Where to configure: Your VPS or hosting provider’s control panel (not your domain’s DNS). Hetzner, DigitalOcean, Vultr, and Linode all support PTR records in their dashboards.
Set the PTR record for your server IP to match your MX hostname:
203.0.113.10 → mail.example.com
Verify:
dig -x 203.0.113.10 +short
# Should return: mail.example.com.
The PTR hostname must match your server’s HELO/EHLO hostname and resolve back to the same IP (forward-confirmed reverse DNS).
Step 2: SPF (Sender Policy Framework)
SPF tells receiving servers which IP addresses are authorized to send email for your domain.
Add a TXT record on your domain:
example.com. IN TXT "v=spf1 ip4:203.0.113.10 -all"
| Component | Meaning |
|---|---|
v=spf1 | SPF version identifier |
ip4:203.0.113.10 | Your mail server’s IPv4 address |
-all | Hard fail — reject mail from any other IP |
Common SPF patterns:
# Single server
"v=spf1 ip4:203.0.113.10 -all"
# Server with IPv6
"v=spf1 ip4:203.0.113.10 ip6:2001:db8::1 -all"
# Multiple servers
"v=spf1 ip4:203.0.113.10 ip4:198.51.100.20 -all"
# Include third-party sender (e.g., transactional email)
"v=spf1 ip4:203.0.113.10 include:spf.resend.com -all"
Use -all (hard fail), not ~all (soft fail). Soft fail provides weaker protection and some providers treat it as no SPF at all.
Verify:
dig TXT example.com +short | grep spf
# Should show your SPF record
# Test from another machine
nslookup -type=TXT example.com
Rules:
- Only ONE SPF record per domain (multiple records cause failures)
- Maximum 10 DNS lookups in the SPF chain (including
include:andredirect=) - Keep it simple — list your IPs directly when possible
Step 3: DKIM (DomainKeys Identified Mail)
DKIM adds a cryptographic signature to every outgoing email. Receiving servers verify the signature against your DNS public key. This proves the message wasn’t altered in transit and came from your server.
Most self-hosted email servers generate DKIM keys automatically:
| Server | DKIM Key Location |
|---|---|
| mailcow | Admin UI → Configuration → ARC/DKIM Keys |
| Mailu | Admin UI → Mail Domains → DKIM |
| Docker Mailserver | setup.sh config dkim generates keys |
| Stalwart | Admin UI → Settings → DKIM |
| Mail-in-a-Box | Automatically configured during setup |
The DNS record looks like this:
default._domainkey.example.com. IN TXT "v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA..."
| Component | Meaning |
|---|---|
default._domainkey | Selector name + _domainkey subdomain |
v=DKIM1 | DKIM version |
k=rsa | Key type (RSA or Ed25519) |
p=MIIBIj... | Base64-encoded public key |
The selector (default above) is chosen by your mail server. Check your server’s admin panel for the exact selector name — common values are default, dkim, mail, or <year>.
Verify:
# Replace "default" with your actual selector
dig TXT default._domainkey.example.com +short
Key size: Use 2048-bit RSA minimum. Some DNS providers have TXT record length limits — if your key doesn’t fit, split it across multiple strings within the same TXT record (most mail servers handle this automatically).
Step 4: DMARC (Domain-based Message Authentication, Reporting, and Conformance)
DMARC ties SPF and DKIM together and tells receiving servers what to do when authentication fails. It also enables aggregate reporting — you get daily reports showing who’s sending email as your domain.
Add a TXT record:
_dmarc.example.com. IN TXT "v=DMARC1; p=reject; rua=mailto:[email protected]; adkim=s; aspf=s"
| Component | Meaning |
|---|---|
v=DMARC1 | DMARC version |
p=reject | Policy: reject messages that fail both SPF and DKIM |
rua=mailto:... | Where to send aggregate reports |
adkim=s | Strict DKIM alignment (domain must match exactly) |
aspf=s | Strict SPF alignment |
Policy options:
| Policy | Effect | When to Use |
|---|---|---|
p=none | Monitor only — don’t reject anything | Initial setup, while testing |
p=quarantine | Mark failures as spam | After confirming SPF/DKIM work |
p=reject | Reject failures outright | Production — the goal |
Recommended rollout:
- Start with
p=nonefor 1-2 weeks while monitoring reports - Move to
p=quarantinefor another week - Move to
p=rejectonce you’re confident everything works
Verify:
dig TXT _dmarc.example.com +short
Step 5: MTA-STS (Mail Transfer Agent Strict Transport Security)
MTA-STS tells other mail servers to use TLS when connecting to yours. Without it, SMTP connections can be downgraded to unencrypted (STARTTLS stripping attacks).
Two components required:
DNS Record
_mta-sts.example.com. IN TXT "v=STSv1; id=20260304"
Change the id value whenever you update the policy (use the date as a convention).
Policy File
Host a file at https://mta-sts.example.com/.well-known/mta-sts.txt:
version: STSv1
mode: enforce
mx: mail.example.com
max_age: 604800
This requires HTTPS on the mta-sts subdomain. Use your existing reverse proxy or a Cloudflare Pages site.
Also add a TLSRPT record for TLS failure reports:
_smtp._tls.example.com. IN TXT "v=TLSRPTv1; rua=mailto:[email protected]"
Verification Checklist
After configuring everything, verify with these tools:
| Tool | URL | What It Checks |
|---|---|---|
| mail-tester.com | https://www.mail-tester.com | Overall deliverability score (aim for 10/10) |
| MXToolbox | https://mxtoolbox.com/SuperTool.aspx | SPF, DKIM, DMARC, blacklist, PTR |
| DKIM Validator | https://dkimvalidator.com | DKIM signature validation |
| Learndmarc.com | https://learndmarc.com | Interactive SPF/DKIM/DMARC test |
Quick command-line checks:
# Check MX record
dig MX example.com +short
# Check SPF
dig TXT example.com +short | grep spf
# Check DKIM (replace selector)
dig TXT default._domainkey.example.com +short
# Check DMARC
dig TXT _dmarc.example.com +short
# Check PTR
dig -x YOUR_SERVER_IP +short
# Send test email and check headers
# Look for: spf=pass, dkim=pass, dmarc=pass
Common Mistakes
Multiple SPF Records
Having two TXT records starting with v=spf1 on the same domain causes SPF to fail entirely. Merge them into one record.
Wrong DKIM Selector
The selector in DNS must match the selector your mail server uses when signing. Check your server’s DKIM configuration for the exact selector name.
SPF Lookup Limit Exceeded
SPF allows a maximum of 10 DNS lookups. Each include:, a:, mx:, and redirect= mechanism counts as a lookup. Use ip4:/ip6: (which don’t count) to stay under the limit.
Missing PTR Record
PTR records are configured at your hosting provider, not your domain’s DNS. This is the most commonly missed step.
DMARC Set to None Permanently
p=none provides zero protection — it only monitors. Move to p=reject once you’ve confirmed SPF and DKIM are passing consistently.
DNS Propagation Delay
DNS changes can take up to 48 hours to propagate globally. Wait at least 1 hour before testing, and re-test after 24 hours if initial results are inconsistent.
IP Reputation
Even with perfect SPF, DKIM, and DMARC, your emails can still land in spam if your IP has a bad reputation. Check these:
# Check if your IP is blacklisted
# Use MXToolbox: https://mxtoolbox.com/blacklists.aspx
# Check major blacklists directly
dig +short YOUR_IP.zen.spamhaus.org
dig +short YOUR_IP.bl.spamcop.net
If your IP is blacklisted:
- Request delisting from the specific blacklist
- Consider a new IP from your VPS provider
- Some VPS providers (especially cheap ones) have entire IP ranges blacklisted — this is a real risk of self-hosted email
Building reputation on a new IP:
- Start by sending to your own accounts (Gmail, Outlook)
- Send slowly — don’t blast hundreds of emails on day 1
- Ensure recipients engage (open, reply) — this builds positive signals
- Monitor DMARC reports for any issues
Next Steps
- Set up mailcow — the most popular Docker email server
- Set up Mailu — lightweight Docker email alternative
- Self-Hosted Email: Is It Worth It? — honest assessment of the trade-offs
- Best Self-Hosted Email Servers
- Replace Gmail
- DNS Basics for Self-Hosting
- Docker Compose Basics
Related
Get self-hosting tips in your inbox
Get the Docker Compose configs, hardware picks, and setup shortcuts we don't put in articles. Weekly. No spam.
Comments