SPF and DMARC are two DNS records that tell other mail servers how to treat messages claiming to come from your domain. SPF lists the servers allowed to send mail for the domain. DMARC ties SPF and DKIM to the visible From address, tells receivers what to do with mail that fails, and sends you reports about who is sending as your domain. In this tutorial you will inventory your mail sources, publish an SPF record and a DMARC record, verify both, and tighten the DMARC policy step by step until spoofed mail is rejected.

Prerequisites

To follow this tutorial, you need:

  • A domain you control, with access to its DNS zone. This guide uses your_domain; replace it with your own.
  • A mail server that sends for the domain, for example Postfix on a CubePath VPS running Ubuntu 24.04, with its public IP address. This guide uses 203.0.113.10 as your_server_ip.
  • DKIM signing already working, as described in How to Set Up DKIM with OpenDKIM and Postfix. DMARC works with SPF alone, but forwarded mail usually breaks SPF, so DKIM is what keeps legitimate mail passing.
  • The dig command, provided by the bind9-dnsutils package on Ubuntu (sudo apt install bind9-dnsutils).

How SPF, DKIM and DMARC fit together

RecordWhere it livesWhat it checks
SPFTXT on your_domainThe sending IP is authorized for the envelope sender domain (the MAIL FROM, also called Return-Path)
DKIMTXT on selector._domainkey.your_domainThe message carries a valid signature from the domain
DMARCTXT on _dmarc.your_domainSPF or DKIM passed and the domain that passed matches the From header domain

The last point is called alignment, and it is what makes DMARC useful. A spammer can pass SPF for their own domain while putting your domain in the From header; DMARC fails that message because the domains do not match. A message passes DMARC when at least one of SPF or DKIM passes and is aligned.

Step 1 - Listing everything that sends mail as your domain

An SPF record that forgets a legitimate sender causes that sender's mail to fail. Before writing anything, list every system that sends with an @your_domain address:

  • Your own mail server (its IPv4 and, if it sends over IPv6, its IPv6 address).
  • A hosted mailbox provider such as Google Workspace or Microsoft 365.
  • Newsletter and transactional services (Mailchimp, Brevo, Amazon SES, Postmark and so on).
  • Web applications, monitoring tools, printers and anything else that sends notifications.

Each provider documents the SPF include: value to use, and most also let you set up DKIM for your domain. Check whether an SPF record already exists, because a domain must have only one:

dig +short TXT your_domain
"google-site-verification=..."

If you see a string starting with v=spf1, edit that record in the next step instead of creating a second one. Two SPF records make SPF return permerror and fail for all mail.

Step 2 - Building the SPF record

An SPF record starts with v=spf1, lists the allowed senders as mechanisms, and ends with an all mechanism that says what to do with everything else. The mechanisms you will use most:

MechanismMeaning
ip4:203.0.113.10This IPv4 address (a CIDR range like 203.0.113.0/28 also works)
ip6:2001:db8::10This IPv6 address or range
mxThe servers listed in the domain's MX records
aThe IP addresses of the domain's A/AAAA record
include:_spf.google.comEverything the other domain's SPF record authorizes
-allFail everything not listed
~allSoft fail everything not listed (accepted but treated as suspicious)

For a domain whose mail is sent only by your own server:

v=spf1 ip4:203.0.113.10 -all

For your own server plus Google Workspace and Amazon SES:

v=spf1 ip4:203.0.113.10 include:_spf.google.com include:amazonses.com -all

Keep these rules in mind:

  • Ten DNS lookups at most. Every include, a, mx, exists and redirect costs a lookup, including the ones nested inside included records. Past ten, SPF returns permerror. ip4 and ip6 cost nothing, so prefer them for your own servers.
  • Use -all or ~all, never +all, which authorizes the whole Internet. With DMARC in place, ~all is common and safe, because DMARC decides the final outcome. Use -all once you are sure the list is complete.
  • Do not use ptr. It is deprecated and many receivers ignore it.

For domains and subdomains that never send mail, publish a record that authorizes nothing, so nobody can use them for spoofing:

v=spf1 -all

It is also good practice to give your server's hostname (the name it uses in HELO, such as mail.your_domain) its own record, because some receivers check SPF on the HELO name:

v=spf1 a -all

Step 3 - Publishing and verifying the SPF record

In your DNS provider, create or edit the TXT record on the domain root:

FieldValue
TypeTXT
Name / Host@ (or your_domain)
Valuev=spf1 ip4:203.0.113.10 -all

Add the record for mail.your_domain the same way if you use one. After a few minutes, query it:

dig +short TXT your_domain | grep spf1
"v=spf1 ip4:203.0.113.10 -all"

