[ guide ]

SPF, DKIM and DMARC: set up email authentication for your domain

Published
Last tested
· Debian 13, Exim 4.98, BIND 9.20

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: and include: 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 ~all is a safe default while you are testing.
  • Never use +all, and avoid the deprecated ptr mechanism.

Two rules break SPF silently:

  1. Only one SPF record per name. Two v=spf1 records make SPF fail with a permanent error. Merge them.
  2. At most 10 DNS lookups. Every include, a, mx, exists and redirect counts, including the ones inside included records. ip4, ip6 and all do 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) or reject.
  • rua: where aggregate reports go. They are XML files, one per receiver per day.
  • adkim / aspf: r (relaxed) accepts subdomains as aligned (news.example.com for example.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

  1. Leave p=none for two to four weeks and read the aggregate reports. Small volumes are readable by hand; larger ones are easier with a report parser.
  2. 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.
  3. Switch to p=quarantine. Watch the reports and complaints for another few weeks.
  4. Switch to p=reject once 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 dig output 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=reject on 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.

Tell us what you need.

Describe your project, stack and timeline. We reply with questions or a proposal.

Discuss your project