_dmarc. The tags pct, rf and ri are removed; np, psd and t are added. If you set your DMARC record to half strength years ago with pct=50, you are relying on a value that validators following the new standard no longer evaluate. Ahead of the newsletter waves around Black Friday and Black Week, this is a good moment to walk through your own record tag by tag: what can stay, what should go, what is worth adding, and how does the record stay unambiguous for old and new receivers during the transition?What RFC 9989 changes in DMARC
Until now, DMARC was not an Internet standard in the strict sense. RFC 7489 was published in March 2015 as an informational document (RFC 7489), through the independent submission stream rather than as the output of an IETF working group (RFC 9989). The new version is an IETF Standards Track document. Besides RFC 7489, it also obsoletes RFC 9091, the experimental procedure for DMARC on public suffixes such as country-code domains; according to RFC 9989, that experiment was not implemented as written (RFC 9989).
Reporting has moved into documents of its own. RFC 9990 describes the aggregate reports that receivers send to the address in rua (RFC 9990), and RFC 9991 the failure reports on individual messages sent to the address in ruf (RFC 9991). Both replace the corresponding parts of RFC 7489.
For the record itself there is reassuring news: the version stays v=DMARC1. New tags explicitly do not require a new version, because validators must ignore unknown tags (RFC 9989). An existing record therefore stays valid. It may simply no longer do what its author intended back then, and that is what the following sections are about.
Removed: pct (share of messages), rf (failure report format), ri (aggregate report interval) and the size limit after report addresses. New: np (policy for non-existent subdomains), psd (public suffix flag) and t (test mode). Unchanged: v, p, sp, adkim, aspf, rua, ruf and fo (RFC 9989).
The DMARC record tag by tag
A typical record from recent years looks roughly like this: v=DMARC1; p=quarantine; pct=50; rf=afrf; ri=86400; rua=mailto:dmarc@example.com!10m. Almost every element in it is affected by the new version. The table shows what happens to each tag and what that means for your own record.
| Tag | RFC 7489 | RFC 9989 | What to do |
|---|---|---|---|
| v | DMARC1 | DMARC1, unchanged | nothing |
| p | none, quarantine, reject | unchanged | review the value, see the section on reject |
| sp | policy for subdomains | only for existing subdomains now | keep, add np |
| np | only in the RFC 9091 experiment | new: non-existent subdomains | add, often reject |
| pct | integer from 0 to 100, default 100 (RFC 7489) | removed, historic in the registry | remove or set to 100 |
| t | not available | new: y or n, default n | only for test mode |
| psd | not available | new: y, n or u | usually leave out |
| rf | failure report format, default afrf | removed | remove |
| ri | report interval, default 86400 seconds (RFC 7489) | removed | remove |
| rua, ruf | addresses, optionally with a size limit | size limit obsolete | remove suffixes like !10m |
| adkim, aspf, fo | alignment, reporting options | unchanged | nothing |
Removed does not mean wrong: a record with ri=86400 stays valid; the tag is simply no longer evaluated. With pct, however, the behavior depends on which version the receiving server follows.
pct is gone: why 50 percent did not work reliably
The idea behind pct made sense. RFC 7489 allowed any integer from 0 to 100 (RFC 9989), and domain owners were meant to use it to move gradually from none to quarantine or from quarantine to reject: the stricter treatment would first apply only to a share of the messages that fail DMARC. Many guides from recent years build a staircase of several percentage steps on this, including older posts on this blog such as the guide to analyzing DMARC reports. Its part on reading the reports remains valid; the percentage staircase is outdated with RFC 9989.
Practice looked different. According to the operational experience recorded in RFC 9989, pct was usually applied accurately only for the values 0 and 100; for all other values, the behavior varied widely from one implementation to another (RFC 9989). The value 100 was easy because it was the default and needed no special processing. The value 0, however, took on a meaning nobody had planned: intermediaries such as mailing lists and some mailbox providers treated it as a signal to rewrite the From: header so that forwarded messages would not fail DMARC later (RFC 9989).
RFC 9989 draws the consequence from this experience: pct is removed and replaced by t with the values y and n. Mailbox providers and intermediaries are meant to treat t=y like the former pct=0 and t=n like pct=100 (RFC 9989). In the DMARC tags registry, IANA now lists pct with the status "historic", meaning a tag that is not expected in current implementations (RFC 9989).
A half-set record cannot be translated. t=y corresponds to pct=0, t=n corresponds to pct=100, and the new standard has no intermediate value. If your record currently says pct=50 or any other intermediate value, you have to decide: let the policy apply in full or deliberately go back to test mode.
t=y: test mode with a clear effect
With t=y, the domain owner asks the validating receiver not to apply the published policy yet. The expectation is spelled out precisely: messages that fail DMARC are treated one level more leniently. p=quarantine with t=y effectively becomes none, and p=reject with t=y becomes quarantine (RFC 9989). The tag has no effect on a none policy, and it does not change reporting: aggregate reports keep coming (RFC 9989).
That makes t=y the tool for the phase in which pct=0 used to be set. The domain owner already publishes p=quarantine, but receivers still treat failing messages as with none, and intermediaries with special handling can rewrite the From: header. The aggregate reports then show which paths carry mail that would fail without that rewriting. The example in RFC 9989 describes exactly this case for a test subdomain with p=quarantine and t=y (RFC 9989).
Important in practice: t=y applies to all policy tags in the record, including sp and np (RFC 9989). A record with np=reject and t=y therefore protects non-existent subdomains only at the quarantine level. If you want np=reject fully effective right away, you cannot use test mode in the same record at the same time.
A record with t=y signals that the domain owner is still testing. If it stays unchanged for months, it protects the domain one level less than the policy suggests at first glance. Switching to t=n or removing the tag therefore belongs in the plan with a date.
Transition period: serving old and new validators at once
RFC 9989 has been published since May 2026 (RFC 9989), but not every receiving server validates according to the new version from that day on. For a while, old and new implementations work side by side. Both versions specify what happens to tags an implementation does not know: they must be ignored (RFC 7489). A validator that only knows RFC 7489 therefore skips t and psd, and a validator following RFC 9989 no longer evaluates pct, because the new version no longer describes the tag.
This explains why pct=50 is particularly unfortunate during the transition: an old validator applies the policy to at most half of the failing messages, a new one to all of them. The same record behaves differently at two receivers, and aggregate reports become hard to compare. The record only becomes unambiguous once pct is missing or set to 100. Then both generations apply the full policy, because 100 was the default in RFC 7489 (RFC 7489).
| Record (excerpt) | Validator per RFC 7489 | Validator per RFC 9989 | Unambiguous? |
|---|---|---|---|
| p=quarantine; pct=50 | quarantine for at most half | quarantine for all | no |
| p=quarantine; pct=100 | quarantine for all | quarantine for all | yes |
| p=quarantine (no pct) | quarantine for all | quarantine for all | yes |
| p=quarantine; t=y | quarantine for all | treated like none | no |
| p=quarantine; pct=0; t=y | no quarantine, normal classification | treated like none | yes, test mode |
For test mode, this means: if you also want to reach older validators, you can keep pct=0 next to t=y. The old validator does not know t and reads pct=0; the new one does not evaluate pct and reads t=y. Both end up with treatment one level more lenient, because under RFC 7489, too, messages that do not fall under reject because of pct were to be treated as with quarantine (RFC 7489). As soon as test mode ends, both tags go together.
np: protection for subdomains that do not exist
Anyone forging mail in a brand's name likes to use senders that sound plausible: invoice.example.com, shipping.example.com, service.example.com. If these names do not exist in DNS at all, they used to fall under the subdomain policy sp or, without sp, under the main policy p. With np, the domain owner can set a policy specifically for this case. The tag comes from the RFC 9091 experiment and is now a regular part of the standard (RFC 9989).
A name counts as non-existent if DNS answers with NXDOMAIN; then all names below it do not exist either (RFC 9989). If np is missing, sp applies to such names and, if that is missing too, p (RFC 9989). That makes np one of the few changes that pay off without a long lead time: a subdomain that does not exist in DNS typically sends no legitimate mail. A record that is still monitoring the main domain with p=none can therefore already carry np=reject. The policy only affects messages that fail DMARC anyway.
Before setting it, a look at the aggregate reports is still worthwhile. If a sender subdomain with failing authentication shows up there that actually belongs to one of your own services, such as a ticket system or an invoicing service, set up that service properly first. For domains that send no mail at all, the BSI technical guideline on email authentication already requires a DMARC policy of reject (BSI); np=reject carries this idea over to the names below an active domain.
Policy for subdomains without a DNS entry. If the tag is missing, sp applies, otherwise p.
Test mode with y or n. t=y replaces the former pct=0 and lowers the treatment by one level.
Public suffix flag: y, n or the default u. Rarely relevant for shops.
psd and the DNS tree walk
The third new tag addresses a small group. With psd=y, operators of public suffixes, such as registries for top-level domains, mark their record; psd=n explicitly declares a name to be the organizational domain for itself and everything below it; the default u leaves the decision to the procedure (RFC 9989). For the record of a shop or company, this usually means: leave psd out.
What lies behind it is more interesting. RFC 7489 determined the organizational domain with the help of a public suffix list but specified neither a particular list nor how often to fetch it. RFC 9989 replaces this with the DNS tree walk: starting from the sender domain, the receiver queries the parent names level by level and determines the organizational domain from the DMARC records it finds (RFC 9989). So that sender domains with very many labels cannot trigger a flood of queries, even very long names lead to at most eight DNS queries (RFC 9989).
For most domains, nothing changes in the result; for complex organizations with deep subdomain trees, it may. RFC 9989 names the remedy itself: different results between the old list logic and the new tree walk can be avoided with strict alignment and explicit records for all sender domains in use (RFC 9989). A record published directly at the sender name is queried before any tree walk and therefore takes precedence.
If the receiver finds more than one DMARC record at a name, it discards all of them (RFC 9989). This happens easily during a rebuild: the new record is created and the old one stays. To receivers, the domain then has no DMARC policy until the mistake is fixed. Change or delete the old TXT record in the same step.
rf, ri and the size limit: leftovers that can go
Two more tags are removed without replacement. rf set the format of failure reports, with the default afrf; ri requested an interval between two aggregate reports, with the default of 86400 seconds, which is one day (RFC 7489). Even RFC 7489 only required daily reports; shorter intervals were a best-effort courtesy. RFC 9990 now describes the reporting period without a tag of its own: it typically covers a single UTC day starting at 0000 UTC (RFC 9990).
The size limit after a report address, such as !10m at the end of rua=mailto:dmarc@example.com!10m, also belongs to the old syntax. RFC 9989 lists it only as obsolete syntax that report senders should ignore (RFC 9989). Leaving it in does no harm according to the text; a tidy record is still easier to check.
The report addresses themselves stay as they are. rua remains the basis of every migration: if the tag is missing, receivers must not generate aggregate reports for the domain (RFC 9989). ruf for failure reports on individual messages remains allowed as well. The BSI, however, advises against requesting such reports because they carry personal data (BSI). If your record still has ruf, use the rebuild to make that decision deliberately.
p=reject is not the goal for every domain
The tone around the strictest policy is new as well. RFC 9989 states explicitly that domains whose users might post to mailing lists should not publish p=reject (RFC 9989). The reason is indirect paths such as forwarding, role addresses and lists, where SPF typically fails. If you still want reject, RFC 9989 says you should first monitor with p=none for at least a month, then run p=quarantine for an equally long period and compare the results (RFC 9989).
For domains that publish reject, RFC 9989 also requires valid DKIM signatures rather than relying on SPF alone, because DKIM usually survives forwarding (RFC 9989). And on the receiving side there is a sentence that may surprise many: receivers must not reject messages solely because of p=reject and, without further knowledge, must treat such mail as if the policy were quarantine (RFC 9989).
In its technical guideline TR-03182, the BSI sets a different emphasis: it expects quarantine or reject and names reject as the long-term goal (BSI). The two can be reconciled. For a pure sending domain that a shop uses for order confirmations and newsletters and whose addresses nobody uses for mailing lists, reject is usually well founded. For the domain that hosts the team's personal mailboxes, quarantine is often the more cautious choice. If you display a brand logo via BIMI in the inbox, also check after the rebuild whether the requirements of the respective mailbox provider for the policy are still met.
Rebuilding the DMARC record step by step
The rebuild itself takes minutes; the preparation does not. In this order, the record stays unambiguous in every phase:
- Save the current state: query the current record at
_dmarcand note it with a date. The free mail server check outputs the published DMARC record verbatim and reports when there is more than one record at_dmarc. - Read the aggregate reports: the reports from recent weeks show which sources still fail and whether legitimate senders show up under missing subdomains.
- Decide on pct: if
pctis below 100, either enforce in full (removepct) or deliberately switch to test mode (t=y, pluspct=0for older validators). - Remove leftovers: delete
rf,riand size limits such as!10mafter the report addresses. - Add np: for non-existent subdomains, usually
np=reject, provided the reports give no reason against it. In test mode witht=y, it applies one level more leniently. - Replace instead of adding: change the old TXT record in the same step; do not create a second one.
- Measure again: after the TTL has expired, query the record again and check in the next aggregate reports whether the new policy is being applied. That exactly one record with the intended policy stays in place is then shown continuously by DNS monitoring.
In the zone file, the rebuild looks like this for the three cases. The address example.com stands for your own domain:
; before, per RFC 7489
_dmarc.example.com. 3600 IN TXT "v=DMARC1; p=quarantine; pct=50; rf=afrf; ri=86400; rua=mailto:dmarc@example.com!10m"
; path A: apply the full policy
_dmarc.example.com. 3600 IN TXT "v=DMARC1; p=quarantine; np=reject; rua=mailto:dmarc@example.com"
; path B: test mode, unambiguous for older validators too
_dmarc.example.com. 3600 IN TXT "v=DMARC1; p=quarantine; np=reject; t=y; pct=0; rua=mailto:dmarc@example.com"After saving, a simple query shows what the world actually sees. The number of lines is what matters: if two v=DMARC1 records come back, the old one is still there.
Before Black Week: keep the record in view
Especially before the high-revenue weeks in November, it pays not to leave the rebuild until the last day. The large mailbox providers tie their rules for bulk senders to a published DMARC record: Google requires it from senders who send more than 5,000 messages per day to Gmail accounts (Google), Microsoft, for Outlook, from domains sending more than 5,000 emails per day (Microsoft), and Yahoo from bulk senders with at least p=none (Yahoo). None of the three requires a strict policy; Google, too, accepts none (Google).
The risk therefore lies less in the policy than in the rebuild itself. If you create a second record while editing, mistype v=DMARC1 or accidentally write the record to the wrong name, these receivers see no valid DMARC record, possibly in the middle of a campaign. Which other rules apply to bulk sending is summarized in the post on deliverability rules for bulk email.
In DNS and domain monitoring, XICTRAQ® checks SPF, DKIM and DMARC with every DNS check. For DMARC, the website view shows whether exactly one record is published and which policy it sets; if there are several records at _dmarc, this appears as a warning. Changes to the TXT records of the domain itself, such as the SPF record, are kept in the changelog with old and new value. If email authentication is missing entirely, an alert goes out through the configured notification channels.
The check does not yet evaluate the new tags np, psd and t separately. If you have switched your record to RFC 9989, you can read them in the verbatim record that the mail server check outputs unchanged. Whether an alert really arrives at night is answered better by an alerting drill than by any assumption. And for your own documentation, one simple rule helps: every change to the DMARC record gets a date, the old and new wording and one sentence of justification.
Bring your DMARC record in line with RFC 9989 once and properly
The rebuild is manageable: pct, rf, ri and size limits out, np in, t=y only for a time-limited test phase, and psd usually stays out. More important than any single tag is that the record afterwards means the same thing to old and new validators and exists only once in DNS. As with the HSTS preload list, the same applies here: a record that controls the behavior of other people's servers is checked first and committed second.
If you are setting up DMARC for the first time, start with the basics from the guide to SPF, DKIM and DMARC and write the record according to RFC 9989 right away, that is, without pct. If you have already switched, use the weeks before the holiday season to read the aggregate reports and then let the record run quietly. The live demo shows what the check looks like day to day.
This post is based on: RFC 9989, Domain-Based Message Authentication, Reporting, and Conformance (DMARC), IETF, May 2026, in particular sections 4.7, 4.8, 4.10, 5.1 and 7.4 and appendices A.6, B.2.5 and C; RFC 9990, DMARC Aggregate Reporting, IETF; RFC 9991, DMARC Failure Reporting, IETF; RFC 7489, DMARC, sections 6.3 and 6.6.4; BSI TR-03182 Email Authentication; Google, email sender guidelines (Gmail); Microsoft, Outlook requirements for high-volume senders; Yahoo, Sender Best Practices. This post describes the state of the standards and does not replace a review of your own mail infrastructure.
Is my old DMARC record invalid under RFC 9989?
v=DMARC1, and validators must ignore tags they do not know (RFC 9989). A record with pct=50, however, acts at validators following the new version like a record without pct, that is, with the full policy. If you do not want that, switch the record deliberately.What do I use instead of pct=50?
t=y stands for the former pct=0, and t=n or a missing t for pct=100 (RFC 9989). If the aggregate reports are clean, the policy applies in full without pct. If legitimate sources still fail, test mode with t=y is usually the more honest state.Should I set np=reject?
np is missing, sp or p applies to such names (RFC 9989). Beforehand, it is worth checking the aggregate reports for senders under subdomains that are missing in DNS.Do I need to add psd to my record?
psd=y is meant for operators of public suffixes, psd=n for organizations that want to explicitly mark a name as their own organizational domain (RFC 9989). Without the tag, the default u applies, and the receiver determines the organizational domain via the DNS tree walk.