emailverifier.dev
Posts

RFC 9989 Changed DMARC: A Practical Migration Guide

| 7 min read | Usama Ejaz
RFC 9989 migration diagram showing one legacy DMARC record passing through a DNS tree walk into core policy, aggregate reporting, and failure reporting.

RFC 9989 keeps v=DMARC1, so there is no wholesale DNS rewrite. The migration work sits in deployment logic: retire percentage rollouts, support the new t and np tags, test DNS Tree Walk behavior, update aggregate-report parsers, and reassess p=reject on domains used by people or mailing lists.

The RFC Editor announced RFC 9989 on May 20, 2026 UTC. The IETF Datatracker records May 19, 2026 as the document’s last update. RFC 9989 became the Standards Track successor to RFC 7489 and RFC 9091 at publication. There is no separate provider-enforcement date.

So what changed? Enough to review your records and tooling, but not enough to rename DMARC or panic-edit DNS.

What did the IETF replace?

The old RFC 7489 combined the core protocol, aggregate reports, and failure reports in one document. The new work splits those responsibilities:

  • RFC 9989 defines DMARC policy, alignment, discovery, and receiver behavior.
  • RFC 9990 defines aggregate reporting.
  • RFC 9991 defines failure reporting.

RFC 9989 is a Proposed Standard. RFC 7489 was Informational. The status change matters for implementers, but it does not create a DMARC2 record format.

Area RFC 7489 model RFC 9989 model Your action
Version tag v=DMARC1 v=DMARC1 Keep it.
Policy discovery Public Suffix List DNS Tree Walk Test deep subdomains and organizational boundaries.
Staged enforcement pct sampled failing mail pct removed, t adds a one-level test mode Replace percentage rollout logic.
Non-existent subdomains No core np policy np sets their policy Choose an explicit policy after monitoring.
Reports Defined inside RFC 7489 Defined by RFC 9990 and RFC 9991 Update parsers and schemas.

Do existing DMARC records stop working?

No. RFC 9989 still requires v=DMARC1 as the first tag. It also requires receivers to ignore unknown tags, which allows new tags without a version change.

An old record containing pct, rf, or ri does not gain their old behavior from a receiver following RFC 9989. Those tags were removed. The receiver ignores them.

The difference is easy to miss. The record still looks familiar in DNS, yet your rollout assumption might be gone.

Why did percentage rollouts disappear?

RFC 7489 let a domain request policy enforcement for a percentage of failing messages with pct. The RFC 9989 rationale says receiver implementations applied intermediate values inconsistently.

I would remove any rollout plan built around pct=10 or pct=25. It no longer has portable meaning under the current standard.

RFC 9989 replaces only part of the old behavior with t=y. Test mode requests one policy level below the published policy:

  • p=quarantine; t=y requests none for failures.
  • p=reject; t=y requests quarantine for failures.
  • t=n, the default, requests the published policy.

This is a step-down switch, not a percentage sampler. Reports still run during test mode.

How should you stage enforcement now?

I would make the rollout state explicit in deployment history instead of hiding it inside a sample percentage.

  1. Publish p=none with an aggregate-report address.
  2. Inventory every legitimate sender, including support, billing, product, CRM, and vendor streams.
  3. Fix SPF or DKIM alignment for each authorized stream.
  4. Move to p=quarantine only after report data looks stable.
  5. Use t=y for a one-level test where receiver support is part of your plan.
  6. Remove t=y after the test and record the change date.
  7. Consider p=reject only after reviewing indirect mail and mailing-list use.

For domains hosting general-purpose mail, RFC 9989 recommends at least one month at p=none, followed by an equal period at p=quarantine, before any move to p=reject.

What does the new np tag protect?

The np tag sets a policy for non-existent subdomains of the organizational domain. It does not apply to the organizational domain itself or to existing subdomains.

Suppose billing.example.com exists but fake-team.example.com does not. The sp tag governs the existing subdomain. The np tag governs the non-existent one.

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

This example expresses a preference to reject DMARC failures from non-existent subdomains. It does not prove a message is malicious, and a receiver still applies local policy.

If np is absent, RFC 9989 falls back to sp when present, then to p. I prefer an explicit np value once the team understands which subdomains truly do not exist.

