To set up DMARC, first make sure SPF or DKIM passes and aligns with your From domain, then publish a TXT record at _dmarc.example.com such as v=DMARC1; p=none; rua=mailto:dmarc@example.com. Collect the aggregate reports for a few weeks, fix every legitimate sender that fails, and only then tighten the policy to quarantine and eventually reject.

DMARC (RFC 7489) ties SPF and DKIM to the address people actually see in the From header, tells receivers what to do with mail that fails, and asks them to send you reports. Without it, anyone can put your domain in a From header and SPF and DKIM have nothing to say about it.

We use example.com throughout.

What DMARC protects against, and what it doesn't

DMARC stops other people from sending mail with your exact domain in the From header, once you enforce it. That covers the most damaging kind of spoofing: fake invoices or password-reset messages that appear to come from billing@example.com.

It does not stop lookalike domains such as examp1e.com or example-billing.com, and it does not stop someone using your company name as a display name with an unrelated address. Those need user awareness and, sometimes, registering the obvious lookalikes yourself.

DMARC also does not improve placement on its own. It tells receivers your mail is genuinely yours; whether they put it in the inbox still depends on your reputation. What it does do is make your authentication consistent and visible, which is the foundation everything else rests on, and it is required for bulk senders to Gmail and Yahoo.

Step 1: Get SPF and DKIM passing and aligned

DMARC passes if either of these is true:

  • SPF passes, and the envelope sender (Return-Path) domain aligns with the From domain.
  • DKIM passes, and the signature's d= domain aligns with the From domain.

"Aligns" means the same organizational domain in relaxed mode (the default), or the exact same domain in strict mode.

From Return-Path DKIM d= Relaxed result Strict result
hello@example.com bounce@example.com example.com pass pass
hello@example.com bounce@mail.example.com example.com pass pass (via DKIM)
hello@example.com bounce@provider.example provider.example fail fail

The last row is the classic problem: a provider passes SPF and DKIM for its own domain, and DMARC still fails for yours. Fix it by completing the provider's custom domain setup so it signs with d=example.com.

Check a real message from each sender before publishing anything. In Gmail, open it, choose "Show original" and look for dkim=pass header.i=@example.com and spf=pass.

Step 2: Create a mailbox for reports

Reports arrive as compressed XML attachments, daily, from every receiver that processes your mail. Use a dedicated address. On our own servers, dmarc@ delivers to a local mailbox that nothing else uses, so reports never clutter a human inbox.

If you forward it instead, a Postfix virtual alias is one line in /etc/postfix/virtual:

dmarc@example.com  reports@your-inbox.example
sudo postmap /etc/postfix/virtual && sudo systemctl reload postfix

Step 3: Publish a p=none record

Field Value
Type TXT
Host _dmarc
Value v=DMARC1; p=none; rua=mailto:dmarc@example.com

That is enough to start. The tags you are likely to use:

Tag Example Meaning
v DMARC1 Version. Must come first.
p none / quarantine / reject Policy for the domain
rua mailto:dmarc@example.com Where to send aggregate reports
sp reject Policy for subdomains (defaults to p)
adkim r or s DKIM alignment: relaxed (default) or strict
aspf r or s SPF alignment: relaxed (default) or strict
pct 25 Apply the policy to a percentage of failing mail
ruf mailto:... Forensic reports; few receivers send them

Our own domains, which only send from servers we control, run:

v=DMARC1; p=quarantine; rua=mailto:dmarc@example.com; adkim=s; aspf=s

Strict alignment suits us because every message is signed with the exact From domain. If you use providers that send from subdomains, stay relaxed.

Reports on another domain

If rua points at a different domain, for example a reporting service, that domain must publish an authorization record or receivers will not send reports:

example.com._report._dmarc.reports.example.net  TXT  "v=DMARC1"

Reporting services set this up on their side.

Step 4: Read the aggregate reports

Each report lists, per sending IP: how many messages, the SPF and DKIM results, whether they aligned, and what the receiver did. A trimmed record looks like this:

<record>
  <row>
    <source_ip>203.0.113.10</source_ip>
    <count>42</count>
    <policy_evaluated>
      <disposition>none</disposition>
      <dkim>pass</dkim>
      <spf>pass</spf>
    </policy_evaluated>
  </row>
  <identifiers>
    <header_from>example.com</header_from>
  </identifiers>
</record>

