“DMARCbis” was the IETF working name for the rewrite of the DMARC specification. It stopped being a draft in May 2026, when it was published as three RFCs, so strictly speaking there is no DMARCbis any more. There is just DMARC, and it now works differently under the hood than the version almost every guide on the internet still describes.
If you last read the spec as RFC 7489, you learned a version of the protocol that is now obsolete. The rewrite kept the record you already publish working, but it changed how receivers decide what your domain even is, retired several tags outright, and added the single most useful anti-spoofing control the standard has ever had. Everything below is checked against RFC 9989 itself rather than the second-hand summaries, some of which are already getting the important part wrong.
One practical thread runs through all of it: a new discovery_method field in the reports (RFC 9990) lets you check, from your own data, which mailbox providers have actually switched to the new rules - so you do not have to take any vendor’s word for it. More on that below.
Three documents, not one
The monolithic RFC 7489 was split into a core protocol plus two reporting specs, all published together in May 2026. In the process DMARC was promoted from an Informational document to a Proposed Standard for the first time in its history.
- RFC 9989 is the core protocol. Its formal Obsoletes header names both RFC 7489 (the original 2015 spec) and RFC 9091 (the PSD experiment), folding the public-suffix-domain work into the core.
- RFC 9990 covers aggregate reporting, the
ruaside: the daily XML digests receivers send back, and their schema. - RFC 9991 covers failure reporting, the
rufside: the near-real-time, per-message failure reports.
The big change: the Public Suffix List is gone
This is the part that matters most and gets the least coverage.
DMARC passes when SPF or DKIM aligns with your From address, and by default “aligns” does not mean an exact match - it means the two domains belong to the same organization. news.acme.com and mail.acme.com align because both roll up to acme.com. So every alignment check depends on one question: where is the boundary between “a company’s own domain” and “the public registry above it”?
The old approach (RFC 7489) was a shared list. Receivers consulted the Public Suffix List, a community-maintained text file of registry endings (.com, .co.uk, and thousands more). The organizational domain was “the public suffix, plus one label to its left.” The list also has a “private” section for hosting platforms that hand their customers a registrable name each - github.io is the classic example, where alice.github.io and bob.github.io are meant to count as separate organizations. Nobody was required to use the same copy of the list, so different receivers carried different, differently-aged versions - and could disagree.
The new approach (RFC 9989) is to ask DNS. Instead of a file, the receiver does a tree walk - it queries _dmarc up the domain’s labels and lets DNS itself reveal the boundary (capped at eight lookups, in a fixed order of preference: the Author Domain first, then the Organizational Domain, then the Public Suffix Domain). A platform that wants each customer treated as its own organization simply publishes psd=y on its own _dmarc record, and the tree walk picks that boundary up live from DNS - no waiting for a shared list to be updated.
Here is the walk in practice, for a message from [email protected]:
_dmarc.sub.mail.example.com -> no record
_dmarc.mail.example.com -> no record
_dmarc.example.com -> record found (read psd= to confirm the boundary)
Organizational Domain = example.com
Why this can flip a pass into a fail. Take a hosting platform whose domain is cloudplatform.io. It is not a TLD - the TLD is .io - it is an ordinary domain that acts as a registry boundary, giving each customer a registrable name of their own such as acme.cloudplatform.io (exactly like username.github.io). Your client owns the name acme.cloudplatform.io, and the platform signs its outbound mail with a shared key, d=mailer.cloudplatform.io.
- If a tool does not treat
cloudplatform.ioas a boundary - an old list that never added it (the Public Suffix List does carry hosting domains likegithub.ioin its private section, but it only helps if your copy of the list includes that entry) - then bothacme.cloudplatform.ioandmailer.cloudplatform.iolook like parts of one domain,cloudplatform.io. Same organization, so they align and DMARC passes. - If
cloudplatform.iois recognized as a boundary - because it now publishespsd=y, which the tree walk honours - then each customer name is its own organization. Nowacme.cloudplatform.ioandmailer.cloudplatform.ioare different organizations, so they do not align and DMARC fails.
Same message, opposite verdicts, purely because the two methods drew the company boundary in different places. And the finer boundary is the correct one: it is exactly what stops another customer, globex.cloudplatform.io, from signing mail that aligns with your client’s acme.cloudplatform.io.
Practically, alignment is now something you can reason about from DNS alone, rather than depending on which snapshot of a text file a given receiver happens to hold - provided the receiver has actually implemented the tree walk. (More on who has, below.)
The tag changes
Some tags are gone, two are new, and one common one changed meaning in a way secondary sources are already botching.
| Tag | Status | What to know |
|---|---|---|
pct |
Removed | Percentage sampling. Removed outright, not deprecated (RFC 9989 Appendix A.6). |
rf, ri |
Removed | Report format and interval. Gone. |
t |
New | Replaces pct, but as a step-down, not an off switch. See below. |
np |
New | Policy for non-existent subdomains. The sharpest anti-spoofing control in the record. |
psd |
New | Declares public-suffix status: y / n / u (default). Most owners never touch it. |
sp |
Narrowed | Now governs only subdomains that actually exist. Non-existent ones fall to np. |
v=DMARC1 |
Unchanged | No version bump. This is precisely why nothing breaks. |
The t= tag: a test drive for your policy
Think of your DMARC policy as a three-position dial:
p=none- watch only. Nothing is blocked; you just collect reports.p=quarantine- send failing mail to the spam folder.p=reject- bounce failing mail outright.
The new t= tag lets you test a setting one notch gentler before you commit to it. t=y tells receivers: “this is the policy I want, but apply the level just below it for now.” So:
| You publish | Receivers actually apply |
|---|---|
p=reject; t=y |
quarantine |
p=quarantine; t=y |
none (monitor only) |
p=none; t=y |
none (already the floor) |
It is a step down one level, not an off switch, and not the old pct=0. RFC 9989 spells out both what t=y asks for and what it expects. First, the request itself:
A request that the actor performing the DMARC validation check not apply the policy, but instead apply any special handling rules it might have in place, such as rewriting the RFC5322.From header field.
And the step-down it expects (the policy still applies, just one level down):
The Domain Owner is currently testing its specified DMARC assessment policy and has an expectation that the policy applied to any failing messages will be one level below the specified policy. That is, if the policy is “quarantine” and the value of the “t” tag is “y”, a policy of “none” will be applied to failing messages; if the policy is “reject” and the value of the “t” tag is “y”, a policy of “quarantine” will be applied to failing messages, irrespective of any other special handling rules that might be triggered by the “t” tag having a value of “y”.
Two consequences the RFC states outright are worth pulling up. t= “has no effect on any policy (‘p’, ‘sp’, or ‘np’) that is ‘none’” - so p=none; t=y simply stays none. And it “does not affect the generation of DMARC reports” - which is exactly why the test phase works: the aggregate reports you are watching keep flowing the whole time.
So p=reject; t=y still protects you at the quarantine level - failing mail is quarantined, or handled by the receiver’s own special rules such as a From rewrite - it does not stop enforcing entirely. Which of those a given mailbox provider does can vary, so do not assume identical behaviour across every receiver. At least one well-known vendor blog already describes t=y as equivalent to pct=0, which is wrong, and it is the kind of error that quietly loosens a domain’s enforcement by a full level.
A worked example. Acme is at p=none and wants to reach p=reject safely.
The old way (RFC 7489) was to ramp the pct tag - reject a growing slice of failing mail:
v=DMARC1; p=reject; pct=25; rua=mailto:[email protected] # reject 25% of failures
Then edit pct to 50, 75, 100 over several weeks. pct is now removed from the standard, so this no longer works as intended.
The new way (RFC 9989) is to publish your real target, p=reject, with the test flag on:
v=DMARC1; p=reject; t=y; rua=mailto:[email protected] # acts as quarantine while testing
Every failing message is now quarantined - one notch below reject - while Acme watches the reports. When the reports show only spoofed mail is failing, Acme deletes three characters:
v=DMARC1; p=reject; rua=mailto:[email protected] # full reject, live
The record said reject the whole time; the t=y flag was just a temporary hold. That is the point of t= over simply publishing p=quarantine: your record always states your true goal, and finishing the rollout is removing a flag, not rewriting your policy - which is why so many domains that publish quarantine “temporarily” get stuck there forever.
One caution before you pair this with
np=. Thet=flag steps down every policy you declare, not justp=. RFC 9989 applies it to the policies “declared in the ‘p’, ‘sp’, and/or ‘np’ tags.” So a record likep=reject; np=reject; t=yalso puts your non-existent-subdomain protection into test mode: for the whole rollout,np=rejectbehaves as quarantine, not reject. If you wantnp=rejectenforcing at full strength while you test your main policy, a single record cannot do that today - so either accept thatnprides one level down during the test window, or holdt=yoff until you are ready to enforce everything at once.
For most domains none of this comes up: the default is t=n (normal enforcement), and t=y is only worth setting for the short testing window before you move from quarantine to reject. It is a temporary tool, not a tag to leave on.
np= is the new headline control
Because sp= now covers only subdomains that exist, np=reject can shut down invented ones like invoices-secure.yourdomain.com while you leave your real subdomains on something gentler during a rollout. It costs nothing, needs no monitoring, and closes the single most common subdomain-spoofing trick. psd= is mostly for registries: psd=n declares “I am my own Organizational Domain”, psd=y declares a public suffix, and psd=u, the default, lets the tree walk decide.
What it means in practice
Nothing you have already deployed is broken. Receivers that implement RFC 9989 ignore retired tags like pct, while older receivers still on RFC 7489 logic may keep honouring them - so a record carrying pct= keeps working either way, and plenty of receivers still honour it because the installed base is full of it.
The gap to watch runs the other way. t= and np= are new, and receiver support for them is still thin. So the honest position for the next year or so is a split one:
pctis dead in the spec but alive in the field: removed from RFC 9989, yet widely still honoured.t=is alive in the spec but not yet reliable in the field: correct to publish, not yet safe to depend on.
The practical consequence is that a percentage-based ramp is no longer part of the standard and should not be relied on for new deployments. During the transition many receivers still honour pct, but the spec-compliant path is a time-based ramp: hold at quarantine, watch aggregate reports for a week or two, then move to reject.
Who supports it yet?
There is an easy trap here. Google’s, Yahoo’s and Microsoft’s well-known deadlines - February 2024 for Google and Yahoo, May 2025 for Microsoft - are sender requirements: “if you send us bulk mail, you must publish a DMARC record.” That is a completely separate question from whether their receiving systems have swapped the Public Suffix List for the tree walk and learned to honour t= and np=. A provider can demand that you publish DMARC while still evaluating it the old RFC 7489 way, so “Google requires DMARC” does not mean “Google has implemented DMARCbis.”
As of now, receiver-side adoption is early and still rolling out, and there is no blanket public confirmation that the big mailbox providers have migrated. Fortunately you do not have to take anyone’s word for it. RFC 9990 added a discovery_method field to the aggregate report, with two values: psl (the receiver still used the Public Suffix List) or treewalk (it has moved to DMARCbis). Every receiver tells you, in its own reports, which method it used - so watching discovery_method flip from psl to treewalk across your Google, Microsoft and Yahoo reports is the real per-provider adoption signal, read straight from your own data (our Postbox DMARC Monitor collects those reports for you).
In the raw aggregate XML it sits inside <policy_published>, next to the domain’s policy - grep your reports for discovery_method:
<policy_published>
<domain>example.com</domain>
<adkim>r</adkim>
<aspf>r</aspf>
<p>reject</p>
<np>reject</np>
<discovery_method>treewalk</discovery_method>
</policy_published>
The honest takeaway: publish to the new standard, but do not assume any given receiver enforces t= or np=, or uses the tree walk, until its own reports say discovery_method=treewalk.
What to do now
Two things are worth folding into audits today. First, recommend np=reject as standard: it is the cheapest spoofing defence available and there is rarely a reason not to set it. Second, check whether a client’s DMARC vendor has actually implemented the tree walk, because a vendor still doing Public Suffix List lookups will report alignment differently from a receiver that has moved on, which shows up as “the numbers do not match” long before anyone suspects the cause.
A record built to the new spec is nothing exotic - just the modern tags in place of the retired ones:
# at _dmarc.yourdomain.com
v=DMARC1; p=reject;
rua=mailto:[email protected];
sp=reject; np=reject;
adkim=r; aspf=r
# note what is NOT here: no pct=, no rf=, no ri=
The adkim/aspf lines are shown for clarity but are optional - both default to r (relaxed) if you omit them; set them to s only if you want strict alignment.
If you want your own record checked against the 2026 spec, our DMARC enforcement guide walks the safe path to p=reject, and Postbox DMARC Monitor collects the aggregate reports you tighten the policy on.