Skip to content

Publish DMARC for every domain and move it from p=none to p=reject

Control
EX04
Area
Email authentication (SPF, DKIM, DMARC)
Priority
High
Effort
1 h set-up + 4 to 8 weeks monitoring
Last reviewed

Licence and role

Any plan. DNS changes only.

Why it matters

SPF and DKIM alone do not stop someone sending mail with your domain in the From address. DMARC tells receivers what to do when both fail. Microsoft's goal for every custom domain and subdomain is p=reject, reached step by step with aggregate reports so good mail is not rejected along the way.

Steps

  1. Create a dedicated mailbox or Microsoft 365 group (or a DMARC reporting service) for aggregate reports. Publish _dmarc TXT v=DMARC1; p=none; pct=100; rua=mailto:<that address> on each sending domain.
  2. Read the reports for 2 to 4 weeks. Fix every legitimate source that fails both SPF and DKIM alignment (EX03).
  3. Move to p=quarantine (optionally with pct=10, 25, 50, 75, 100), then to p=reject. Do subdomains first and the parent domain last.
  4. Domains that never send mail, and the onmicrosoft.com domain if you do not send from it, get v=DMARC1; p=reject; straight away (the onmicrosoft record is added in the Microsoft 365 admin center > Domains > DNS records).

Impact on users

At p=quarantine and p=reject, mail from services that fail both SPF and DKIM alignment lands in Junk or is rejected by receivers. Nothing changes for inbound mail to your users.

Rollback

Lower the policy (p=reject to p=quarantine or p=none) in DNS. Receivers pick it up within the record TTL.

Read-only check

Reads settings only. It changes nothing in your tenant or on the device.

Modules, connection and role
Requires: ExchangeOnlineManagement module (3.x). DNS checks use Resolve-DnsName (Windows PowerShell 5.1 or PowerShell 7 on Windows).
Connect first:
Connect-ExchangeOnline
Connect-IPPSSession   # only for the alert policy check (EX20)
Role: Global Reader, Security Reader or View-Only Organization Management is enough for these reads.
foreach ($d in (Get-AcceptedDomain).DomainName) { $t = (Resolve-DnsName -Type TXT -Name "_dmarc.$d" -ErrorAction SilentlyContinue | Where-Object { $_.Strings -match '^v=DMARC1' }).Strings -join ' '; "DMARC $d : $(if ($t) { $t } else { 'NO DMARC RECORD' })" }

How to verify

Every accepted domain returns a DMARC record; sending domains are at p=reject (or on a dated plan to get there); parked domains and onmicrosoft.com are at p=reject; aggregate reports arrive daily.

Sources

This control is in the Exchange Online Protection Kit

The free Top 5 has this control with its Jira import, Excel project plan, tracker CSV and the read-only PowerShell script. The Complete kit has all 20 controls.

Download the free Top 5 See the kit