DMARC t=y: The DMARCbis Testing Flag That Replaced pct
t=y tells DMARC receivers to apply your published policy one level softer: p=reject is handled as quarantine, p=quarantine is handled as none, and p=none is unaffected. It is the testing mechanism in RFC 9989, the DMARCbis core spec published in May 2026, and it replaces the removed pct= tag. Until every receiver speaks DMARCbis, publish it as t=y; pct=0 so both generations of receivers land in the same place.
What the t tag means, exactly
RFC 9989 §4.7 defines t as "DMARC policy test mode", with two values: y and n. The default is n, which applies your policy as written.
With t=y, the spec says the domain owner "has an expectation that the policy applied to any failing messages will be one level below the specified policy." The full mapping:
| Published policy | t=n or absent |
t=y |
|---|---|---|
none |
none | none |
quarantine |
quarantine | none |
reject |
reject | quarantine |
Four details matter:
- One level, not all the way down.
p=reject; t=ystill quarantines failing mail. People who readt=yas "don't enforce" get this wrong. - It applies to whichever policy is in effect. §4.7 covers the policy "declared in the "p", "sp", and/or "np" tags". If a message from a subdomain gets your
sp=reject,t=ysoftens that to quarantine too. - It has no effect at none. The spec says so directly.
p=none; t=yis the same asp=none. - Reports keep flowing. "This tag does not affect the generation of DMARC reports." You get the same aggregate data either way.
An invalid value falls back to n. The t entry doesn't say this itself, but §4.8 does: "Syntax errors in the remainder of the record MUST be discarded in favor of default values." So t=yes or t=1 gets you your full policy. Write t=y exactly.
t=y also invites receivers to apply "any special handling rules it might have in place, such as rewriting the RFC5322.From header field". That is what mailing lists already do for strict DMARC domains.
How t differs from pct
pct= was a percentage. pct=25 asked receivers to apply your policy to a quarter of failing mail and treat the rest one step softer. RFC 9989 removes it, along with rf and ri. All three are marked "historic" in the IANA registry (§9.3).
Appendix A.6 explains why. In practice pct was only reliable at 0 or 100, and pct=0 "took on unintended significance" as a signal some intermediaries used to rewrite the From header. So the spec replaced a percentage nobody implemented consistently with a binary flag. t=y and t=n are "meant to be analogous" to pct=0 and pct=100.
Analogous is as far as the RFC goes. It does not tell DMARCbis receivers to read pct=0 as t=y. It defines no processing for pct at all. If you want the background on retiring percentage rollouts, see why pct= is deprecated and how to migrate.
Why you publish t=y and pct=0 together
Two generations of receivers are in the field right now, and each ignores the other's tag:
- An RFC 7489 receiver ignores unknown tags (RFC 7489 §6.3). It never sees
t=y.p=reject; t=yis a full reject request to it. - An RFC 9989 receiver doesn't process
pct.p=quarantine; pct=0is a full quarantine request to it.
Publishing both covers both. The two tags also produce the same result. Under RFC 7489 §6.6.4, failing mail excluded by pct gets the next policy down: reject falls to quarantine, and quarantine falls to normal delivery. With pct=0, that is all failing mail. That matches the one-level step of t=y.
| Record | RFC 7489 receiver | RFC 9989 receiver |
|---|---|---|
p=reject; t=y |
reject | quarantine |
p=reject; pct=0 |
quarantine | reject |
p=reject; t=y; pct=0 |
quarantine | quarantine |
This equivalence is our reading of the two RFCs side by side, not a sentence either one contains. And as always, the receiver's local policy has the final say. RFC 9989 §7.4 even says receivers "MUST NOT reject incoming messages solely on the basis of a "p=reject" policy" without other evidence.
A rollout sequence with t=y
Each step keeps rua= pointed at your reporting address. RFC 9989 §7.4 suggests spending at least a month at none and an equally long period at quarantine before reject, so treat these as multi-week steps.
Step 1: collect reports.
v=DMARC1; p=none; rua=mailto:reports@example.com
t does nothing here, so leave it off. Fix alignment for every legitimate sender first.
Step 2: announce quarantine, apply none.
v=DMARC1; p=quarantine; t=y; pct=0; rua=mailto:reports@example.com
Delivery behaves like p=none. The difference is in the reports: receivers that implement RFC 9990 should mark the rows they would have quarantined, so you see the blast radius before it happens.
Step 3: real quarantine.
v=DMARC1; p=quarantine; rua=mailto:reports@example.com
Remove t=y and pct=0 in the same edit. Leaving pct=0 behind keeps legacy receivers softened.
Step 4: announce reject, apply quarantine.
v=DMARC1; p=reject; t=y; pct=0; rua=mailto:reports@example.com
Failing mail is still quarantined everywhere, and the reports show what reject would catch.
Step 5: full enforcement.
v=DMARC1; p=reject; rua=mailto:reports@example.com
The DMARC generator has a testing mode that emits the t=y; pct=0 pair for you.
How to confirm receivers honor it
RFC 9990, the new aggregate report format, added two things that make t=y observable.
First, policy_published now has a testing element, defined in §3.1.1.5 as "the value of the "t" tag." If a report shows y, that receiver parsed your tag.
Second, when a failing message is softened by test mode, the row's policy_evaluated carries a reason. §3.1.1.9 requires a reason whenever alignment fails and the applied policy doesn't match your configured one. §3.1.6 defines policy_test_mode as: "The message was exempted from application of policy by the testing mode ("t" tag)". A failing row under p=reject; t=y should look like this:
<policy_published>
<domain>example.com</domain>
<p>reject</p>
<testing>y</testing>
</policy_published>
...
<policy_evaluated>
<disposition>quarantine</disposition>
<dkim>fail</dkim>
<spf>fail</spf>
<reason>
<type>policy_test_mode</type>
</reason>
</policy_evaluated>
Paste a report into the DMARC XML analyzer and it shows the testing value and the discovery_method (psl or treewalk) next to the published policy.
A caveat on reading this: many reporters still send the RFC 7489 format, which has no testing field and no policy_test_mode reason. A missing field tells you the reporter hasn't moved to RFC 9990 yet. It doesn't tell you it ignored t=y. For those reporters, pct=0 is what's doing the work.
When to remove t=y
Remove t=y (and pct=0 with it) when the testing-mode reports stop showing legitimate mail in the softened rows. Every source in those rows should be one you recognize as abuse or one you've fixed. Give it the same multi-week window as any policy step, so weekly and monthly senders show up.
Don't leave it in place as a permanent safety net. A record with t=y is one level below what it says. p=reject; t=y is a quarantine domain, and anything that checks for real enforcement (BIMI readiness is a common one) should treat it that way. Run your domain through the DMARC record checker to see how your current record reads, t included.
