Common DMARC record mistakes that silently break enforcement

dmarc

Some DMARC mistakes fail loudly. Most do not.

The dangerous ones are the records that look published in DNS, maybe even show up in a quick TXT lookup, but are unusable enough that receivers treat them as if no real enforcement policy exists.

That is how teams end up saying "we already published p=reject" while spoofed mail still gets through.

If the short version is enough, check these first:

  • only one DMARC TXT record at the exact _dmarc.example.com host
  • v=DMARC1 first, then a valid p= tag
  • semicolon-separated tag/value pairs with no broken syntax
  • reporting addresses and optional tags added only after the base record is valid

Start with the one rule people skip: publish DMARC at the right DNS name

Per RFC 7489 Section 6.1, DMARC records live at the _dmarc subdomain.

For example.com, the record belongs here:

_dmarc.example.com TXT "v=DMARC1; p=reject; rua=mailto:dmarc@example.com"

Not here:

example.com TXT "v=DMARC1; p=reject; rua=mailto:dmarc@example.com"

That wrong-host mistake is more common than it should be, especially in DNS panels that hide the full record name or auto-append the zone name in slightly confusing ways.

If the record is published at the zone apex instead of _dmarc, receivers doing a DMARC lookup will not find a DMARC policy for the domain. It is that simple.

If a DMARC checker says "no DMARC record found" but DNS clearly returns a TXT record somewhere, check the owner name first.

Mistake 1: publishing multiple DMARC records

DMARC is not like a list you can extend by adding another TXT row later.

For a domain, there should be one DMARC policy record at _dmarc.example.com. If multiple DMARC records are returned for that host, receivers can treat the result as invalid. In practice, that usually means your intended policy is not reliably applied.

This often happens after migrations:

  • one old p=none record is left in place
  • a new p=quarantine or p=reject record gets added beside it
  • DNS now returns both

Broken example:

_dmarc.example.com TXT "v=DMARC1; p=none; rua=mailto:dmarc@example.com"
_dmarc.example.com TXT "v=DMARC1; p=reject; rua=mailto:dmarc@example.com"

Correct approach:

_dmarc.example.com TXT "v=DMARC1; p=reject; rua=mailto:dmarc@example.com"

Do not confuse this with DNS string splitting inside a single TXT record. A provider can store one long TXT value as multiple quoted strings that DNS concatenates. That is normal. Two separate DMARC policy records are the problem.

Mistake 2: leaving out required tags

The base record format is stricter than many admins expect.

Per RFC 7489 Section 6.3, v and p are required in a policy record, and v=DMARC1 must appear first.

These are valid starting points:

v=DMARC1; p=none
v=DMARC1; p=none; rua=mailto:dmarc@example.com
v=DMARC1; p=reject; rua=mailto:dmarc@example.com

These are not:

p=reject; rua=mailto:dmarc@example.com
v=DMARC1; rua=mailto:dmarc@example.com
v=DMARC1; p=
v=DMARC1; p=monitor

Two practical reminders:

  • rua is useful, but it is optional
  • p is not optional, even when the goal is "monitoring only"

If the domain is still in discovery mode, the safe minimal record is:

v=DMARC1; p=none; rua=mailto:dmarc@example.com

If you want a fuller explanation of what each tag does, DMARC record tag cookbook goes tag by tag.

Mistake 3: invalid syntax that makes the record unusable

This is the category that causes the most "but it looks right" outages.

DMARC uses a tag-value format, and small syntax errors matter:

  • using commas where semicolons should be used
  • misspelling tag names
  • invalid values like p=monitor
  • malformed mailto: URIs in rua
  • putting v=DMARC1 somewhere other than first

Examples of broken records:

v=DMARC1, p=reject, rua=mailto:dmarc@example.com
v=DMARC1; policy=reject; rua=mailto:dmarc@example.com
rua=mailto:dmarc@example.com; v=DMARC1; p=reject
v=DMARC1; p=reject; rua=dmarc@example.com

Examples of records that are boring but correct:

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

RFC 7489 explicitly says unknown tags must be ignored, but syntax errors in the actual record can cause the usable parts to be ignored or defaulted in ways you did not intend. That is why it is safer to start simple and add complexity only after the base record validates cleanly.

Mistake 4: assuming TXT lookup success means DMARC success

Seeing some TXT output in DNS is not the same as publishing a valid DMARC policy.

A basic DNS lookup can confirm that a TXT record exists. It does not confirm that:

  • the record is at _dmarc.<domain>
  • only one DMARC policy record exists
  • required tags are present
  • values are valid
  • receivers will parse it as intended

That is why a "record exists" result from a generic DNS tool is only the first check, not the last one.

Use both kinds of validation:

  1. DNS lookup: does _dmarc.example.com return the expected TXT value?
  2. DMARC validation: does the record parse cleanly as DMARC, with one policy and valid tags?

Mistake 5: publishing enforcement before the record is operationally clean

This is less about RFC syntax and more about rollout discipline.

Teams sometimes jump straight to this:

v=DMARC1; p=reject

That record is syntactically valid. But if it is published on the wrong host, duplicated, or copied with a tiny syntax error, the team may believe enforcement is active when it is not.

The safer sequence is:

  1. publish one valid p=none record at _dmarc.<domain>
  2. confirm it parses correctly
  3. review aggregate reports and sender alignment
  4. move to p=quarantine, then p=reject when the domain is actually ready

That rollout pattern is covered in more detail in A sane DMARC setup process for busy domains and DMARC policy modes explained.

A simple pre-enforcement checklist

Before assuming DMARC enforcement is live, verify all of this:

  • the record is published at _dmarc.example.com
  • there is exactly one DMARC policy record
  • the record starts with v=DMARC1; p=...
  • semicolons separate the tags
  • p is one of none, quarantine, or reject
  • optional tags like rua, adkim, aspf, pct, and sp are spelled correctly
  • a DMARC validator parses the record without errors

If spoofed mail is still appearing after that, the problem usually shifts from "bad DMARC DNS syntax" to "receiver handling, non-DMARC abuse, or legitimate sources that are still misaligned". For that next layer, DMARC troubleshooting with Authentication-Results headers is the better place to look.

Bottom line

The most common DMARC record mistakes are boring: wrong hostname, duplicate records, missing p, and tiny syntax errors.

They are also exactly the mistakes that make teams think enforcement exists when receivers are effectively ignoring the policy.

So the safest DMARC record is not the most advanced one. It is the one valid record, at the correct _dmarc host, with a clean v=DMARC1; p=... core that every receiver can parse the same way.

Previous Post