DevKitLab Logo DevKitLab
Email / DMARC / SPF / DKIM / Deliverability

From p=none to p=reject Without Losing Your Own Mail

A staged DMARC rollout: read the aggregate reports first, find every legitimate sender, use t=y as the brake now that pct is gone, and only then enforce.

Publishing v=DMARC1; p=none takes about a minute. Publishing p=reject takes about the same minute — and if you do it on the same afternoon, you may spend the following week explaining why the invoices, the password resets, and the recruiting tool all stopped arriving.

The gap between those two records isn’t technical. It’s inventory. p=reject asks receivers to reject mail that fails DMARC, and almost every organisation is sending legitimate mail from more places than it can list from memory. The rollout is the process of finding those places before you ask receivers to do that.

If the terms below need grounding first — what alignment is, why SPF passing isn’t enough — start with SPF, DKIM, and DMARC: what each one actually does. This piece assumes that model and deals with the operational half.

Stage 0: publish a monitoring record and do nothing else

The first record’s only job is to make receivers start reporting:

v=DMARC1; p=none; rua=mailto:dmarc-reports@yourdomain.com

p=none asks for no DMARC enforcement. Receivers can still apply their ordinary spam and abuse controls; DMARC itself must not change their handling because of this record. What changes is that participating receivers can begin sending aggregate reports about messages they saw using your domain.

Two things about that address are worth getting right immediately, because both fail silently.

If the reporting address is on another domain, that domain has to authorise you. Sending reports to you@dmarc-vendor.example requires a TXT record at yourdomain.com._report._dmarc.dmarc-vendor.example containing v=DMARC1. Without it, conforming reporters drop your reports — and “no reports arriving” looks identical to “nobody has sent us any.” Hosted DMARC vendors normally publish this for you, but it’s worth confirming rather than assuming.

Aggregate reports are XML and are usually gzip-compressed. Reporting periods are typically a UTC day, but a receiver can send more than one report or none at all. A mailbox is fine for a small domain; past a certain volume you’ll want something that parses them. Either way, don’t route them to an address nobody reads — the reports are the whole point of this stage.

Confirm the record is live and the reporting destination checks out with the SPF and DMARC checker, which reads the record tag by tag and verifies external report authorisation specifically.

Then wait. If this is a general-purpose mail domain — one where people may post to mailing lists — RFC 9989 recommends at least a month at p=none; even for a dedicated sending domain, wait long enough to catch monthly, quarterly, and annual senders. Enforcing before a low-frequency sender has appeared in the data is exactly how the surprise happens.

Stage 1: turn reports into a sender inventory

Aggregate reports tell you, per source IP: how many messages, whether SPF passed, whether DKIM passed, and — the part that matters — whether either one aligned with your From domain.

You’re building one table:

SenderAligned SPF?Aligned DKIM?Action
Google Workspace (staff mail)yesyesnothing
Marketing platformnonoconfigure both
Billing systemyesnoadd DKIM
Helpdesknonoconfigure, or move to a subdomain
Unknown IP in another countrynonoinvestigate before enforcing

The recurring surprise is how long the list gets. Payroll, CI notifications, the CRM, the calendar invites from a scheduling tool, the survey tool someone signed up for in 2023 — every SaaS product that sends “on your behalf” shows up here. That is what stage 0 buys you.

Some sources will be genuine forwarding rather than a system you own: a recipient auto-forwards your message, the forwarding server relays it from an IP that isn’t yours, and SPF fails. If DKIM survived, DMARC still passes on the DKIM side. This is one of the clearest arguments for making sure DKIM is aligned everywhere rather than leaning on SPF alone.

A quick note on what the reports can’t tell you: they aggregate, so a failing source is a count and an IP, not a message you can read. When you need to know why one specific message failed, that answer lives in its headers — save the raw source and run it through the email header analyzerhow to read email headers covers what to look for.

Stage 2: align every legitimate sender

For each sender on the list, the goal is at least one aligned pass. In practice, aim for aligned DKIM everywhere, because it survives forwarding and doesn’t consume your SPF budget.

DKIM at a provider usually means the provider generates a key pair and gives you a CNAME or TXT record to publish, often at a selector like s1._domainkey.yourdomain.com. The critical detail is the d= value in the resulting signature: it must be your domain, not the provider’s. A signature with d=mailer-provider.com verifies perfectly and aligns with nothing. Glance at the key size while you’re in there: RFC 8301 sets the floor at 1024 bits and recommends 2048, and providers do still hand out 1024-bit keys. The checker measures every key it finds.

SPF alignment normally requires the SMTP envelope sender to be under your domain. Many providers offer a “custom bounce domain” or “custom Return-Path” setting for exactly this — configure it and SPF can align too. Without it, the envelope sender stays at the provider’s bounce domain, SPF passes there, and contributes nothing to DMARC. For a delivered message, confirm the identity the receiver actually used in smtp.mailfrom (or smtp.helo for a null reverse path), not from Return-Path alone.

