DMARC Failed… But the Email Was Delivered
The email looked legitimate. The sender felt familiar. No high-severity alert appeared.
But something did not add up.
Behind the scenes, DMARC had failed. Authentication did not align. The message should have raised concern.
Yet it was still delivered.
This Agent Foskett investigation shows how to use Microsoft Defender XDR and KQL to uncover delivered DMARC failures, sender mismatch and spoofing behaviour that can hide behind normal-looking email delivery.
The goal is simple: stop assuming “delivered” means safe.
DMARC delivery summary
A practical Microsoft Defender investigation focused on email that failed DMARC but still reached users, showing how AuthenticationDetails, sender alignment and delivery outcome need to be read together.
What we are looking for
Baseline query: DMARC failed but email was delivered
- 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"
| where DeliveryAction has_any ("Delivered", "Allowed", "Junked")
| project
Timestamp,
SenderFromAddress,
SenderFromDomain,
SenderMailFromAddress,
SenderMailFromDomain,
RecipientEmailAddress,
Subject,
AuthenticationDetails,
DeliveryAction,
NetworkMessageId
| order by Timestamp descWhat this activity can indicate
Query: sender alignment and DMARC failure
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 15
- 16
- 17
EmailEvents
| where Timestamp > ago(30d)
| where AuthenticationDetails has "dmarc=fail"
| where SenderFromDomain != SenderMailFromDomain
| project
Timestamp,
SenderFromAddress,
SenderFromDomain,
SenderMailFromAddress,
SenderMailFromDomain,
RecipientEmailAddress,
Subject,
AuthenticationDetails,
DeliveryAction
| order by Timestamp descQuery: group delivered DMARC failures by campaign pattern
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
EmailEvents
| where Timestamp > ago(30d)
| where AuthenticationDetails has "dmarc=fail"
| where DeliveryAction has_any ("Delivered", "Allowed", "Junked")
| summarize
MessageCount = count(),
RecipientCount = dcount(RecipientEmailAddress),
FirstSeen = min(Timestamp),
LastSeen = max(Timestamp)
by SenderFromAddress, SenderFromDomain, Subject, DeliveryAction
| where MessageCount >= 2
| order by MessageCount descQuery: pivot from delivered DMARC failure to URL clicks
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
let DeliveredDmarcFailures =
EmailEvents
| where Timestamp > ago(30d)
| where AuthenticationDetails has "dmarc=fail"
| where DeliveryAction has_any ("Delivered", "Allowed", "Junked")
| project NetworkMessageId, EmailTime = Timestamp, SenderFromAddress, RecipientEmailAddress, Subject;
DeliveredDmarcFailures
| join kind=inner (
UrlClickEvents
| where Timestamp > ago(30d)
| project NetworkMessageId, ClickTime = Timestamp, AccountUpn, Url, ActionType
) on NetworkMessageId
| order by ClickTime descWhat happens after the user trusts the message?
View session hijacking →
Investigate file access →
Read more →
Continue your investigation
Open guide →
Review spoofing →
Open KQL hub →