Sort the source IPs into three groups:

  1. Yours and passing. Good.
  2. Yours and failing. A forgotten tool, a provider without custom DKIM, or a forwarder. Fix these before enforcing.
  3. Not yours. Spoofing or spam using your domain. This is what enforcement will stop.

Reading XML by hand gets old fast. A parser or reporting tool that groups by IP and sender makes this a five-minute weekly job. For a quick look without any tool, unpack the attachment (unzip for .zip, gunzip for .gz) and print one line per row:

python3 - report.xml <<'EOF'
import sys, xml.etree.ElementTree as ET
root = ET.parse(sys.argv[1]).getroot()
for r in root.iter('record'):
    row = r.find('row')
    pe = row.find('policy_evaluated')
    print(row.findtext('source_ip'), row.findtext('count'),
          'dkim=' + pe.findtext('dkim'), 'spf=' + pe.findtext('spf'),
          r.findtext('identifiers/header_from'))
EOF

To identify an unfamiliar source IP, dig +short -x on it usually shows the provider's hostname.

Example DMARC records for common situations

Situation Record
Just starting, collecting reports v=DMARC1; p=none; rua=mailto:dmarc@example.com
Reports clean, first enforcement v=DMARC1; p=quarantine; pct=25; rua=mailto:dmarc@example.com
Fully enforced v=DMARC1; p=reject; rua=mailto:dmarc@example.com
Enforce on root, still testing subdomains v=DMARC1; p=reject; sp=none; rua=mailto:dmarc@example.com
Domain that never sends mail v=DMARC1; p=reject (with SPF v=spf1 -all)

A subdomain without its own _dmarc record inherits the organizational domain's policy, using sp= if it is set and p= otherwise. A subdomain can also publish its own record, which overrides the inherited one.

Forwarding and mailing lists

Two kinds of legitimate mail can fail DMARC through no fault of yours:

  • Forwarding. When someone forwards mail from an old address to a new one, the forwarder's IP is not in your SPF record. DKIM still passes if the message is unchanged, so DMARC still passes. This is why DKIM matters even when SPF is perfect. Forwarders that use SRS, as our own server does with postsrsd, stop SPF failing outright, but the SPF pass is then for the forwarder's domain, so for DMARC it is still DKIM that carries the message.
  • Mailing lists. Lists that add a footer or a subject tag break the DKIM signature. Many list servers now rewrite the From address to their own domain for senders with strict policies, which avoids the failure.

You will see both in your reports as small numbers of failures from IPs you do not recognize. They are not a reason to stay at p=none forever, but they are a reason to check before assuming every failure is spoofing.

Do not forget parked domains

Domains you own but never send from are easy targets, because nobody watches them. Give each one v=spf1 -all at the root and v=DMARC1; p=reject at _dmarc. There is no rollout needed: if a domain sends nothing legitimate, nothing legitimate can break. Add an rua address if you want to see who tries.

Step 5: Tighten the policy

When reports show only your own senders, all passing, for a few weeks, move to p=quarantine, then p=reject. The detailed rollout, with pct, subdomain policy and what to watch at each stage, is in DMARC p=none to p=reject.

Common mistakes

  • No rua address. p=none without reports does nothing at all. The reports are the point.
  • Jumping straight to p=reject. A forgotten sender (invoices, the helpdesk) starts bouncing immediately, and you find out from customers.
  • Publishing on the wrong host. The record goes at _dmarc, not at the root. A DMARC record at example.com itself is ignored.
  • Two DMARC records. Like SPF, only one is allowed. Two records means receivers act as if you have none.
  • Assuming SPF pass means DMARC pass. Without alignment, it does not. Check header.from against the authenticated domains.
  • Reports to an outside domain without authorization. Add the _report._dmarc record, or use a mailbox on your own domain.

How to verify it worked

dig +short TXT _dmarc.example.com

You should see exactly one string starting with v=DMARC1. Then send a test through each sender to port25's verifier (check-auth@verifier.port25.com) and to a Gmail address. In Gmail's "Show original", look for dmarc=pass. From your own server:

swaks --to check-auth@verifier.port25.com --from hello@example.com \
  --server mail.example.com --port 587 --tls \
  --auth LOGIN --auth-user hello@example.com

mail-tester.com also reports the DMARC record and result. Our email checker shows SPF, DKIM and DMARC for a domain in one place. Within a day or two the first aggregate reports should arrive at your rua mailbox.

DMARC is also one of the things the Gmail and Yahoo bulk sender requirements check. If you want someone to set it up, read the reports and walk you to enforcement, that is part of our Deliverability Fix; see the pricing page for what each plan includes.