Microsoft 365 Security Blog

SPF, DKIM and DMARC Across Client Tenants Without Breaking Their Mail

Written by Marcus Harris | Aug 14, 2026, 8:00:00 AM

Setting up email authentication on one domain is a twenty minute job. Doing it across thirty client tenants, where each one has a marketing platform, an invoicing tool and a booking system nobody told you about, is a different exercise entirely.

The failure mode is always the same. You tighten DMARC on a Friday, and on Monday the client rings because their appointment reminders stopped arriving three days ago and nobody noticed until a customer complained.

Here is the sequence that avoids that, and the audit that tells you which tenants are safe to touch.

What each record actually decides

Worth being precise, because the three get talked about as one thing and they are not.

SPF publishes which servers may send as the domain. DKIM signs each message so the receiver can verify it was not altered and came from an authorised sender. DMARC is the only one that instructs the receiver what to do when the first two fail, and asks for reports.

The part that catches people: SPF and DKIM publish facts. Nothing acts on those facts until DMARC is at quarantine or reject. A tenant with immaculate SPF and DKIM and a p=none record has no protection against impersonation whatsoever. It just has good telemetry.

Alignment is the step people skip. A message can pass SPF and still fail DMARC if the passing domain does not match the From header.

Audit before you touch anything

Run this across every domain you manage before planning any changes. It takes seconds per tenant and it tells you where you actually stand.

$domains = @("client-one.co.uk","client-two.com","client-three.co.uk")

foreach ($d in $domains) {
    $spf   = (Resolve-DnsName $d -Type TXT -EA 0).Strings |
             Where-Object { $_ -like "v=spf1*" }
    $dmarc = (Resolve-DnsName "_dmarc.$d" -Type TXT -EA 0).Strings |
             Where-Object { $_ -like "v=DMARC1*" }
    $dkim  = Resolve-DnsName "selector1._domainkey.$d" -Type CNAME -EA 0

    [pscustomobject]@{
        Domain     = $d
        SPF        = if ($spf)   { $spf }   else { "MISSING" }
        SPFPolicy  = if ($spf -match '(-all|~all|\?all)') { $Matches[1] } else { "none" }
        DKIM       = if ($dkim)  { "present" } else { "MISSING" }
        DMARC      = if ($dmarc) { $dmarc } else { "MISSING" }
    }
}

Pipe it to Export-Csv and you have a fleet-wide baseline you can show a client, and a work queue sorted by how exposed each one is.

In my experience the distribution is roughly: a third have no DMARC record at all, a third have p=none published years ago and never revisited, and the rest have something that looks right until you read it properly.

Step one: inventory the senders, not the records

This is the step that separates a clean rollout from an incident, and it is the one that cannot be scripted.

For each tenant, list every system that sends mail showing that domain in the From address. Microsoft 365 itself, obviously. Then the accounting platform, the CRM, the marketing tool, the e-signature service, the booking system, the survey tool somebody in marketing signed up for in 2023.

The reliable way to find them is not to ask the client, because they will forget. Publish DMARC at p=none with reporting first and let the reports tell you. Which is exactly why the next step exists.

Step two: SPF

One TXT record on the root. Only one is permitted, so edit rather than add.

Host:  @
Type:  TXT
Value: v=spf1 include:spf.protection.outlook.com -all

Two traps.

The ten lookup limit. SPF allows ten DNS lookups and each include: can consume several. Exceed it and the record returns permerror, which fails exactly as if you had published nothing. This bites on tenants with three or four platforms chained together, and it fails silently. Check the count before you publish, not after.

Hard fail versus soft fail. -all is what you want. ~all tells receivers to accept anyway, which makes the record decorative. Publish ~all only while you are still hunting senders.

Step three: DKIM

DKIM moved to the Defender portal.

Where to find itsecurity.microsoft.com › Email & collaboration › Policies & rules › Threat policies › Email authentication settings › DKIM

The CNAME format changed and now resolves to dkim.mail.microsoft with a tenant-specific partition character, so any guide showing a fixed onmicrosoft.com target is out of date. Pull the real values:

Connect-ExchangeOnline -DelegatedOrganization client-one.co.uk

Get-DkimSigningConfig |
  Format-List Domain,Enabled,Status,Selector1CNAME,Selector2CNAME

Publish both CNAMEs, wait for propagation, then enable signing:

Set-DkimSigningConfig -Identity client-one.co.uk -Enabled $true

Enabling before the records resolve throws an error rather than queuing, so check first.

Then repeat for every third-party sender. Each issues its own key. This is the afternoon, and it is the part that makes everything after it safe.

Step four: DMARC at monitor

Host:  _dmarc
Type:  TXT
Value: v=DMARC1; p=none; rua=mailto:reports@yourdomain.co.uk; fo=1; adkim=r; aspf=r;

Point rua at an aggregator, not a mailbox. The reports are XML and unreadable by eye, and a mailbox full of them is a mailbox you stop opening. If you are managing a fleet, use one aggregator account with every client domain in it, so you have a single console rather than thirty.

At p=none nothing about mail handling changes. You are only asking receivers to tell you what they see, which is how you find the senders the client forgot.

Step five: read the reports for two to four weeks

Cover a full billing cycle so monthly senders appear. You are looking for three things.

Recognised sources failing. Misconfiguration. Fix the include or publish the key.

Unrecognised sources. Either a forgotten platform or genuine spoofing. Identify every one before proceeding. This is where the value is.

Forwarding. Mail through mailing lists breaks SPF by design. DKIM usually survives, which is precisely why you need both.

Step six and seven: ramp, then reject

v=DMARC1; p=quarantine; pct=25; rua=mailto:reports@yourdomain.co.uk; fo=1;

Hold a week, check reports and junk folders, then 50, then 100. Failures land in junk rather than being refused, so a mistake here is recoverable.

v=DMARC1; p=reject; sp=reject; rua=mailto:reports@yourdomain.co.uk; fo=1;

Set sp=reject or an attacker can spoof accounts.clientdomain.co.uk freely while the root is locked down. Keep rua permanently, because reports are how you learn that somebody added a new platform without telling you.

Why this is worth putting on the roadmap

Microsoft, Google and Yahoo now enforce authentication on senders above 5,000 messages a day: SPF and DKIM passing, DMARC published, valid reverse DNS, one-click unsubscribe on marketing mail. Google moved from temporary deferrals to permanent rejections in late 2025.

Most of your clients are well below that threshold. The thresholds keep falling, though, and the filters those rules tightened are the same filters ordinary business mail passes through. Domains that authenticate land in inboxes. Domains that do not, increasingly do not.

The impersonation risk is the reason to do it. The deliverability improvement is what makes it an easy conversation with the client.

If you run the audit script above and it comes back mostly red, that is normal. It is also a very straightforward piece of work to sell.