SPF, DKIM and DMARC: stop your server's email going to spam
Your contact form works, but the messages land in spam, or never arrive at all. Mailbox providers judge mail on proof of identity and reputation, and a fresh server has neither. Here is how SPF, DKIM, DMARC and reverse DNS fit together, how to test them, and the mistakes that quietly break delivery.
You move your sites to your own server, everything loads fast, and then a client calls: "We haven't had a single contact-form enquiry in two weeks." The form works. The mail is sent. It just lands in spam — or gets silently dropped. This is the most common surprise of self-hosting, and it has nothing to do with your content. Gmail, Outlook and Yahoo decide where your mail goes based on two things: can you prove you're allowed to send for this domain, and does the sending IP have a good reputation? A brand-new server starts with neither. Here's how to fix the first and protect the second.
Why mail from your own server lands in spam
Anyone can put any address in the From: line of an email; the protocol never checked. Spammers exploited that for decades, so receivers now demand proof. Since 2024, Google and Yahoo formally require it: every sender needs SPF or DKIM plus valid forward and reverse DNS, and anyone sending in bulk (around 5,000 messages a day to Gmail) needs SPF, DKIM and DMARC. In practice, a server without all three is treated as suspicious from the first message.
There are four pieces, and they all live in DNS:
- SPF — which servers may send mail for your domain.
- DKIM — a cryptographic signature proving the message wasn't forged or altered.
- DMARC — the policy tying the two to the visible
From:address, plus reporting. - Reverse DNS (PTR) — the IP's own name, which must match the server's hostname.
SPF: the list of allowed senders
SPF is a TXT record on your domain listing the IPs and services allowed to send for it:
example.com. TXT "v=spf1 a mx include:_spf.google.com ~all"
a and mx allow the IPs your domain's A and MX records point to; include: pulls in a third-party sender's list; ~all says "anything else is suspicious" (softfail), while -all says "reject it" (hardfail). Start with ~all until you're sure the list is complete.
Three rules people break constantly:
- One SPF record per domain. Two
v=spf1TXT records don't merge; they make SPF fail entirely. Add new senders to the existing record. - Maximum 10 DNS lookups. Every
include,a,mxandredirectcounts, including the nested ones inside each include. Exceed 10 and SPF returns a permanent error. Remove senders you no longer use. - SPF checks the envelope sender, not the
From:line. That's why it isn't enough on its own, and why DMARC exists.
DKIM: a signature on every message
With DKIM, your mail server signs each outgoing message with a private key. The matching public key is published in DNS under a selector:
mail._domainkey.example.com. TXT "v=DKIM1; k=rsa; p=MIIBIjANBgkqh..."
The receiver fetches the key, verifies the signature, and knows two things: the message was sent by someone holding your key, and the signed headers and body weren't changed in transit. On Linux, OpenDKIM plugged into Postfix as a milter is the classic setup.
- Use 2048-bit keys. 1024-bit is the bare minimum and increasingly frowned upon.
- Long keys need splitting. A 2048-bit key exceeds the 255-character limit for a single TXT string, so it must be published as several quoted strings in one record. Many DNS UIs do this for you; hand-written zone files don't.
- Selectors allow rotation. Publish a new key under a new selector, switch signing over, and remove the old one later — no gap in coverage.
DMARC: the policy that ties it together
DMARC checks that SPF or DKIM passed and that the domain they authenticated aligns with the domain in the visible From: address. It's what stops someone sending as ceo@example.com from their own server with valid SPF for their domain.
_dmarc.example.com. TXT "v=DMARC1; p=none; rua=mailto:dmarc@example.com"
The p= policy tells receivers what to do with mail that fails: none (just report), quarantine (spam folder) or reject. rua= is where receivers send daily aggregate reports — XML summaries of who sent mail claiming to be you, and whether it passed.
Roll it out in stages:
- Start at
p=noneand read the reports for two to four weeks. You'll discover senders you forgot: the newsletter tool, the invoicing system, the CRM. - Fix each legitimate sender (add it to SPF, enable DKIM signing in its settings).
- Move to
p=quarantine, thenp=rejectonce the reports are clean.
Reverse DNS, HELO and the IP itself
The receiving server also looks at the connection. Your server's IP should have a PTR record (reverse DNS) that resolves to a hostname — say mail.example.com — which in turn resolves back to the same IP. The name the server announces in its SMTP greeting (HELO) should be that same hostname. PTR records are set at your hosting provider, usually in the cloud console next to the IP, not in your domain's DNS.
Two more checks before you blame your records:
- Is port 25 open? Many cloud providers, Hetzner included, block outbound port 25 on new accounts until you ask for it to be opened. Mail then queues up and never leaves.
- Is the IP on a blocklist? Recycled cloud IPs sometimes carry a previous tenant's history. Check it against Spamhaus and similar lists before you go live.
How to test properly
Don't guess — check what the world sees:
dig +short TXT example.com
dig +short TXT mail._domainkey.example.com
dig +short TXT _dmarc.example.com
dig +short -x 203.0.113.10
Then send a real message to a Gmail address and open Show original. You want three lines reading SPF: PASS, DKIM: PASS and DMARC: PASS. The Authentication-Results header gives the details when something fails. A score service like mail-tester.com checks content, blocklists and authentication in one go, and swaks lets you send test messages from the command line without touching your app.
Mistakes that quietly break delivery
- Two SPF records after adding a new service. SPF fails for everything.
- The app sends as the wrong domain. WordPress defaults to
wordpress@yourdomain, and many scripts send aswww-data@hostname. TheFrom:must be a domain you've authenticated. - Forgetting third-party senders — newsletters and transactional mail services each need their own SPF include and DKIM setup.
- Jumping straight to
p=rejectbefore reading reports. Your invoices disappear along with the spam. - Unaligned DKIM. A service that signs with its domain passes DKIM but fails DMARC. Set up custom-domain signing in its dashboard.
- Forwarding breaks SPF. When mail is forwarded, the forwarding server isn't in the original sender's SPF. DKIM survives forwarding, which is one more reason never to skip it.
- Sending a burst from a cold IP. A new IP that suddenly sends thousands of messages looks like a compromised box. Ramp up gradually.
How ShadowPanel automates the DNS side
ShadowPanel is a self-hosted hosting control panel with mail hosting built in (Postfix with an OpenDKIM milter), and the deliverability setup is one button instead of an evening of key generation and TXT-record copying.
- One-click DKIM. The email page has a deliverability section per domain. One click generates a 2048-bit DKIM key, registers it in OpenDKIM's signing tables, and starts signing outgoing mail for that domain.
- SPF and DMARC records added for you. If the domain's DNS zone is managed by the panel — on its own nameserver, or through a connected Hetzner DNS or Cloudflare account — the panel inserts the DKIM record, an SPF record (
v=spf1 a mx ~all) and a starter DMARC policy (p=none, reports topostmaster@your domain). Existing SPF and DMARC records are detected and left alone, so it never creates the duplicate-SPF trap. Long DKIM keys are split correctly for the local nameserver. - External DNS? Copy and paste. If the zone lives elsewhere, the panel shows the exact records to add at your DNS provider, and a status check shows which records are present.
- Mailboxes and aliases. Create mailboxes and forwarders/aliases per domain; the panel refuses an alias that would silently shadow an existing mailbox.
What stays your job: setting the PTR record at your hosting provider, getting port 25 opened if needed, and tightening DMARC from p=none to quarantine or reject once the reports look clean. If you're moving mail off shared hosting at the same time, migrating from cPanel to a VPS without downtime covers the mailbox side, and installing a hosting panel on a Hetzner VPS covers the server.
The bottom line
Mail from a new server goes to spam because receivers can't verify it, not because it's bad mail. Publish one clean SPF record, sign everything with a 2048-bit DKIM key, start DMARC at p=none and tighten it once the reports are clean, set a matching PTR record, and test with real headers instead of hope. ShadowPanel turns the DKIM, SPF and DMARC part into a single click for domains it manages, and hands you copy-ready records for the ones it doesn't.
curl -fsSL https://shadowpanel.de/install.sh | bash
Related articles
How to manage many WordPress sites: updates, backups, security
Ten WordPress sites are a hobby; fifty are a job. Here is a repeatable routine for keeping a whole fleet updated and sec...
Security advisory: critical PHP vulnerabilities CVE-2026-109618 and CVE-2026-109619
Two critical, unauthenticated remote code execution flaws in PHP core were disclosed on 21 October 2026. Every server th...
How to migrate from cPanel to a VPS without downtime
Leaving shared hosting is mostly a planning problem, not a technical one. This guide walks through the inventory, the DN...