🍱 Lunchbox Hands

dns

DMARC From p=none to p=reject: The Rollout That Doesn't Break Your Own Mail

Publish p=none, read the aggregate reports, fix the usual failures and alignment, then step up to quarantine and reject without losing your own invoices.

Most DMARC horror stories have the same plot. Someone reads that p=reject stops spoofing, publishes it on a Friday, and on Monday the invoices, password resets and newsletter are all bouncing. DMARC doesn’t break your mail. It enforces a list of legitimate senders that nobody has written down yet, and the rollout is how you write it down. The sequence below gets you from monitoring to enforcement on data, not guesses.

If you want the background on what SPF, DKIM and DMARC records are, start with DNS record types explained. This post covers the order to do things in.

Step 1: publish p=none with a report address

Your first record is a TXT record at _dmarc.example.com:

v=DMARC1; p=none; rua=mailto:[email protected]

p=none tells receivers to deliver mail exactly as before. rua is the part that matters: it asks every participating receiver to send you a daily aggregate report. Without it, RFC 9989 (the current DMARC standard, §4.7) says receivers “MUST NOT generate aggregate feedback reports” for your domain. A p=none record without rua protects nothing and teaches you nothing, and our DMARC checker warns about exactly that case.

Two rules trip people up. v=DMARC1 must be the first tag, or “the entire record MUST be ignored” (RFC 9989 §4.7). And there must be exactly one record at _dmarc. If a receiver finds more than one, they are all discarded (§4.10), so in practice you have no policy at all.

The external report address gotcha

Most people point rua at a report service in another domain. That works only if the service agrees. RFC 9990 §4 says that when the report address is in a different organizational domain, the receiver must look up a TXT record built from both names:

example.com._report._dmarc.reports.example.net.  TXT  "v=DMARC1"

That record lives in the vendor’s DNS, not yours. If it’s missing, the URI “MUST be ignored”. You get no reports and no error message. Reputable services publish it for every customer (often as a wildcard, *._report._dmarc.vendor), but check. The DMARC checker builds that name for each outside rua and ruf address and looks it up, so you can see whether your reports actually have somewhere to go.

Step 2: read an aggregate report

Reports arrive as XML, usually zipped. The format is in RFC 9990 §3.1.1. Stripped down, one report from one receiver looks like this:

<feedback>
  <report_metadata>
    <org_name>google.com</org_name>
    <report_id>1234567890</report_id>
    <date_range><begin>1792022400</begin><end>1792108799</end></date_range>
  </report_metadata>
  <policy_published>
    <domain>example.com</domain>
    <adkim>r</adkim><aspf>r</aspf>
    <p>none</p><sp>none</sp><testing>n</testing>
  </policy_published>
  <record>
    <row>
      <source_ip>198.51.100.25</source_ip>
      <count>412</count>
      <policy_evaluated>
        <disposition>none</disposition>
        <dkim>fail</dkim>
        <spf>fail</spf>
      </policy_evaluated>
    </row>
    <identifiers>
      <header_from>example.com</header_from>
      <envelope_from>bounces.mailvendor.example</envelope_from>
    </identifiers>
    <auth_results>
      <spf><domain>bounces.mailvendor.example</domain><scope>mfrom</scope><result>pass</result></spf>
    </auth_results>
  </record>
</feedback>

Read it in this order:

ElementWhat it tells you
report_metadataWho sent the report and which period it covers (begin/end are Unix seconds, UTC).
policy_publishedThe policy the receiver saw. If this doesn’t match what you published, you have a DNS problem.
row/source_ip + countOne sending IP and how many messages it sent as your domain.
policy_evaluatedThe DMARC verdict: did DKIM and SPF pass aligned, and what was done (disposition).
auth_resultsThe raw SPF and DKIM results, before alignment.

The gap between the last two is the whole game. In the example, auth_results says SPF passed, but policy_evaluated says SPF failed. That’s an alignment failure, covered in step 4.

Group rows by source_ip, look up who owns each IP, and sort into three piles: your senders that pass, your senders that fail, and strangers. Strangers are spoofers or forwarders, and DMARC is supposed to stop the spoofers. Your failing senders are the work list.

(RFC 7489, the original DMARC spec, was obsoleted in May 2026 by RFC 9989, 9990 and 9991. Reports from receivers still on the old format look the same, minus the new np, testing and discovery_method fields and with a pct element instead.)

Step 3: fix the three usual failures

Almost every failing row comes down to one of these.

1. A SaaS sender that isn’t in SPF. The CRM, help desk, billing system or event platform sends as [email protected], and nobody added its include. Each provider documents its own include host. Add it to your one SPF record. Don’t create a second one, since two v=spf1 records are a permerror (RFC 7208 §4.5).

2. DKIM never turned on at the provider. Some providers sign with their own domain until you publish their DKIM key in your DNS and flip a switch in their admin panel. Their signature passes, but it’s for the wrong domain, so it doesn’t count for DMARC. This is the better fix whenever a provider offers it, because DKIM survives forwarding and SPF does not.