There is one operational trap. A forgotten DNS record turns a supposedly non-existent subdomain into an existing one, shifting policy from np to sp. DNS inventory still matters.

What changed in policy discovery?

RFC 7489 relied on a Public Suffix List to find the organizational domain. RFC 9989 replaces this with a DNS Tree Walk.

For alerts.eu.mail.example.com, a receiver starts at the author domain and walks upward through DMARC TXT lookups until it finds the applicable record or reaches the stopping condition. The algorithm limits the process to eight DNS queries.

The new psd tag marks a boundary:

  • psd=y identifies a Public Suffix Domain and belongs to its operator.
  • psd=n identifies a non-public-suffix domain as an organizational boundary for itself and its subdomains.
  • psd=u is the default unknown state.

Most SaaS teams should not add psd=y. It is for public-suffix operators. A deliberate psd=n is useful only when your DNS and mail ownership model matches the boundary being declared.

What should a receiver or gateway developer test?

  • An author domain with its own DMARC record.
  • A deep subdomain inheriting an organizational record.
  • An existing subdomain governed by sp.
  • A non-existent subdomain governed by np.
  • A tree containing psd=n and a separate tree containing psd=y.
  • A domain longer than eight labels.
  • Multiple DMARC records at one lookup name, which invalidates the set.
  • Transient DNS errors, which are neither DMARC pass nor fail.

What should aggregate-report parsers accept?

RFC 9990 defines the aggregate-report format. Its sample report uses the urn:ietf:params:xml:ns:dmarc-2.0 namespace and adds fields tied to the new core protocol.

A parser should accept and preserve:

  • np in the published policy.
  • testing for the t state.
  • discovery_method with psl or treewalk.
  • generator metadata.
  • Extensions defined by the RFC 9990 schema.
  • A required DKIM selector for reported DKIM signatures.

Do not overwrite old-report support during migration. Receivers will adopt the new RFCs at different speeds, so a report pipeline needs schema-aware parsing and a preserved raw payload for failed parses.

Should every domain move to p=reject?

No. RFC 9989 is unusually blunt here.

Domains hosting people who post to mailing lists should avoid p=reject because forwarding and list modification break authentication in legitimate indirect flows. The RFC also says a domain publishing p=reject must not rely only on SPF. Valid aligned DKIM signatures are required for resilience through forwarding.

A fair objection is simple: weak enforcement leaves exact-domain spoofing room to reach users.

I agree. A dedicated transactional domain with controlled senders and aligned DKIM is a stronger candidate for p=reject. A broad employee domain with mailing-list traffic has a different failure cost. One policy should not pretend both domains have the same mail path.

Receivers also must not treat a DMARC failure plus p=reject as the sole rejection reason. RFC 9989 requires local analysis and says quarantine is the fallback without other knowledge.

Where does DMARC stop?

A DMARC pass shows authorized use of the visible From domain through aligned SPF or DKIM. It does not prove a message is safe, a sender is trustworthy, or a mailbox belongs to a person. RFC 9989 states this boundary directly.

It also does not stop look-alike domains or display-name impersonation. Those threats need separate controls.

This matters for signup systems too. DMARC protects your sending identity. Address verification evaluates a submitted address for structure, routing, mailbox evidence, and risk. Validation, verification, and confirmation remain separate states.

A migration checklist I would run

  1. List every organizational domain and author domain in active mail.
  2. Export current DMARC records with a timestamp.
  3. Find and remove reliance on pct, rf, and ri.
  4. Decide whether np should differ from sp or p.
  5. Review any psd value against the real DNS ownership boundary.
  6. Test RFC 9989 tree-walk discovery against deep and delegated subdomains.
  7. Update report parsing for RFC 9990 while retaining old schema support.
  8. Verify aligned DKIM on every sender before stronger enforcement.
  9. Review mailing lists, forwarders, aliases, and other indirect paths.
  10. Record monitoring, quarantine, test, and enforcement dates.

If signup quality is your current problem, the emailverifier.dev API contract keeps deliverable, risky, undeliverable, and unknown address outcomes separate from domain authentication.

Keep DMARC current. Keep its claim narrow.

Continue reading