egressif.

Resources / Authentication

DMARC in 2026 (RFC 9989, 9990, 9991)

The RFC most "DMARC explained" articles still cite is obsolete. In May 2026 DMARC was re-issued as three Standards-Track RFCs. Here is everything that changed - tags, the Tree Walk, reporting, and the new p=reject guidance - in plain language, with records you can copy.

Last checked: June 21, 2026

If you read a “how DMARC works” article today, it almost certainly cites RFC 7489. That document is now obsolete. As of May 2026, DMARC is defined by three Standards-Track RFCs that jointly replace it: RFC 9989 (core protocol), RFC 9990 (aggregate reporting), and RFC 9991 (failure reporting). Together they also absorb RFC 9091 (the experimental PSD-DMARC extension).

The work was known for years as “DMARCbis.” It is not “DMARC2”: the DNS record version is still v=DMARC1, and your existing records keep working. What changed is the spec’s status (DMARC is now a Proposed Standard, not merely Informational) and a set of concrete, operationally meaningful refinements.

The 60-second version

  • The version tag is unchanged: v=DMARC1. No record is broken by this.
  • Three tags are removed: pct, rf, ri.
  • Three tags are added: np (non-existent-subdomain policy), psd (public-suffix marker), t (testing mode).
  • The Public Suffix List is gone, replaced by a DNS-based “Tree Walk” (max 8 lookups) for finding the Organizational Domain.
  • Receivers should not reject a message on p=reject alone, and the spec discourages p=reject for domains whose users post to mailing lists.
  • Reporting moved into its own two RFCs, with a new XML namespace and a required DKIM selector in reports.

The rest of this page is the detail, with records you can copy.

MESSAGEFrom: domainSPF PASS + ALIGNED?MAIL FROM vs From:DKIM PASS + ALIGNED?d= vs From:EITHER?OReither YESboth NODMARC PASSAPPLY POLICYnone / quarantine/ reject
DMARC passes when an aligned SPF or an aligned DKIM is present; if both fail, the receiver applies the published p= policy.

What DMARC still does (unchanged)

DMARC lets the owner of the visible From: domain (the Author Domain) publish a DNS policy telling receivers what to do when mail claiming that domain fails authentication, and where to send reports.

A message passes DMARC when either SPF or DKIM passes and that passing identifier is aligned with the From: domain. Not both - either. This is the single most misunderstood point and it did not change.

Alignment is unchanged too:

  • Relaxed (adkim=r / aspf=r, the defaults): the identifier shares the same Organizational Domain. mg.example.com aligns with example.com.
  • Strict (adkim=s / aspf=s): the identifier domain is identical to the From: domain.

A DMARC pass only proves the domain was used with authorization. RFC 9989 is explicit that it asserts nothing about whether the message is wanted - reputation, content, and engagement still decide inbox placement.

Why it needed an update

RFC 7489 worked, but a decade of deployment exposed real problems, and DMARCbis targets each one:

  • The Public Suffix List was a single, externally-maintained file (by Mozilla), updated out of band. A missing or stale entry produced wrong Organizational Domains.
  • The pct tag was effectively useless: in practice only 0 and 100 were used, and intermediate values behaved differently across receivers.
  • Non-existent subdomains had no policy: an attacker could send from fake.example.com and, if no _dmarc record existed there, no DMARC policy applied.
  • PSD-DMARC (RFC 9091) for registry-level domains (.bank, .gov, co.uk) was only experimental.

Tag changes, with the full comparison

TagRFC 7489DMARCbis (RFC 9989)Status
vDMARC1DMARC1Unchanged
pnone / quarantine / rejectidenticalUnchanged
spsubdomain policyidenticalUnchanged
adkim / aspfr / sidentical (strict encouraged where you control signing)Unchanged
fo0 / 1 / d / sidenticalUnchanged
ruaURIs (with size-limit syntax)URIs, size-limit syntax dropped, stricter external authModified
rufURIsdefined in RFC 9991, stricter external authModified
pct0-100-Removed
rireport interval (seconds)-Removed
rfreport format-Removed
np-none / quarantine / rejectNew
psd-y / n / uNew
t-y / nNew

Removed: pct, rf, ri

  • pct - removed because it was inconsistently implemented; only 0 and 100 were reliable. Graduated rollout now uses t (below).
  • ri - the aggregate-report interval had de facto standardized at 24 hours; receivers ignored other values.
  • rf - only one report format (afrf) was ever deployed.

They still parse harmlessly if left in place (receivers ignore unknown tags), but you should remove them.

New: np - non-existent subdomain policy

np sets a policy for subdomains that do not exist in DNS - closing the spoofing gap above. Values match p. The fallback order is np (if the subdomain is NXDOMAIN) → spp.

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

Here, existing subdomains inherit p=quarantine, while mail from any non-existent subdomain is rejected.

New: psd - public-suffix marker

psd declares whether the publishing domain is a Public Suffix Domain. y = it is a PSD (the Organizational Domain is one label below); n = a normal Organizational Domain; u = undetermined (default; the Tree Walk continues upward). Ordinary senders leave psd absent or set psd=n - it is for TLD/registry operators.

New: t - testing mode (the pct replacement)

t is binary: t=y downgrades the published policy by one level at compliant receivers (rejectquarantine, quarantinenone); t=n (default) applies it normally. Reports are still sent, so you can watch results before enforcing.

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

Important transition gotcha: DMARC-v1 receivers (still the majority in 2026) ignore the unknown t tag and apply p=reject directly. t=y is not a universal “safe test” switch - it only downgrades at receivers that already implement DMARCbis. Do not pair it with pct=0; remove pct first.

The Public Suffix List is gone: the DNS Tree Walk

