DMARC Failures Explained with KQL
DMARC failures can look scary at first glance, but they do not always mean a mailbox has been compromised.
This page explains how to investigate DMARC failures in Microsoft Defender XDR using EmailEvents, AuthenticationDetails, sender alignment, delivery actions and URL click pivots.
It is built for practical investigation: what failed, who received it, whether it was delivered, whether anyone clicked, and what to check next.
DMARC investigation summary
A focused Microsoft Defender KQL resource for investigating dmarc=fail results, sender alignment issues, delivery decisions and click activity.
How to read DMARC failures (what actually matters)
Internal-looking sender
Multiple recipients or repeated subjects
Forwarded mail breaking alignment
Mailing platforms using separate sending domains
Pivot to URL clicks
Review sign-ins and user behaviour
Start with DMARC failures in EmailEvents
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
EmailEvents
| where Timestamp > ago(30d)
| where AuthenticationDetails has "dmarc=fail"
| project
Timestamp,
SenderFromAddress,
SenderFromDomain,
SenderMailFromAddress,
SenderMailFromDomain,
RecipientEmailAddress,
Subject,
AuthenticationDetails,
DeliveryAction,
ThreatTypes,
NetworkMessageId
| order by Timestamp desc
How to read this query
SenderFromDomain β SenderMailFromDomain
Repeated subject across users = campaign signal
Forwarded mail breaking authentication
Blocked or quarantined failures already handled
Always follow the behaviour, not just the alert
π Next step β Compare with full spoofing patterns or continue into the full KQL threat hunting guide.
Compare DMARC, SPF and DKIM failures
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
EmailEvents
| where Timestamp > ago(30d)
| where AuthenticationDetails has_any ("spf=fail", "dkim=fail", "dmarc=fail")
| extend
SPF_Fail = iff(AuthenticationDetails has "spf=fail", "Yes", "No"),
DKIM_Fail = iff(AuthenticationDetails has "dkim=fail", "Yes", "No"),
DMARC_Fail = iff(AuthenticationDetails has "dmarc=fail", "Yes", "No")
| project
Timestamp,
SenderFromAddress,
SenderFromDomain,
SenderMailFromDomain,
RecipientEmailAddress,
Subject,
SPF_Fail,
DKIM_Fail,
DMARC_Fail,
AuthenticationDetails,
DeliveryAction
| order by Timestamp desc
Find delivered messages where DMARC failed
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
EmailEvents
| where Timestamp > ago(30d)
| where AuthenticationDetails has "dmarc=fail"
| where DeliveryAction !in~ ("Blocked", "Quarantined", "Junked")
| project
Timestamp,
SenderFromAddress,
SenderFromDomain,
SenderMailFromDomain,
RecipientEmailAddress,
Subject,
AuthenticationDetails,
DeliveryAction,
NetworkMessageId
| order by Timestamp desc
π Next step β Compare delivered DMARC failures with full spoofing patterns.
Group DMARC failures by sender domain
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
EmailEvents
| where Timestamp > ago(30d)
| where AuthenticationDetails has "dmarc=fail"
| summarize
MessageCount = count(),
RecipientCount = dcount(RecipientEmailAddress),
FirstSeen = min(Timestamp),
LastSeen = max(Timestamp)
by SenderFromDomain, SenderMailFromDomain, Subject, DeliveryAction
| order by MessageCount desc
Pivot DMARC failures to URL clicks
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
let DMARCFailures =
EmailEvents
| where Timestamp > ago(30d)
| where AuthenticationDetails has "dmarc=fail"
| project NetworkMessageId, EmailTime = Timestamp, SenderFromAddress, RecipientEmailAddress, Subject, DeliveryAction;
DMARCFailures
| join kind=inner (
UrlClickEvents
| where Timestamp > ago(30d)
| project NetworkMessageId, ClickTime = Timestamp, AccountUpn, Url, ActionType
) on NetworkMessageId
| order by ClickTime desc
π If clicks are present, continue β check identity activity after interaction.
What to do after a DMARC failure
Continue your investigation
Related case study β The email came from meβ¦ but I never sent it.
