If mail from your domain lands in spam, or you have never checked what protects your domain from being spoofed, this guide gets you to a correct SPF, DKIM and DMARC setup. It works whether you send through your own server or through a provider such as Google Workspace or Microsoft 365; only the DKIM signing step differs.
How the three fit together
Every message has two sender addresses. The envelope sender (also called Return-Path or MAIL FROM) is used by servers to route bounces. The header From is what people see in their mail client. Spammers forge the second one.
- SPF checks whether the server that delivered the message is allowed to send for the envelope domain.
- DKIM adds a cryptographic signature to each message. The receiver fetches your public key from DNS and
checks that the signed headers and body were not changed. The signature names a domain (
d=). - DMARC passes when SPF or DKIM passes and the domain it checked matches the header From domain. This match is called alignment. DMARC also tells receivers what to do with failures and where to send reports.
Without DMARC, a message can pass SPF for some unrelated domain while showing your address in From. DMARC closes that gap.
Before you start: list every sender
Write down everything that sends mail as your domain: your mail server or provider, the website’s contact form, newsletter and CRM tools, invoicing, helpdesk, monitoring alerts. Anything missing from this list will start failing once DMARC is enforced. You also need access to your domain’s DNS.
Step 1: SPF
Publish one TXT record on the domain itself. The simplest useful record allows the servers in your MX records:
example.com. IN TXT "v=spf1 mx ~all"
Typical additions:
"v=spf1 mx ip4:203.0.113.10 include:_spf.google.com ~all"
mx,a,ip4:,ip6:andinclude:add allowed senders;include:pulls in a provider’s own SPF record.~all(softfail) or-all(fail) ends the record. With DMARC in place, the DMARC policy decides what happens to failures, so~allis a safe default while you are testing.- Never use
+all, and avoid the deprecatedptrmechanism.
Two rules break SPF silently:
- Only one SPF record per name. Two
v=spf1records make SPF fail with a permanent error. Merge them. - At most 10 DNS lookups. Every
include,a,mx,existsandredirectcounts, including the ones inside included records.ip4,ip6andalldo not count. Too many includes is the most common reason a long-standing SPF record stops working after a new tool is added.
A domain that never sends mail should say so, which protects it from being used in spoofing:
parked.example. IN TXT "v=spf1 -all"
_dmarc.parked.example. IN TXT "v=DMARC1; p=reject"
Check what is published:
dig +short TXT example.com | grep spf1
Step 2: DKIM
DKIM needs a key pair. The private key stays on the signing server; the public key goes into DNS under a
selector, a name you choose so that keys can be rotated later (for example mail2026).
If you use a mail provider
Google Workspace, Microsoft 365 and most newsletter tools generate the key for you. Find the DKIM or “domain
authentication” section in their admin panel, publish the record they show, then switch signing on. For tools
that send on your behalf, set up their custom domain authentication: if they sign with their own domain
(d=mailtool.example), DKIM passes but is not aligned with your From address and does not help DMARC.
If you run your own mail server
Generate a 2048-bit key and print the public part in the form DNS needs:
openssl genrsa -out example.com.key 2048
openssl rsa -in example.com.key -pubout -outform der 2>/dev/null | base64 -w0
Publish it as a TXT record. A 2048-bit key is longer than the 255-character limit of a single TXT string, so split it into several quoted strings; receivers join them. Many DNS panels do this for you.
mail2026._domainkey.example.com. IN TXT ( "v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAo8/PQ..."
"...rest of the base64 key..." )
On Debian with Exim, the remote_smtp transport reads DKIM settings from macros. Put the key where Exim can
read it (root:Debian-exim, mode 0640) and add to /etc/exim4/exim4.conf.localmacros:
DKIM_CANON = relaxed
DKIM_DOMAIN = ${lc:${domain:$h_from:}}
DKIM_SELECTOR = mail2026
DKIM_PRIVATE_KEY = /etc/exim4/dkim/${dkim_domain}.key
Then regenerate the configuration and restart:
update-exim4.conf && systemctl restart exim4
For several domains on one server, look the domain and selector up from a small map file instead of fixing them in the macros; that is how the server behind this site signs for each of its domains. With Postfix the usual route is OpenDKIM as a milter; the DNS side is identical.
Exim signs only mail that leaves the server through remote_smtp, so local deliveries carry no signature.
Test with an outside mailbox (step 4).
Check the record:
dig +short TXT mail2026._domainkey.example.com
Step 3: DMARC
Start in monitoring mode. p=none changes nothing for delivery but asks receivers to send you daily
aggregate reports:
_dmarc.example.com. IN TXT "v=DMARC1; p=none; rua=mailto:dmarc@example.com; adkim=r; aspf=r"
p: what to do with failing mail:none,quarantine(spam folder) orreject.rua: where aggregate reports go. They are XML files, one per receiver per day.adkim/aspf:r(relaxed) accepts subdomains as aligned (news.example.comforexample.com),s(strict) requires an exact match. Relaxed is the usual choice.sp: a separate policy for subdomains, if needed.
If reports should go to an address on another domain, that domain must agree to receive them by publishing
example.com._report._dmarc.reports.example.net IN TXT "v=DMARC1". Otherwise receivers skip the reports.
Step 4: verify with a real message
DNS lookups only show that the records exist. To see that everything works together, send a message from your domain to an outside mailbox, for example a Gmail address, and open the full headers (“Show original” in Gmail). You are looking for something like:
Authentication-Results: mx.google.com;
dkim=pass header.i=@example.com header.s=mail2026;
spf=pass smtp.mailfrom=noreply@example.com;
dmarc=pass (p=NONE sp=NONE dis=NONE) header.from=example.com
Check three things: dkim=pass names your domain, spf=pass is present, and dmarc=pass. Repeat the
test for every sender on your list from the start of this guide.
What alignment looks like in practice
When we tested the setup for this site, a message sent with sendmail by a local system user produced:
dkim=pass header.i=@h4me.net header.s=h4me2026;
spf=pass smtp.mailfrom=user@another-domain.example;
dmarc=pass (p=NONE sp=NONE dis=NONE) header.from=h4me.net
SPF passed, but for the envelope domain of the local user, not for h4me.net, so it was not aligned.
DMARC still passed because DKIM was signed with d=h4me.net and that is aligned with the From address.
One aligned pass is enough for DMARC. This is also why DKIM is worth setting up even when SPF looks fine:
scripts, cron jobs and forwarded mail often carry an envelope sender you did not plan for. If you want SPF to
align as well, send with an envelope sender in your domain, for example by submitting through SMTP as
noreply@example.com instead of calling sendmail as a system user.
Step 5: from p=none to enforcement
- Leave
p=nonefor two to four weeks and read the aggregate reports. Small volumes are readable by hand; larger ones are easier with a report parser. - For every source that fails: if it is legitimate, add it to SPF or set up DKIM for it; if it is not yours, it is exactly what DMARC will block.
- Switch to
p=quarantine. Watch the reports and complaints for another few weeks. - Switch to
p=rejectonce nothing legitimate fails.
What the big mailbox providers require
Since February 2024 Gmail and Yahoo require every sender to pass SPF or DKIM, and bulk senders (around 5,000 messages a day or more) to have SPF, DKIM and a DMARC record with alignment, plus easy unsubscribe for marketing mail. Microsoft announced similar rules for Outlook.com bulk senders in 2025. A small business sending ordinary mail is not a bulk sender, but the same setup is what keeps its mail out of spam.
Common mistakes
- Two SPF records, often one added by a new tool next to the old one. SPF then fails for all mail.
- More than 10 lookups in SPF after adding several services.
- A forgotten sender: the newsletter tool or invoicing system starts failing after
p=quarantine. - A broken DKIM record: stray quotes, line breaks or a truncated key pasted into a DNS panel. Compare
digoutput with the key you generated. - Provider signs with its own domain: DKIM passes but does not align. Configure custom domain authentication in the tool.
- Forwarding: forwarded mail usually fails SPF because the forwarding server is not in your record. DKIM survives forwarding as long as the message is not modified, which is why DKIM matters even if SPF is perfect.
p=rejecton day one: legitimate mail you did not know about disappears without a trace.
A live example
This site’s domain uses the setup described here. You can inspect it yourself:
dig +short TXT h4me.net | grep spf1
dig +short TXT h4me2026._domainkey.h4me.net
dig +short TXT _dmarc.h4me.net
At the time of writing it is in the monitoring stage (p=none), exactly as recommended above, and will move
to quarantine once the reports confirm that only our own server sends as h4me.net.