Determining the Organizational Domain no longer consults the PSL. Instead the receiver walks up the DNS tree, querying _dmarc.<domain> and removing the leftmost label until it finds a record (using the psd tag to know when it has reached the boundary), capped at 8 queries.

# newsletter.example.com
Query 1: _dmarc.newsletter.example.com  -> no record
Query 2: _dmarc.example.com             -> "v=DMARC1; p=reject; psd=n"   (Organizational Domain = example.com)

# contact.bank.co.uk  (co.uk is a public suffix)
Query 1: _dmarc.contact.bank.co.uk      -> no record
Query 2: _dmarc.bank.co.uk              -> no record
Query 3: _dmarc.co.uk                   -> "v=DMARC1; p=reject; psd=y"   (Org Domain = bank.co.uk, one label below the PSD)
AspectPSL (RFC 7489)DNS Tree Walk (DMARCbis)
Source of truthA static list maintained by MozillaThe DNS itself
FreshnessPeriodic downloadReal-time DNS queries
CoverageManual, can be incompleteAny domain publishing _dmarc
StandardizationCommunity projectIETF Standards Track

In practice the Tree Walk adds at most one or two DNS lookups for a typical domain, and caching (including cached NXDOMAIN) absorbs most of it. Publish _dmarc with a TTL of at least 3600s; those records are the Tree Walk’s anchor points.

”p=reject” is no longer a blanket reject

The change most likely to surprise an operator: RFC 9989 makes it normative that a receiver should not reject a message solely because the policy is p=reject. DMARC is one signal among several. In the absence of other knowledge, failing mail under p=reject is to be treated more like quarantine than refused outright. The spec further advises that domains whose users post to mailing lists should not publish p=reject at all, because list software routinely breaks alignment.

This is not a weakening of DMARC. It is the standard catching up to a real failure mode (legitimate forwarded and list mail getting destroyed by a policy it never anticipated). The corollary is important and is exactly where infrastructure choice matters: p=reject is appropriate for domains where every step of the send is controlled end to end, and risky for general-purpose domains that you do not fully control.

Reporting changes (RFC 9990 and 9991)

Aggregate reports (RFC 9990) remain XML, sent at least every 24 hours, and are still the single most useful DMARC signal. What changed:

  • New XML namespace: urn:ietf:params:xml:ns:dmarc-2.0.
  • A DKIM selector is now required in the report, so you can tell which key produced which result.
  • New fields reflect DMARCbis (testing mode, np, how the policy was discovered) and a pass disposition; the pct-era sampled_out override is gone.
  • Each rua URI receives its own report. External destinations (a report address in a different domain) must be authorized with a _report._dmarc record, or the URI is ignored.

Failure reports (RFC 9991) are the per-message reports. They got their own document, clearer privacy language, and mandatory rate-limiting. Most large providers (Google, Yahoo, Microsoft) send few or none for privacy reasons - treat aggregate reports as your foundation and failure reports as an occasional supplement.

What did NOT change (and common confusion)

  • The evaluation model: still “aligned SPF or aligned DKIM,” never “SPF and DKIM and DMARC all passing independently.”
  • Your records: v=DMARC1 is unchanged; nothing needs to be rebuilt by a deadline.
  • DMARC still does not guarantee inbox placement - it proves authorized domain use, nothing more.
  • DKIM2 is a separate effort. You will hear about it, but the DMARC refresh does not require it; today’s DMARC still relies on SPF and DKIM exactly as before.

A practical migration path

# Before (RFC 7489 era)
_dmarc.example.com. 3600 IN TXT "v=DMARC1; p=quarantine; pct=100; ri=86400; rf=afrf; rua=mailto:dmarc@example.com; fo=1"

# 1. Remove the dead tags (pct, ri, rf)
_dmarc.example.com. 3600 IN TXT "v=DMARC1; p=quarantine; rua=mailto:dmarc@example.com; fo=1"

# 2. Close the non-existent-subdomain hole
_dmarc.example.com. 3600 IN TXT "v=DMARC1; p=quarantine; np=reject; rua=mailto:dmarc@example.com; fo=1"

# 3. Escalate to reject, testing first at DMARCbis receivers
_dmarc.example.com. 3600 IN TXT "v=DMARC1; p=reject; t=y; np=reject; rua=mailto:dmarc@example.com; fo=1"

# 4. Enforce (after clean reports), tighten DKIM alignment where you control signing
_dmarc.example.com. 3600 IN TXT "v=DMARC1; p=reject; np=reject; adkim=s; aspf=r; rua=mailto:dmarc@example.com; fo=1"

If your rua/ruf points at a third-party monitoring domain, remember the external-authorization record on that domain, and update it whenever you change providers (a silent way reports stop arriving).

What this means for you, and what Egressif does

If your DMARC knowledge predates 2026: the pct rollout trick is gone (use t), the PSL is no longer how the Organizational Domain is found, rf/ri are removed, and p=reject is explicitly not a blanket reject instruction. Add np=reject to shut the subdomain hole. None of this breaks existing records.

Egressif runs p=reject on the domains it operates. That is responsible only because we control the whole path: SPF and DKIM are published and aligned on your domain, every sending source is accounted for, and there are no unmanaged intermediaries quietly breaking alignment. That is precisely the case the standard reserves p=reject for - a domain controlled end to end - rather than a general-purpose mailbox whose users post to mailing lists. We keep the records complete and current as sources change, use np to block non-existent subdomains, and watch the aggregate reports so a new source that fails alignment surfaces as a signal we act on, not a silent placement drop weeks later.

Related references

Tell us what you run today.

Domains, rough volume, current providers, and what hurts. You will get a straight answer on fit, and a real number, in one conversation.

Talk to our team