Publish DMARC for every domain and move it from p=none to p=reject¶
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¶
- 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.
- Read the reports for 2 to 4 weeks. Fix every legitimate source that fails both SPF and DKIM alignment (EX03).
- Move to p=quarantine (optionally with pct=10, 25, 50, 75, 100), then to p=reject. Do subdomains first and the parent domain last.
- 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.