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

MechanismPurposeRecord TypeRequired?
SPFDeclares which IPs can send mail for your domainTXTYes
DKIMCryptographically signs outgoing messagesTXTYes
DMARCTells receivers what to do when SPF/DKIM failTXTYes
PTRMaps your IP back to your hostnamePTRYes
MTA-STSEnforces TLS for incoming connectionsTXT + HTTPSRecommended
DANE/TLSAPins TLS certificates via DNSTLSAOptional

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"
ComponentMeaning
v=spf1SPF version identifier
ip4:203.0.113.10Your mail server’s IPv4 address
-allHard 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: and redirect=)
  • 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:

ServerDKIM Key Location
mailcowAdmin UI → Configuration → ARC/DKIM Keys
MailuAdmin UI → Mail Domains → DKIM
Docker Mailserversetup.sh config dkim generates keys
StalwartAdmin UI → Settings → DKIM
Mail-in-a-BoxAutomatically configured during setup

The DNS record looks like this:

default._domainkey.example.com.  IN  TXT  "v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA..."
ComponentMeaning
default._domainkeySelector name + _domainkey subdomain
v=DKIM1DKIM version
k=rsaKey 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"
ComponentMeaning
v=DMARC1DMARC version
p=rejectPolicy: reject messages that fail both SPF and DKIM
rua=mailto:...Where to send aggregate reports
adkim=sStrict DKIM alignment (domain must match exactly)
aspf=sStrict SPF alignment

Policy options:

PolicyEffectWhen to Use
p=noneMonitor only — don’t reject anythingInitial setup, while testing
p=quarantineMark failures as spamAfter confirming SPF/DKIM work
p=rejectReject failures outrightProduction — the goal

Recommended rollout:

  1. Start with p=none for 1-2 weeks while monitoring reports
  2. Move to p=quarantine for another week
  3. Move to p=reject once 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:

ToolURLWhat It Checks
mail-tester.comhttps://www.mail-tester.comOverall deliverability score (aim for 10/10)
MXToolboxhttps://mxtoolbox.com/SuperTool.aspxSPF, DKIM, DMARC, blacklist, PTR
DKIM Validatorhttps://dkimvalidator.comDKIM signature validation
Learndmarc.comhttps://learndmarc.comInteractive 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:

  1. Start by sending to your own accounts (Gmail, Outlook)
  2. Send slowly — don’t blast hundreds of emails on day 1
  3. Ensure recipients engage (open, reply) — this builds positive signals
  4. Monitor DMARC reports for any issues

Next Steps

Comments