Skip to main content
← All guides

SPF, DKIM, and DMARC in Microsoft 365 — the walk to p=reject

Most tenants published a DMARC record years ago and have sat at p=none ever since. That's the norm, not negligence: p=none is the only policy you can publish without first knowing every service that sends mail as your domain, and that inventory is real work nobody scheduled. The DNS records take ten minutes. This page is about the rest of the job.

The part checklists get wrong

"DMARC configured" and "DMARC enforced" are different controls, and the frameworks score them separately. CISA SCuBA splits them explicitly: MS.EXO.4.1 wants a syntactically valid record on every accepted custom domain; MS.EXO.4.2 (with CIS M365 v6 2.1.10) requires p=reject on each of them. A record reading p=none passes the first and fails the second, so a checklist that ticks "DMARC: yes" at p=none is reporting the publish step as the enforcement step. Same trap on SPF: CISA MS.EXO.2.2 is met only when every domain's SPF record ends in -all — a ~all softfail still permits a soft or neutral result at the receiver.

And "every accepted custom domain" means exactly that, including the parked ones that send nothing. A domain that sends no mail is the easiest p=reject you'll ever publish, and the one attackers check first.

The sender inventory is the actual work

Before any policy tightens, list everything that sends as your domain: Exchange Online, then the long tail — CRM, marketing platform, ticketing, payroll, monitoring alerts, the scan-to-email copier. For each one you need aligned SPF or aligned DKIM, and for most third-party platforms aligned DKIM (their console gives you CNAMEs for a custom signing domain) is the right answer, because stuffing their ranges into SPF walks you into the 10-DNS- lookup limit that silently breaks the whole record.

The record you can't guess at is the one that shows up in reports. Publish p=none with aggregate reporting first (rua=mailto:..., ideally into a report-parsing service rather than a mailbox nobody opens), and let two to four weeks of reports find the senders the inventory missed.

The walk itself

  1. SPF: one record per domain, v=spf1 include:spf.protection.outlook.com -all plus your verified third parties. The softfail argument (that ~all is harmless once DMARC enforces) has some merit at receivers that evaluate DMARC, but not every receiver does, and the framework text asks for -all regardless. Hard fail costs nothing once the inventory is honest.
  2. DKIM: add the two CNAME records per domain (M365 admin center → Settings → Domains → your domain → DNS records), then enable signing in the Defender portal → Email & collaboration → Policies & rules → Threat policies → Email authentication settings → DKIM. Enable it for the custom domains, not just the onmicrosoft.com default.
  3. DMARC: _dmarc.yourdomain.com TXT, v=DMARC1; p=none; rua=... → read reports → fix senders → p=quarantine (use pct= to ramp if the volume is scary) → p=reject. Add sp=reject so subdomains don't inherit a hole. The tenants that get stuck are the ones with no named owner for the report-reading step; put a recurring 30-minute review on a calendar or the walk stops at step one.

What a renewal reviewer actually accepts

Not "DMARC is configured." A dated export of the three DNS records for every accepted domain (p=reject visible in the DMARC record, -all visible in SPF) plus a recent aggregate-report summary showing alignment pass rates. The per-domain completeness is what reviewers have learned to probe: one enforced primary domain and three forgotten p=none vanity domains reads as an unfinished control, because it is.

How Calibrant checks this

As separate checks, matching the framework split: SPF hard-fail, DKIM enabled, and DMARC at p=reject, each evaluated across every accepted custom domain rather than just the primary. A published-but-p=none record fails the enforcement check by design, so the readout can't report the publish step as done. Findings land in a dated assessment report built for the renewal exchange above.

Early access is by invite — code SEO-EMAIL-AUTH at calibrant.ai.