diff --git a/defender-office-365/reports-mdo-email-collaboration-dashboard.md b/defender-office-365/reports-mdo-email-collaboration-dashboard.md
index b4f9cc97e8..8a3ea09108 100644
--- a/defender-office-365/reports-mdo-email-collaboration-dashboard.md
+++ b/defender-office-365/reports-mdo-email-collaboration-dashboard.md
@@ -18,7 +18,7 @@ ms.collection:
description: Admins can learn about the information on the Microsoft Defender for Office 365 Overview dashboard in the Microsoft Defender portal.
ms.custom:
ms.service: defender-office-365
-ms.date: 07/14/2025
+ms.date: 07/16/2025
appliesto:
- ✅ Microsoft Defender for Office 365 Plan 1 and Plan 2
- ✅ Microsoft Defender XDR
@@ -56,8 +56,9 @@ The graph on the **Phish / Malware Efficacy** card visually represents the prote
- **Pre-delivery**: Items detected before they reach the recipient's mailbox.
- **Post-delivery**: Items removed after the item was delivered to the recipient's mailbox via [zero-hour auto purge (ZAP)](zero-hour-auto-purge.md).
-- **Uncaught**: Delivered items that ZAP identified but didn't remove due to them already being remediated. For example:
+- **Uncaught**: Delivered items that ZAP identified but failed to remove. For example:
- Admin deletions or remediations.
+ - ZAP being disabled for the specific mailboxes.
- [Admin submissions](submissions-admin.md) to Microsoft identifying the message as malware or phishing.
- User deletions.
- Non-Microsoft security provider deletions.
@@ -256,87 +257,47 @@ Organizations with Defender for Office 365 Plan 2 can use the following query in
> The numbers might differ slightly due to the different refresh rates for advanced hunting vs. reporting data.
```kusto
-// This query by default will take the last 30 days of data.
-// The query and calculation can be tweaked to meet individual needs, and will update over time to get incrementally more accurate.
-// Ben Harris - Microsoft Defender for Office 365 PM.
let _startTime = ago(30d);
let _endTime = now();
-// Get all mailflow detected as clean at time of delivery
-let EmailEventsClean = materialize(
+let PreDelivery = toscalar(
EmailEvents
- | where Timestamp between (_startTime .. _endTime) and EmailDirection == "Inbound"
- | where ThreatTypes !contains "Phish" and ThreatTypes !contains "Malware"
- | project NetworkMessageId,ThreatTypes
-);
-// Get all mailflow detected as phish or malware at time of delivery
-let EmailEventsThreats = materialize(
- EmailEvents
- | where Timestamp between (_startTime .. _endTime) and EmailDirection == "Inbound"
- | where ThreatTypes contains "Phish" or ThreatTypes contains "Malware"
+ | where Timestamp between (_startTime .. _endTime)
+ and EmailDirection == "Inbound"
+ and DeliveryLocation in ("Junk folder", "Quarantine")
+ and (ThreatTypes contains "Phish" or ThreatTypes contains "Malware")
| extend MDO_detection = parse_json(DetectionMethods)
| extend FirstDetection = iif(isempty(MDO_detection), "Clean", tostring(bag_keys(MDO_detection)[0]))
| extend FirstSubcategory = iif(FirstDetection != "Clean" and array_length(MDO_detection[FirstDetection]) > 0, strcat(FirstDetection, ": ", tostring(MDO_detection[FirstDetection][0])), "No Detection (clean)")
- | project NetworkMessageId,FirstDetection,FirstSubcategory,MDO_detection,ThreatTypes
+ | summarize PreDelivery = count()
);
-// Get all post delivery ZAP / Redelivery events, and arg_max them to ensure we have the latest verdict to work with for each
-let EmailPostDeliveryFiltered = materialize(
+let PostDelivery = toscalar(
EmailPostDeliveryEvents
- | where Timestamp between (_startTime .. datetime_add('day', 7, _endTime))
- | where ActionType in ("Malware ZAP","Phish ZAP","Redelivery")
- | extend Key = strcat(NetworkMessageId , "-", RecipientEmailAddress)
- | summarize arg_max(Timestamp, *) by Key
- | project Action,ActionType,ActionResult,ThreatTypes,NetworkMessageId
-);
-// Optional - get all admin submissions for malware or phish, so we can also count these in the miss bucket.
-let CloudAppEventsFiltered = materialize(
- CloudAppEvents
- | where Timestamp between (_startTime .. datetime_add('day', 7, _endTime))
- | where ActionType == "AdminSubmissionSubmitted"
- | extend SubmissionType = tostring(parse_json(RawEventData).SubmissionType)
- | extend NetworkMessageId = tostring(parse_json(RawEventData).ObjectId)
- | where SubmissionType in ("1", "2")
- | project SubmissionType,NetworkMessageId
-);
-// get the number of threats caught in mailflow
-let Mal_Phish_Mailflow = toscalar(
- EmailEventsThreats
- | summarize count()
+ | where Timestamp between (_startTime .. _endTime)
+ and ActionType in ("Malware ZAP","Phish ZAP")
+ and ActionResult in ("Success","UserTriaged")
+ | summarize PostDelivery = count()
);
-// get the number of threats caught in mailflow which turned out to be false positives (FPs) so we can correct the calculation
-let FP_ZAP = toscalar(
- EmailPostDeliveryFiltered
- | where ThreatTypes !contains "Phish" and ThreatTypes !contains "Malware" and ActionType == "Redelivery"
- | join kind=leftsemi (EmailEventsThreats) on NetworkMessageId
- | summarize count()
-);
-// get the number of threats successfully cleaned up post delivery, ignoring where administrative policy stopped action
-let FN_ZAP_Successful = toscalar(
- EmailPostDeliveryFiltered
- | where ActionType in ("Malware ZAP","Phish ZAP") and ActionResult in ("Success","AdminPolicy")
- | join kind=leftsemi (EmailEventsClean) on NetworkMessageId
- | summarize count()
-);
-// get the number of threats unsuccessfully cleaned up post delivery.
-let FN_ZAP_Unsuccessful = toscalar(
- EmailPostDeliveryFiltered
- | where ActionType in ("Malware ZAP","Phish ZAP") and ActionResult !in ("Success","AdminPolicy")
- | join kind=leftsemi (EmailEventsClean) on NetworkMessageId
- | summarize count()
+let Uncaught = toscalar(
+ EmailPostDeliveryEvents
+ | where Timestamp between (_startTime .. _endTime)
+ and ActionType in ("Malware ZAP","Phish ZAP")
+ and ActionResult !in ("Success", "UserTriaged")
+ | summarize Uncaught = count()
);
-// join the administrative submissions to clean mailflow to find the additional miss
-let FN_Admin_Submissions = toscalar(
- CloudAppEventsFiltered
- | join kind=rightsemi (EmailEventsClean) on NetworkMessageId
- | summarize count()
- );
- // print each result, and run the calculation to work out effectiveness at time of delivery and post delivery.
-union withsource=Table
- (print StatisticName="Mal/Phish Mailflow totals - Minus FPs", Value=toreal(Mal_Phish_Mailflow) - toreal(FP_ZAP)),
- (print StatisticName="Admin Mal/Phish FNs Submitted", Value=toreal(FN_Admin_Submissions)),
- (print StatisticName="Mal/Phish FPs Reverse Zapped", Value=toreal(FP_ZAP)),
- (print StatisticName="Mal / Phish Successfully Zapped", Value=toreal(FN_ZAP_Successful)),
- (print StatisticName="Mal / Phish UN-Successfully Zapped", Value=toreal(FN_ZAP_Unsuccessful)),
- (print StatisticName="Effectiveness Post Delivery", Value=abs(round(((toreal(FN_Admin_Submissions)+toreal(FN_ZAP_Unsuccessful))/(toreal(Mal_Phish_Mailflow)+toreal(FN_ZAP_Successful)+toreal(FN_ZAP_Unsuccessful)+toreal(FN_Admin_Submissions)-toreal(FP_ZAP))*100-100),2))),
- (print StatisticName="Effectiveness Pre-Delivery", Value=abs(round(((toreal(FN_Admin_Submissions)+toreal(FN_ZAP_Unsuccessful)+toreal(FN_ZAP_Successful))/(toreal(Mal_Phish_Mailflow)+toreal(FN_ZAP_Successful)+toreal(FN_ZAP_Unsuccessful)+toreal(FN_Admin_Submissions)-toreal(FP_ZAP))*100-100),2)))
+let PreDeliveryReal = toreal(PreDelivery);
+let PostDeliveryReal = toreal(PostDelivery);
+let UncaughtReal = toreal(Uncaught);
+let Effectiveness = round(
+ iif(
+ (PreDeliveryReal + PostDeliveryReal + UncaughtReal) == 0,
+ 0.0,
+ ((PreDeliveryReal + PostDeliveryReal) / (PreDeliveryReal + PostDeliveryReal + UncaughtReal)) * 100.0
+ ), 2
+);
+union
+ (print StatisticName = "Pre-Delivery Catch", Value = PreDeliveryReal),
+ (print StatisticName = "Post-Delivery Catch", Value = PostDeliveryReal),
+ (print StatisticName = "Failed ZAP / Miss or Uncaught", Value = UncaughtReal),
+ (print StatisticName = "Phish / Malware Efficacy", Value = Effectiveness)
| project StatisticName, Value
```