While you’re editing SPF, watch the ceiling. SPF allows ten DNS-querying terms, include counts recursively, and each new provider adds at least one. Going over returns PermError, which DMARC counts as an SPF failure — and the record looks completely normal while it happens. The checker expands the whole include tree and counts every lookup-costing term against the limit, which is the practical way to see how close you are. The count is a conservative upper bound rather than a per-message result, so read a number above ten as “some sending paths will hit PermError” and go find which ones.

If a sender can’t be aligned, move it to a subdomainnotifications.yourdomain.com for the helpdesk, say. Subdomain mail is judged against sp (or np), so you can enforce strictly on your primary domain while a subdomain runs at a different policy.

Stage 3: quarantine, then reject

With the inventory clean and the reports quiet, step up. Quarantine first:

v=DMARC1; p=quarantine; rua=mailto:dmarc-reports@yourdomain.com

Quarantine is recoverable in a way reject isn’t. A message that shouldn’t have failed often lands in spam, where the recipient can still find it and tell you. A rejected message may be gone, and the sender gets a bounce they may not forward to you. For a general-purpose mail domain, keep quarantine for an equally long period — at least a month — and keep reading reports.

Before choosing p=reject, decide whether this is a general-purpose mail domain whose users send to mailing lists. RFC 9989 specifically cautions those domains against p=reject without analysing the impact: a list can rewrite or modify a legitimate message and make it fail DMARC. For that use case, retain a less strict policy or move routine automated mail to a dedicated subdomain.

Then:

v=DMARC1; p=reject; sp=reject; np=reject; adkim=r; aspf=r;
  rua=mailto:dmarc-reports@yourdomain.com

When this record is inherited from the organisational domain, sp=reject covers existing subdomains and np=reject covers subdomains that don’t exist at all — the ones an attacker invents precisely because nothing constrains them. Set np deliberately; it costs nothing and closes a real gap.

The staging brake is t, not pct. If you learned DMARC before 2026, the standard advice was pct=20 to apply the policy to a fraction of your mail. RFC 9989 removed pct (along with rf and ri) when it obsoleted RFC 7489 in May 2026, and points to the test-mode tag t as its replacement. Records still carrying pct keep working — receivers ignore removed tags rather than rejecting the record — but the behaviour now splits: receivers following the current spec apply your full policy to every message, while ones that still honour pct sample. If you’re relying on pct as a safety margin, you no longer have the margin you think you have.

How t=y actually behaves

t=y is the current brake, and it’s routinely misread as “don’t enforce this.” It asks receivers not to apply the published policy directly; RFC 9989 expects a result one step softer than the one you published, while the final disposition remains the receiver’s choice:

PublishedExpected treatment with t=y
p=rejectquarantine
p=quarantinenone
p=nonenone

So p=reject; t=y is a way to publish your intended final policy while the expected treatment stays at quarantine. It’s genuinely useful for a cautious cutover — and it’s also a foot-gun, because a record that reads p=reject at a glance requests only quarantine until someone removes the tag. Put a reminder on it. The checker reports that expected treatment separately when it sees t=y, so the lower level is visible rather than something you have to remember.

What to watch after enforcing

Enforcement isn’t the end of the reporting relationship. Keep rua in place permanently — it’s how you find out that a new SaaS tool started sending as you, or that a provider rotated a DKIM key and didn’t tell you.

Two failure modes are worth naming because they look like DMARC breaking when it isn’t:

Mailing lists. A list that appends a footer or rewrites the subject invalidates the DKIM signature, and it relays from its own IP so SPF fails too. Under p=reject, subscribers on strict receivers stop getting those messages. Well-run lists handle this by rewriting the From header to the list’s own domain; badly-run ones don’t. Some intermediaries also seal the result they saw with ARC, so a downstream receiver can tell the message authenticated before the list touched it — but that only helps where the receiver trusts that intermediary, which makes it a mitigation rather than a fix. Either way this is a known cost of enforcement, not a misconfiguration on your side.

Forwarding. Similar shape, better outcome: as long as the message body is untouched, aligned DKIM survives and DMARC still passes.

The checklist

  1. Publish p=none with a working rua. Verify external report authorisation if the address is off-domain.
  2. Monitor for long enough to see every sender. For a general-purpose mail domain, use at least a month at p=none.
  3. Build the sender inventory from the reports: every source, and whether it has an aligned pass.
  4. Align each sender, preferring DKIM with d=yourdomain.com. Watch the ten-lookup SPF ceiling. Move the stubborn ones to a subdomain.
  5. Move to p=quarantine and, for a general-purpose mail domain, keep reading reports for at least another month.
  6. Move to p=reject, and set sp and np explicitly.
  7. Use t=y as the brake if you want one — remembering it softens by one step, and remembering to remove it.
  8. Keep rua forever.

The whole sequence is deliberately boring. Nothing about p=reject is difficult to publish; the work is in the inventory, and the inventory only exists because you spent a few weeks at p=none reading reports instead of guessing. To check the record you end up with — policy, subdomain and non-existent-subdomain handling, alignment modes, the SPF budget behind it, and whether your report addresses are authorised — run the domain through the SPF and DMARC checker.