3. SPF over the 10-lookup limit. RFC 7208 §4.6.4: include, a, mx, ptr, exists and redirect each count, across your record and everything it includes, and the total “MUST” stay at 10 or below, or the result is permerror. Includes nest. As of today _spf.google.com and spf.protection.outlook.com are pure IP lists (one lookup each), while sendgrid.net includes a further record, so it costs two. Add five or six vendors and you’re at 11 without seeing it. The SPF record checker follows every include, shows the total against the limit, and flags void lookups and includes that point at domains with no record.

A record that fixes the first two failures for Google Workspace plus one mail vendor:

v=spf1 include:_spf.google.com include:sendgrid.net -all

Step 4: why SPF passes and DMARC still fails

SPF checks the envelope sender (the Return-Path, RFC5321.MailFrom), which the recipient never sees. DMARC cares about the visible From address. A message passes DMARC only if SPF or DKIM passes and the domain it passed for aligns with the From domain.

Many email service providers put their own bounce domain in the Return-Path so they can process bounces. SPF passes for bounces.mailvendor.example, which isn’t your domain, so it doesn’t align (the example row above). The fixes are a custom return-path (a subdomain of yours, CNAMEd to the vendor) or, better, DKIM signing with d=example.com.

Alignment has two modes, set by aspf and adkim:

ModeRule (RFC 9989 §3.2.10)bounce.example.com vs From example.com
Relaxed (r, default)Same organizational domainAligned
Strict (s)Exact matchNot aligned

Leave both relaxed. Strict mostly breaks legitimate mail from your own subdomains.

Step 5: quarantine, then reject

Once a month of reports shows every legitimate sender passing aligned, step up. RFC 9989 §7.4 spells out the pace: p=none “for at least a month,” then p=quarantine “for an equally long period of time,” comparing the results before reject.

v=DMARC1; p=quarantine; rua=mailto:[email protected]

Give it another month of reports. Then:

v=DMARC1; p=reject; rua=mailto:[email protected]

You may see older guides ramp with pct=25, pct=50 and so on. RFC 9989 removes pct. Its Appendix A.6 says values other than 0 and 100 were “usually not accurately applied,” and its tag registry marks pct “historic.” The replacement is t=y (“testing”), which asks receivers to apply one level below the published policy: p=reject; t=y gets quarantine treatment, p=quarantine; t=y gets none. Receivers still on RFC 7489 may read pct, so it can stay for compatibility, but don’t count on a percentage ramp.

Two more points from §7.4. Domains whose users post to mailing lists “SHOULD NOT” publish p=reject. If your domain is mostly people, p=quarantine may be the right place to stop. If it’s mostly machines (receipts, alerts, marketing), reject is reasonable, but a p=reject domain “MUST NOT rely solely on SPF” and must sign with DKIM.

Step 6: subdomains and sp

A subdomain with no _dmarc record of its own uses the organizational domain’s record. Under RFC 9989 §4.10.1 the receiver checks the exact From domain first, then walks up the DNS tree one label at a time (the “DNS Tree Walk,” which replaces the old Public Suffix List lookup). The sp tag sets the policy for subdomains, and when it’s absent (and np doesn’t apply), “the policy specified by the ‘p’ tag MUST be applied for subdomains.”

So p=reject at example.com already covers anything.example.com. The dangerous record is this one:

v=DMARC1; p=reject; sp=none; rua=mailto:[email protected]

It locks the front door and leaves every subdomain open. The DMARC checker grades it “Partially enforced” and warns about it specifically. If you’re launching a new sending subdomain, give it its own record in monitoring mode instead of weakening sp for everyone:

_dmarc.news.example.com.  TXT  "v=DMARC1; p=none; rua=mailto:[email protected]"

Note that sp inside a subdomain’s own record is ignored (RFC 9989 §4.7). RFC 9989 also adds np, a policy for subdomains that don’t exist at all (for example xk7q.example.com). Without it, they fall back to sp, then p.

The deadline that already passed

Since February 2024, Google and Yahoo have required this from senders. Google’s email sender guidelines require SPF or DKIM from all senders. Senders of “more than 5,000 messages per day to Gmail accounts” need SPF and DKIM, a DMARC record (which “can be set to none”), and a From domain aligned with either the SPF or DKIM domain. Yahoo’s sender best practices say the same: SPF or DKIM for everyone, and for bulk senders both, plus “a valid DMARC policy with at least p=none” that passes, with alignment. Yahoo doesn’t publish a volume threshold. Step 1 of this rollout is the minimum both ask for.

Checking each step

After every DNS change, wait out the TTL and confirm the new value with the DNS propagation checker, or read the raw TXT answer with DNS lookup. Then run the SPF record checker for lookup count and syntax and the DMARC checker for the grade, the tag-by-tag findings and the external report authorization. Its Generate tab writes the record for each step above. And if a report address itself bounces, the email validator will tell you whether its domain can receive mail at all (and why valid syntax isn’t the same as deliverable).