To see the result a receiver reaches, send a message from your server to a Gmail address and open Show original. The authentication summary should read:

SPF: PASS with IP 203.0.113.10

Step 4 - Creating the DMARC record

A DMARC record is a TXT record on _dmarc.your_domain made of tag=value pairs:

TagMeaning
v=DMARC1Version, must come first
p=Policy for the domain: none (monitor only), quarantine (send to spam) or reject
rua=Address that receives daily aggregate reports, as a mailto: URI
sp=Policy for subdomains; defaults to the value of p
adkim= / aspf=Alignment mode: r (relaxed, the default: mail.your_domain aligns with your_domain) or s (strict, exact match)

Start with a monitoring policy. It changes nothing for delivery but gives you reports on every source that sends as your domain:

v=DMARC1; p=none; rua=mailto:dmarc-reports@your_domain

Create a mailbox or alias for dmarc-reports@your_domain first. Large receivers send one compressed XML report per day, and busy domains get many, so a dedicated address keeps them out of personal inboxes.

Step 5 - Publishing and verifying the DMARC record

Create the TXT record:

FieldValue
TypeTXT
Name / Host_dmarc (or _dmarc.your_domain)
Valuev=DMARC1; p=none; rua=mailto:dmarc-reports@your_domain

Verify it:

dig +short TXT _dmarc.your_domain
"v=DMARC1; p=none; rua=mailto:dmarc-reports@your_domain"

Send another test message to Gmail and check Show original:

SPF:   PASS with IP 203.0.113.10
DKIM:  'PASS' with domain your_domain
DMARC: 'PASS'

Step 6 - Reading the aggregate reports

After a day or two, reports start arriving as .zip or .xml.gz attachments. Each one lists, per sending IP, how many messages the receiver saw and whether SPF and DKIM passed and aligned. You can inspect one on the server with standard tools:

zcat google.com!your_domain!1758758400!1758844799.xml.gz | less

The important part of each <record> looks like this:

<row>
  <source_ip>198.51.100.25</source_ip>
  <count>42</count>
  <policy_evaluated>
    <disposition>none</disposition>
    <dkim>fail</dkim>
    <spf>fail</spf>
  </policy_evaluated>
</row>

For each source IP that fails, decide whether it is legitimate:

  • A service you use (a CRM, a newsletter tool): add its include: to SPF and set up DKIM for your domain in that service.
  • Forwarded mail: SPF breaks on forwarding, but DKIM usually survives. If DKIM passes, there is nothing to fix.
  • Unknown IPs: most likely spoofing. This is exactly what the policy will block.

Reading XML by hand does not scale. For regular use, send the reports to a DMARC analysis service or run an open source parser such as parsedmarc, which turns them into dashboards.

Step 7 - Moving to quarantine and reject

Once the reports show that all legitimate sources pass DMARC for a couple of weeks, tighten the policy. Change the record to quarantine, so failing mail goes to spam:

v=DMARC1; p=quarantine; rua=mailto:dmarc-reports@your_domain

Watch the reports for another two to four weeks. If no legitimate mail fails, switch to reject, so failing mail is refused outright:

v=DMARC1; p=reject; rua=mailto:dmarc-reports@your_domain

Keep rua in place after reaching reject. The reports are how you notice a new service someone in your organization starts using without configuring SPF and DKIM.

For domains that never send mail, you can publish the strict policy right away, together with v=spf1 -all:

v=DMARC1; p=reject

Troubleshooting

  • SPF: PERMERROR in the headers. The domain has two SPF records, or the record needs more than ten DNS lookups. Merge the records into one and replace a/mx lookups for your own servers with ip4/ip6.
  • SPF passes but DMARC fails. The envelope sender domain differs from the From domain, which is common with third-party senders that use their own bounce domain. Configure DKIM for your domain in that service, or set up its custom return-path option.
  • No DMARC reports arrive. Check the rua address for typos, confirm the mailbox receives external mail, and, if it is on another domain, publish the _report._dmarc authorization record. Remember that reports are sent once a day.
  • Legitimate mail lands in spam after switching to quarantine. Look up the source IP in the reports, fix its SPF or DKIM, and go back to p=none in the meantime if the impact is large.

Conclusion

Your domain now publishes an SPF record listing its legitimate senders and a DMARC policy that receivers enforce, with daily reports that show who sends as your domain. Together with DKIM, this is the authentication baseline that Gmail, Yahoo and Microsoft expect from senders. Next, make sure your server's reverse DNS matches its hostname and review the other deliverability factors in How to Prevent Your Emails from Going to Spam.