To get to DMARC p=reject safely, run p=none with aggregate reports until every legitimate sender passes and aligns, then switch to p=quarantine (optionally ramping with pct), and finally to p=reject, setting sp= for subdomains along the way. For most domains that takes several weeks to a few months. The risk is never the record itself, which you can change back in minutes; it is a legitimate sender you forgot, whose mail starts disappearing.

This guide assumes you already have a DMARC record. If not, start with how to set up DMARC. We use example.com throughout.

Before you start: a pre-flight checklist

Run through this once, before phase 1 or before tightening an existing p=none:

  • The rua mailbox exists, receives mail and is read by someone. Reports you never open are the same as no reports.
  • You have a list of every tool that might send as your domain: your mail server, app provider, newsletter, helpdesk, CRM, invoicing, HR and recruiting tools, and any scanners or printers that email.
  • Each of those sends DKIM signed with your domain, or you know which ones do not.
  • SPF is one record, under 10 lookups.
  • Someone owns the rollout and has a calendar reminder for each phase.

The rollout at a glance

Phase Record Move on when
1. Monitor v=DMARC1; p=none; rua=mailto:dmarc@example.com Every legitimate source passes and aligns for 2–4 weeks
2. Quarantine, partial v=DMARC1; p=quarantine; pct=25; rua=mailto:dmarc@example.com No complaints about missing mail for 1–2 weeks
3. Quarantine, full v=DMARC1; p=quarantine; rua=mailto:dmarc@example.com Reports stay clean for 2+ weeks
4. Reject, partial v=DMARC1; p=reject; pct=25; rua=mailto:dmarc@example.com No new legitimate failures
5. Reject, full v=DMARC1; p=reject; rua=mailto:dmarc@example.com Keep monitoring indefinitely

The partial steps are optional. A small organization with two senders can reasonably go none → quarantine → reject over a month. A large one with dozens of tools should take every step.

Domains you own but never send from are the exception. They can go straight to p=reject, with v=spf1 -all for SPF, because there is no legitimate mail to break. Do those first; it takes five minutes and removes easy targets for spoofing.

Phase 1: Monitor at p=none

At p=none, receivers take no action on failing mail but send you aggregate reports. Your job in this phase is to make the reports boring.

Turn reports into a sender inventory

Each report row gives a source IP, a message count, the SPF and DKIM results, and the domains involved. Group the source IPs and label each one:

Source Example What to do
Your server or provider, passing and aligned Your mail server, your app's provider Nothing
Yours, failing A helpdesk signing with its own domain Set up DKIM with your domain, or a custom return path
Forwarders and mailing lists A university or ISP forwarding to a new address Usually ignore; DKIM is the fix
Not yours Unknown IPs sending as you This is what enforcement blocks

To identify an unknown IP:

dig +short -x 198.51.100.25

The reverse DNS name usually reveals the provider. Then ask around: someone in marketing or finance may have signed up for a tool that sends as your domain.

Fix each failing legitimate sender

For each one, get DKIM signed with d=example.com. It is the more robust fix because it survives forwarding. Then, if the provider supports it, set a custom return path so SPF aligns too. Both are described in how to set up DKIM and how to set up an SPF record.

Watch your SPF lookup count as you add providers. Going over 10 lookups gives permerror; SPF too many DNS lookups covers the fixes.

Look at the right details in each report

The policy_evaluated block tells you the DMARC verdict, but the auth_results block tells you why:

<auth_results>
  <dkim>
    <domain>provider.example</domain>
    <selector>s1</selector>
    <result>pass</result>
  </dkim>
  <spf>
    <domain>bounces.provider.example</domain>
    <result>pass</result>
  </spf>
</auth_results>

This row passed DKIM and SPF but failed DMARC: both passed for the provider's domains, not example.com. That is the most useful pattern to recognize, because the fix is always the same: turn on custom domain signing at that provider. Sort sources by message count too. A failing source sending thousands of messages a week matters far more than one forwarded message.

Stay in this phase until at least one full business cycle has passed. Monthly invoices and quarterly mailings only appear in reports when they are sent.

Phase 2 and 3: Quarantine

v=DMARC1; p=quarantine; pct=25; rua=mailto:dmarc@example.com

