If your custom domain only has the SPF record the Microsoft 365 setup wizard asked for, you're a third of the way there. Receiving systems increasingly expect SPF, DKIM and DMARC to pass and to line up with the From address people actually see. In this post I'll set up all three for a domain that sends from Exchange Online, show how to confirm the result in message headers, and cover the mistakes that break them most often.
How it works#
- SPF is a TXT record listing the servers allowed to send for the envelope (MAIL FROM) domain. On its own it says nothing about the From address in the mail client.
- DKIM adds a signature to each message. The signing domain appears as
d=in theDKIM-Signatureheader, and in Microsoft 365 the public keys are published through two CNAME records that point to Microsoft-hosted keys. - DMARC is a TXT record at
_dmarc.yourdomain. A message passes when SPF or DKIM passes and that domain aligns with the From domain. The record tells receivers what to do with failures and where to send reports.
Before you start, you need the domain added and verified in Microsoft 365, access to its DNS host, and a list of every service that sends as your domain: marketing platforms, CRM and ticketing tools, scanners and on-premises servers. Microsoft already handles SPF and DKIM for your onmicrosoft.com domain.
Step 1: Publish one correct SPF record#
For a domain that only sends through Exchange Online, create a single TXT record at the root of the domain:
v=spf1 include:spf.protection.outlook.com -allIf an on-premises server also sends for the domain, add its public IP in front, for example v=spf1 ip4:203.0.113.10 include:spf.protection.outlook.com -all. Keep these rules in mind:
- One SPF record per domain or subdomain. Two records produce a permanent error (
permerror), so merge them. - Ten DNS lookups maximum. Every
include,a,mx,existsandredirectcosts at least one, nested includes count too, andip4,ip6andallcost nothing. Go over the limit and SPF returnspermerror. -allversus~all: hard fail says unlisted sources aren't authorized; soft fail asks receivers to accept but mark the message. Microsoft recommends-allfor Microsoft 365 domains, alongside DKIM and DMARC.- Don't flatten
include:spf.protection.outlook.cominto IP addresses; Microsoft's sending addresses change.
Third-party bulk senders are better placed on a subdomain such as marketing.contoso.com with its own SPF record. That protects your main domain's reputation and gives the subdomain its own lookup budget.
Step 2: Turn on DKIM signing for the custom domain#
- In the Microsoft Defender portal (
security.microsoft.com), go to Email & collaboration › Policies & rules › Threat policies › Email authentication settings and open the DKIM tab. - Try to switch the domain's toggle to Enabled. An error says the CNAME records are missing. That's expected: the keys now exist, so select OK.
- Click the domain row to open its details and copy the two values under Publish CNAMEs.
- At your DNS host, create two CNAME records with the hostnames
selector1._domainkeyandselector2._domainkey, each pointing to the value the portal gave you. - Give Microsoft 365 a few minutes (sometimes longer) to detect them, then turn on Sign messages for this domain with DKIM signatures. The status should change to Signing DKIM signatures for this domain.
Watch out: copy the CNAME targets exactly rather than building them by hand. Domains added since May 2025 use a newer format such as selector1-contoso-com._domainkey.contoso.n-v1.dkim.mail.microsoft, where Microsoft assigns the letter before -v1. Older domains keep targets ending in .onmicrosoft.com.
Prefer PowerShell? The same values and switch are available in Exchange Online PowerShell:
Get-DkimSigningConfig -Identity contoso.com | Format-List Name, Enabled, Status, Selector1CNAME, Selector2CNAME
# If the domain isn't listed yet:
New-DkimSigningConfig -DomainName contoso.com -Enabled $false
# Once both CNAMEs resolve:
Set-DkimSigningConfig -Identity contoso.com -Enabled $trueStep 3: Roll out DMARC in stages#
Create a TXT record with the hostname _dmarc, starting in monitoring mode with aggregate reports:
v=DMARC1; p=none; rua=mailto:dmarc-reports@contoso.com- p=none: collect the aggregate reports (daily XML files, usually compressed) in a dedicated mailbox or a DMARC reporting service. Find every legitimate source and fix its SPF or DKIM alignment.
- p=quarantine: once the reports look clean, ask receivers to treat failures as suspicious. You can phase it in with
pct=, for example 10, 25, 50, 75 and then 100. - p=reject: the end goal, where failing mail is refused.
Microsoft suggests starting with low-volume subdomains and leaving the parent domain until last. Subdomains inherit the parent's policy unless they publish their own record, and each domain should have exactly one _dmarc record.
Verify with DNS and message headers#
First confirm the records resolve publicly:
Resolve-DnsName -Name contoso.com -Type TXT
Resolve-DnsName -Name selector1._domainkey.contoso.com -Type CNAME
Resolve-DnsName -Name _dmarc.contoso.com -Type TXTThen send a message to a mailbox outside your organization (DKIM signing is skipped for mail that stays inside it) and view the headers. When the receiver is another Microsoft 365 tenant, a healthy result looks like this:
Authentication-Results: spf=pass (sender IP is 198.51.100.25)
smtp.mailfrom=contoso.com; dkim=pass (signature was verified)
header.d=contoso.com;dmarc=pass action=none
header.from=contoso.com;compauth=pass reason=100The detail to check is header.d=contoso.com. If it shows any other domain, or DKIM reports none, signing with your custom domain isn't active yet. Microsoft's Message Header Analyzer makes long headers much easier to read.
Tips & gotchas#
- A second SPF record added for a new SaaS tool is a common way to break SPF overnight. Merge it into the existing record.
- SPF passes but DMARC fails for a third-party sender that uses its own domain as MAIL FROM. Set up DKIM at that service with your domain (
d=contoso.com), or a custom return-path on your domain. - DNS host quirks: enter just
selector1._domainkeyas the hostname (most hosts append the domain), turn off proxying for the DKIM CNAMEs on DNS services that offer it, and create both selectors even though only one is active at a time. - Reports sent to another domain need that domain to publish an authorization record, such as
contoso.com._report._dmarcwith the valuev=DMARC1;. - Parked domains that never send mail should get
v=spf1 -all, ap=rejectDMARC record and no DKIM records. If you don't send from youronmicrosoft.comdomain, add a DMARC record for it too, under Settings › Domains in the Microsoft 365 admin center.