For eleven years, every DMARC record you ever wrote was based on an informational document. RFC 7489, published in 2015, was not a standard. It was a description of something the big mailbox providers had already agreed to do, written up and submitted independently, and the entire email authentication world ran on it anyway.
That changed in 2026. DMARC is now a proper IETF Standards Track protocol, published as three RFCs that together replace RFC 7489. The update is known as DMARCbis , and while your existing records keep working, a few things you have been copy-pasting into DNS for years are now deprecated. One tag is gone entirely. Two new ones are worth adding today.
This post covers what actually changed, why each change happened, and the specific edits to make to your DMARC records. No history lesson beyond the paragraph above, and every claim maps to a record you can verify with dig.
TL;DR
<svg fill="none" stroke="currentColor" viewbox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1"></path>
</svg>
</button>
- DMARC is now a real standard: RFC 9989 (the core protocol), RFC 9990 (aggregate reporting), and RFC 9991 (failure reporting), replacing the informational RFC 7489.
- The
pcttag is removed. It was honored inconsistently and rarely did what operators expected. A new binaryt(testing) tag replaces it:t=yfor monitoring,t=nfor enforcement. - New
nptag sets a policy for non-existent subdomains, which is the cheapest fix for a whole class of spoofing. - The Public Suffix List is gone. Receivers now find your organizational domain with a DNS Tree Walk instead.
- The
rfandritags are also removed; reports are XML and receivers control the schedule. -
You do not have to change anything today. Existing records still validate. But you should drop
pct, addnp, and keep progressing toward enforcement.
Prerequisites
<svg fill="none" stroke="currentColor" viewbox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1"></path>
</svg>
</button>
- A domain you send mail from, with an existing DMARC record (or the intent to add one).
- Access to that domain's DNS to add or edit TXT records.
-
dig(ornslookup) for verification. Examples below usedig. - A basic grasp of SPF and DKIM. DMARC sits on top of both; if either is shaky, start there first.
Why "it became a standard" is more than a footnote
<svg fill="none" stroke="currentColor" viewbox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1"></path>
</svg>
</button>
The practical reason this matters: an informational document has no formal authority over how receivers behave. Gmail, Yahoo, and Microsoft implemented RFC 7489 the way they each read it, and the gaps between those readings are exactly where DMARC surprised people. The clearest example is the pct tag, which we will get to, where three major receivers did three different things.
Standards Track changes the contract. The behavior is now specified, the ambiguous corners have been nailed down, and future receivers have one document to conform to instead of a decade of folklore. That is the whole point of DMARCbis: same protocol, sharper edges filed down.
The tag changes at a glance
<svg fill="none" stroke="currentColor" viewbox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1"></path>
</svg>
</button>
Here is the before and after. If you only read one section, read this one.
| Tag | RFC 7489 (old) | DMARCbis (new) | What to do |
|---|---|---|---|
p |
Policy: none/quarantine/reject
|
Unchanged | Keep |
rua |
Aggregate report address | Unchanged | Keep |
ruf |
Failure report address | Unchanged | Keep (rarely honored) |
pct |
Apply policy to N% of mail | Removed | Delete it |
t |
did not exist |
New: testing flag (y/n) |
Use instead of pct
|
np |
did not exist | New: policy for non-existent subdomains | Add np=reject
|
psd |
did not exist | New: declares a public suffix domain | Registry operators only |
rf |
Report format | Removed | Delete it |
ri |
Report interval | Removed | Delete it |
sp |
Subdomain policy | Unchanged | Keep if you use it |
adkim/aspf
|
Alignment mode | Unchanged | Keep |
A record that was perfectly valid yesterday, such as v=DMARC1; p=quarantine; pct=50; rua=mailto:dmarc@example.com, is not broken under DMARCbis. Receivers will parse it, ignore the retired pct, and apply your policy in full. But "ignore pct and apply the full policy" might be the opposite of what pct=50 was doing for you yesterday. That is the one change that can bite silently, so it gets its own section.
The pct tag is gone, and why that is a relief
<svg fill="none" stroke="currentColor" viewbox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1"></path>
</svg>
</button>
The pct tag was meant to let you roll out enforcement gradually. pct=10 told receivers "apply my quarantine/reject policy to 10% of failing mail, and treat the other 90% as p=none." The idea was a dial you could turn from 0 to 100 as confidence grew.
In practice it was a mess. Receivers implemented the sampling differently, some rounded aggressively, some ignored it, and the population being sampled was never clearly defined. Worst of all, the failure mode was invisible: you would set pct=10 expecting a gentle rollout and have no reliable way to know what any given receiver actually did with it.
DMARCbis replaces the dial with a switch. The new t tag is binary:
t=y -> testing mode. Report as normal, but do not enforce.
Equivalent to the old pct=0.
t=n -> enforce the policy in p. This is the default.
Equivalent to the old pct=100.
So the migration is mechanical:
-
pct=0becomest=y -
pct=100(or nopct) becomes the default,t=n, so just delete the tag -
Any fractional
pct(likepct=50) has no direct equivalent. There is no half-enforcement anymore. You pick monitoring or enforcement.
That last point is the one to think about. If you were parked at pct=50 as a permanent state, DMARCbis is telling you to make a decision. The correct rollout was never "sit at 50% forever" anyway; it was "watch reports at p=none, then commit to quarantine, then reject." The t flag makes that the only shape available, which is a good thing.
Warning
If you currently have a fractional
pct(anything other than 0 or 100) combined withp=quarantineorp=reject, a DMARCbis-conformant receiver will apply your full policy, not the sampled fraction. Review those records before receivers do it for you. Move the domain tot=yif you are not ready to enforce, or commit to enforcement and droppct.
The np tag: the cheapest anti-spoofing win
<svg fill="none" stroke="currentColor" viewbox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1"></path>
</svg>
</button>
This is the new tag worth adding today. np sets the policy for non-existent subdomains , meaning subdomains that have no A, AAAA, or MX records at all.
Attackers love non-existent subdomains. p=none on your root plus no protection on random-invoice.example.com means someone can spoof a subdomain you never created and never will. sp (subdomain policy) covers subdomains generally, but np lets you be stricter about the ones that provably do not exist without touching real subdomains that do.
The pattern that gives you the most protection for the least risk:
v=DMARC1; p=none; np=reject; rua=mailto:dmarc@example.com
Read that as: "I am still only monitoring my main domain (p=none), but any mail claiming to come from a subdomain that does not exist should be rejected outright (np=reject)." You get hard protection on the spoofing surface you are certain about, with zero risk to legitimate mail, because by definition nothing legitimate sends from a subdomain that has no DNS records.
The resolution order receivers use is: np for non-existent subdomains, then sp for existing subdomains, then p as the fallback. If you do not set np, it inherits from sp, and if that is unset, from p.
The Public Suffix List is out, replaced by a DNS Tree Walk
<svg fill="none" stroke="currentColor" viewbox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1"></path>
</svg>
</button>
This one is mostly invisible to you as a sender, but it explains a class of past weirdness, so it is worth understanding.
DMARC has to figure out your organizational domain , the registered domain that owns a given subdomain, so it can find the right policy and check alignment. For mail.marketing.example.co.uk, the organizational domain is example.co.uk, and knowing that requires knowing that .co.uk is a public suffix and .uk alone is not where registration happens.
RFC 7489 solved this with the Public Suffix List (PSL), a big crowd-maintained file of every known suffix (.com, .co.uk, .github.io, and thousands more). It worked, but it was an external dependency baked into email authentication: a file that could be stale, that receivers cached differently, and that no DNS operator controlled.
DMARCbis replaces it with a DNS Tree Walk. Instead of consulting a static list, the receiver walks up the DNS tree from the sending domain, querying for DMARC records at each ancestor, and uses what it finds to determine the boundary. Registry and registrar operators can plant a psd=y record to explicitly declare "I am a public suffix, do not walk past me."
For a normal sender, the takeaway is simple: your DMARC record now does more work in determining the boundary , and the answer comes from DNS you control rather than a list you do not. Publishing DMARC at your organizational domain matters more than before.
Reports: XML only, and the receiver sets the schedule
<svg fill="none" stroke="currentColor" viewbox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1"></path>
</svg>
</button>
Two smaller removals. The rf (report format) and ri (report interval) tags are gone.
-
rfis gone because aggregate reports are XML. That was already true in practice; the tag pretended there were alternatives. -
riis gone because receivers were always going to send reports on their own schedule (typically daily) regardless of what you requested. The tag implied a control you never really had.
Nothing to do here except delete these tags if you have them. Your rua address keeps receiving the same daily XML aggregate reports it always did. RFC 9990 is the document that now specifies that reporting format, and RFC 9991 covers the (rarely used) failure reports.
What to actually do to your records
<svg fill="none" stroke="currentColor" viewbox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1"></path>
</svg>
</button>
Here is the concrete checklist. Start by looking at what you have:
audit your current DMARC record
# read the root domain policy
$ dig +short TXT _dmarc.example.com
"v=DMARC1; p=quarantine; pct=50; rua=mailto:dmarc@example.com; rf=afrf; ri=86400"
# that record has three retired tags: pct, rf, ri
# and no np protection on non-existent subdomains
Then apply these edits:
-
Remove
pct. If it waspct=100or absent, just delete it. If it was0, replace witht=y. If it was fractional, decide: enforce (delete it) or monitor (t=y). -
Remove
rfandri. They do nothing now. -
Add
np=reject. This is the highest-value single edit for most domains. It costs nothing in deliverability and closes the non-existent-subdomain spoofing hole. -
Confirm you have a
ruaaddress you actually read. DMARC without report monitoring is a smoke detector with the battery out. -
Keep progressing
p. The retirement ofpctdoes not change the fundamental rollout:noneto watch,quarantineto soft-enforce,rejectto stop spoofing.
A clean, modern record for a domain still in the monitoring phase looks like this:
v=DMARC1; p=none; np=reject; rua=mailto:dmarc@example.com
And once you have read a few weeks of reports and confirmed every legitimate sender is aligned, the enforced version:
v=DMARC1; p=reject; np=reject; rua=mailto:dmarc@example.com
Verify the change took effect the same way you audited it:
verify the updated record
$ dig +short TXT _dmarc.example.com
"v=DMARC1; p=reject; np=reject; rua=mailto:dmarc@example.com"
# no pct, no rf, no ri, and np closes the subdomain hole
If you would rather see the record parsed into plain English, with each tag explained and the policy spelled out, a free browser tool like SMTPfast's DMARC checker reads the record and tells you what a receiver will actually do with it, which is handy when you are staring at a string of tags and want a second opinion.
Tip
Do not jump a production domain straight to
p=reject. If any legitimate system sends mail on your behalf without proper SPF or DKIM alignment (a CRM, a billing tool, an old cron job),p=rejectsilently kills those messages. Sit atp=nonelong enough to read the aggregate reports, fix every unaligned sender, then move toquarantine, thenreject.np=rejectis the exception: it is safe to add immediately because it only affects subdomains that do not exist.
The one-line migration summary
<svg fill="none" stroke="currentColor" viewbox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1"></path>
</svg>
</button>
If you take nothing else from this:
delete pct -> use t=y for testing, otherwise no tag
delete rf -> reports are XML, always were
delete ri -> receivers set the schedule, always did
add np=reject -> free protection on non-existent subdomains
keep progressing p: none -> quarantine -> reject
DMARCbis is not a rewrite. It is a decade of hard-won operational knowledge finally written into the spec, with the confusing parts removed. The pct dial that nobody implemented the same way is gone, the guessing about organizational domains is now a DNS query you control, and there is a new tag that hands you real spoofing protection for the cost of four characters in a TXT record.
Your old records still work. But now is a good time to open your DNS, delete three retired tags, and add one new one.
Originally published at devops-daily.com.
Top comments (0)