Failing mail now goes to spam rather than the inbox, which is a softer failure than rejection: if you missed a sender, its mail is still findable. Tell your team before you change the record, so "my invoice went to spam" gets reported to you instead of worked around.

Raise pct in steps (25, 50, 100) or go straight to 100 if reports were clean. Our own domains, which send only from servers we control, run at p=quarantine with strict alignment:

v=DMARC1; p=quarantine; rua=mailto:dmarc@example.com; adkim=s; aspf=s

How pct actually works

pct (RFC 7489) applies the policy to that percentage of failing messages only; passing mail is never affected. The rest gets the next-softer treatment:

Record Failing mail in the pct sample The rest of the failing mail
p=quarantine; pct=25 Quarantined Treated as none
p=reject; pct=25 Rejected Quarantined

Receivers implement it as sampling, so the split is approximate. The DMARC revision at the IETF (often called DMARCbis) replaces pct with a simpler testing flag, so expect values other than 0 and 100 to matter less over time. Use it as a ramp, not something to rely on.

Phase 4 and 5: Reject

v=DMARC1; p=reject; rua=mailto:dmarc@example.com

Receivers that honor DMARC now refuse failing mail during the SMTP conversation. The sender gets a bounce, which at least means a forgotten legitimate tool shows up as an error rather than silence. Gmail's rejection, for example, cites the domain's DMARC policy.

If something legitimate breaks

  1. Step back to p=quarantine (or p=none if the sender is business-critical). It is just a TXT edit; lower the record's TTL beforehand so changes take effect quickly.
  2. Find the sender in the bounce message or in the next aggregate report.
  3. Set up DKIM with your domain for it, confirm with a test message that DMARC passes, then return to p=reject.

Write down what broke and why. The same kind of tool tends to be forgotten again.

Subdomains: the sp= tag

Spoofers often use subdomains you never created, such as billing.example.com. Without sp=, subdomains inherit p=. You can lock them down before the root domain is ready:

v=DMARC1; p=quarantine; sp=reject; rua=mailto:dmarc@example.com

Or the other way round, if a subdomain hosts a sender that is not ready yet:

v=DMARC1; p=reject; sp=quarantine; rua=mailto:dmarc@example.com

A subdomain that publishes its own _dmarc record, for example _dmarc.news.example.com, uses that record instead of the inherited policy. That is useful if a provider sends from news.example.com and needs a slower rollout.

For subdomains that never send mail, also publish v=spf1 -all on any that have DNS records, such as www.

Common mistakes

  • Skipping phase 1. Going straight to p=reject is how invoices and password resets go missing.
  • Ending phase 1 too early. A quarterly mailing will not show up in two weeks of reports.
  • Fixing with SPF alone. Forwarded mail breaks SPF. DKIM aligned with your domain is the robust fix.
  • Forgetting subdomains. Without sp=, an unused subdomain is as exposed as the root was.
  • Stopping reports after reaching reject. New tools get added; keep the rua and keep reading.
  • Two DMARC records. Receivers then act as if you have no policy at all.
  • DKIM records broken by a DNS edit. A duplicate DKIM TXT record once broke our own signing with "multiple DNS replies". At p=reject, that kind of mistake means bounced mail, so check after every DNS change.

How to verify it worked

dig +short TXT _dmarc.example.com

You should see one record with the policy you expect. Send a message from each legitimate sender to port25's verifier (check-auth@verifier.port25.com), to mail-tester.com and to a Gmail account. In Gmail's "Show original", look for dmarc=pass (p=REJECT). From your own server:

swaks --to check-auth@verifier.port25.com --from hello@example.com \
  --server mail.example.com --port 587 --tls \
  --auth LOGIN --auth-user hello@example.com

To see enforcement working, send an unsigned message with your From address from a machine not in your SPF record (one that can reach port 25) to your own Gmail account:

swaks --to you@gmail.com --from test@example.com \
  --server gmail-smtp-in.l.google.com --port 25

At p=reject Gmail should refuse it, citing your domain's DMARC policy. Our email checker shows the published policy for any domain.

DMARC is one of the Gmail and Yahoo bulk sender requirements, though they only require p=none. If you want someone to read the reports, fix the failing senders and walk the domain to enforcement, that is part of our Deliverability Fix; ongoing DMARC report monitoring is part of the Managed Server plan on our pricing page.