EvolitBlogContact

Exchange Online Protection: How to Diagnose and Fix False Positives in Quarantine

EOP regularly quarantines legitimate business emails — vendor invoices, system alerts, and Gmail messages — due to aggressive filtering rules or missing email authentication. Learn how to read email headers to find the exact cause, bulk-release messages via PowerShell, and build permanent fixes with SPF, DKIM, and DMARC.

Exchange Online Protection: How to Diagnose and Fix False Positives in Quarantine

TL;DR: EOP quarantines legitimate business emails more often than most admins realize — partner invoices, system alerts, bulk notifications, and even plain Gmail messages can end up blocked. This guide walks you through reading email headers to find the exact cause, bulk-releasing quarantined messages via PowerShell, and building lasting fixes so the same sender never hits quarantine again.

The Problem

The EX1227432 incident in February 2026 — when Microsoft deployed an overly aggressive URL detection rule that quarantined legitimate business emails for five straight days across organizations worldwide — was not a one-off. Before that was EX1064599, where Gmail messages were mass-quarantined as "High Confidence Phish" with an SCL score of 8. Throughout 2025, a series of similar incidents caused security breach notifications and vendor invoices to land in quarantine with no warning to admins or end users.

In practice it looks like this: a colleague reports they never received a vendor payment confirmation. You check their inbox — nothing. Check Junk — nothing. Fifteen minutes later you find it sitting in quarantine flagged as high-confidence phishing. The vendor has been sending from the same domain for years. Nothing changed on their end. EOP just decided it was phishing.

The pattern tends to hit hardest in these scenarios:

  • Vendors using third-party sending platforms (Mailchimp, SendGrid, Shopify) with a client domain that has no DKIM configured
  • Automated system notifications from Azure Monitor, CI/CD pipelines, or on-premises monitoring tools
  • Small business senders on shared hosting with overly broad or broken SPF records
  • Marketing-style emails with heavy HTML, many links, or large embedded images (high BCL)
  • During Microsoft-side global incidents when false positive rates spike across all tenants simultaneously

There is a compounding frustration: most end users never know a message was quarantined. Unless quarantine digest notifications are turned on, the silence reads as the sender not responding — not as a filtering problem.

Why It Happens

EOP assigns every inbound message a Spam Confidence Level (SCL) from -1 to 9. Higher means more likely spam or phishing. The thresholds:

  • SCL -1: trusted source, spam filtering bypassed entirely
  • SCL 0-4: not spam, delivered normally
  • SCL 5-6: spam, moved to Junk folder
  • SCL 7-9: high-confidence spam or phishing, sent to quarantine

Beyond SCL, EOP also calculates BCL (Bulk Complaint Level, 0-9 — above 7 quarantines bulk mail) and PCL (Phish Confidence Level). All these scores land in the X-Forefront-Antispam-Report header of every processed message.

Email authentication is the biggest lever. When a sender's domain lacks proper SPF, DKIM, and DMARC records, EOP's composite authentication check fails, producing compauth=fail reason=001 in the Authentication-Results header. Even a completely legitimate sender becomes suspect when their DNS is misconfigured — EOP has no cryptographic proof of sender identity.

The second major cause is Microsoft pushing updated detection rules globally without advance notice. One morning a new rule lands and starts blocking messages that passed through cleanly for months. Your only early warning is a spike in user complaints.

Third are content heuristics: excessive links, large images embedded in HTML, signatures rendered by external email marketing tools, or specific keyword combinations in the subject line can push SCL and BCL into quarantine territory regardless of authentication results.

Step-by-Step Resolution

Step 1: Check Service Health First

Before you touch any configuration, spend 60 seconds at: Microsoft 365 admin center -> Health -> Service health -> Exchange Online. If there's an active incident describing filtering or quarantine problems, you have your answer — wait for Microsoft and manually release specific messages in the meantime. There is no point configuring transport rules in response to a Microsoft-side incident.

Step 2: Find the Message and Read the Headers

Portal path: https://security.microsoft.com/quarantine

PowerShell is much faster for mass incidents:

# Connect to Exchange Online
Connect-ExchangeOnline -UserPrincipalName admin@yourdomain.com

# Find quarantined messages from a specific sender in the last 7 days
Get-QuarantineMessage -SenderAddress "invoices@vendor.com" `
    -StartReceivedDate (Get-Date).AddDays(-7) `
    -EndReceivedDate (Get-Date) | `
    Select-Object ReceivedTime, SenderAddress, Subject, QuarantineTypes, Expires

# See all quarantined messages in the last 24 hours grouped by quarantine reason
Get-QuarantineMessage -StartReceivedDate (Get-Date).AddDays(-1) | `
    Group-Object QuarantineTypes | `
    Select-Object Name, Count

Once you find the message, click View message header in the portal or export it and paste it into Microsoft Message Header Analyzer (mha.azurewebsites.net). Key fields to check:

  • X-Forefront-Antispam-Report — look for SCL=, CAT=, SFV=:
    • CAT:HPHISH = high-confidence phishing (only admins can release — users cannot)
    • CAT:BULK = bulk mail flagged by BCL threshold
    • SFV:SPM = spam verdict from content filters
    • SFV:SKQ = message was previously quarantined and released
    • SFV:SKB = blocked sender or domain triggered the action
  • Authentication-Results — check spf=, dkim=, dmarc=:
    • compauth=fail reason=001 = composite auth failed = sender domain has missing or broken DNS records
    • spf=fail = SPF hard fail (sender IP not authorized)
    • spf=permerror = SPF record exceeds 10 DNS lookup limit
  • X-Microsoft-AntispamBCL= value (0-9; above 7 quarantines bulk mail)

Step 3: Release Quarantined Messages

Single message via portal: security.microsoft.com/quarantine -> select the message -> Release -> check "Submit to Microsoft for analysis" (recommended — this trains the filter).

Bulk release via PowerShell:

# Release all messages from a specific sender to all original recipients
Get-QuarantineMessage -SenderAddress "invoices@vendor.com" `
    -PageSize 1000 | `
    Release-QuarantineMessage -ReleaseToAll

# Release messages for a specific recipient from the last 48 hours
Get-QuarantineMessage `
    -RecipientAddress "john.smith@yourdomain.com" `
    -StartReceivedDate (Get-Date).AddDays(-2) | `
    Release-QuarantineMessage -ReleaseToAll

# Report false positives to Microsoft after releasing
Get-QuarantineMessage -SenderAddress "invoices@vendor.com" `
    -StartReceivedDate (Get-Date).AddDays(-7) | `
    ForEach-Object { Submit-QuarantineMessage -Identity $_.Identity -ReportType NotJunk }

Critical limitation: Messages quarantined as High Confidence Phishing or Malware can only be released by an admin. End users can submit a release request, but an admin must approve it — even a permissive quarantine policy cannot override this. Check the CAT field before promising users they can self-release.

Default quarantine retention periods:

  • Spam: 15 days
  • Phishing, High Confidence Phishing, Malware: 30 days

After expiry the messages are permanently deleted with no recovery option.

Step 4: Add the Sender to Tenant Allow/Block List

A good temporary fix while the sender fixes their authentication:

# Add a specific email address to the allow list
New-TenantAllowBlockListItems `
    -ListType Sender `
    -Entries "invoices@vendor.com" `
    -Allow `
    -ExpirationDate (Get-Date).AddDays(45) `
    -Notes "Trusted vendor - waiting for DKIM/DMARC configuration"

# Add an entire domain (use carefully — broad scope)
New-TenantAllowBlockListItems `
    -ListType Sender `
    -Entries "vendor.com" `
    -Allow `
    -ExpirationDate (Get-Date).AddDays(45)

# View all current allow entries
Get-TenantAllowBlockListItems -ListType Sender -Allow | `
    Select-Object Value, ExpirationDate, Notes | `
    Sort-Object ExpirationDate

TABL entry limits by license tier:

  • No Defender for Office 365: maximum 500 allow entries total
  • Defender for Office 365 Plan 1: maximum 1000 allow entries
  • Defender for Office 365 Plan 2: maximum 5000 allow entries

Step 5: Create a Mail Flow Rule for Long-Term Relief

For trusted partners or internal systems you cannot control:

# Bypass spam filtering for a trusted partner domain
New-TransportRule `
    -Name "EOP Bypass - Trusted Partner XYZ" `
    -SenderDomainIs "trustedpartner.com" `
    -SetSCL -1 `
    -Comments "Trusted invoice system - bypass until SPF/DKIM is fixed"

# Review existing transport rules and their priorities
Get-TransportRule | `
    Select-Object Name, Priority, State, SetSCL | `
    Sort-Object Priority

Step 6: Fix Authentication at the Source

If the problem is your own domain or a client domain you manage, proper SPF + DKIM + DMARC configuration fixes the problem permanently.

SPF (DNS TXT record):

v=spf1 include:spf.protection.outlook.com -all

Watch out for the 10 DNS lookup limit. If you include multiple third-party services (SendGrid, Mailchimp, Zendesk, Salesforce), you can easily exceed this, which produces spf=permerror — treated by EOP as an authentication failure, not a neutral result.

DKIM — enable via PowerShell:

# Generate DKIM keys for your domain
New-DkimSigningConfig -DomainName "yourdomain.com" -Enabled $true

# Get the CNAME records to add in DNS
Get-DkimSigningConfig -Identity "yourdomain.com" | `
    Select-Object Selector1CNAME, Selector2CNAME, Status

Add the two CNAME records to your DNS provider and wait up to 48 hours for propagation before the signing activates.

DMARC (DNS TXT record):

v=DMARC1; p=quarantine; rua=mailto:dmarc@yourdomain.com; pct=100

Start with p=none if you are uncertain about SPF/DKIM completeness — it puts you in monitoring mode without quarantining any mail.

Common Pitfalls and Edge Cases

Pitfall 1: SCL -1 transport rules do not override HPHISH classification.

You create a mail flow rule with SetSCL -1 and assume the problem is solved. Messages still land in quarantine — because CAT:HPHISH classification overrides the SCL value set by the transport rule. Transport rules have no effect on high-confidence phishing or malware filtering. You need to also add the domain to TABL, or modify the anti-phish policy action:

# Check current anti-phish policy settings
Get-AntiPhishPolicy | `
    Select-Object Name, EnableAntiSpoofEnforcement, AuthenticationFailAction

# Change action from Quarantine to MoveToJunk (less restrictive)
Set-AntiPhishPolicy -Identity "Default" -AuthenticationFailAction MoveToJunk

Pitfall 2: TABL allow entries expire after 45 days and nobody watches them.

Allow entries in TABL automatically expire 45 days after the filtering system last evaluates the sender as "clean." You do not control the exact expiry moment — it depends on filter activity. Without monitoring, entries silently expire and messages start hitting quarantine again weeks later with no clear trigger. Build in a weekly check:

# Find TABL allow entries expiring within 7 days
Get-TenantAllowBlockListItems -ListType Sender -Allow | `
    Where-Object { $_.ExpirationDate -lt (Get-Date).AddDays(7) } | `
    Select-Object Value, ExpirationDate, Notes | `
    Sort-Object ExpirationDate

Pitfall 3: Get-MessageTrace only covers the last 10 days.

A user reports a missing email from two weeks ago. Get-MessageTrace returns nothing. The cmdlet's search window is capped at 10 days. For older messages, use Start-HistoricalSearch, which is an asynchronous operation — results are available after roughly 30 minutes in the Message Trace section of the M365 admin center. Set the right expectation with the user before they think you ignored the request.

Pitfall 4: PIM-activated roles cannot manage quarantine.

If your organization uses Azure Privileged Identity Management to activate admin roles on demand, be aware that Microsoft explicitly documents that PIM-activated roles are not supported for quarantine management (MC447339, February 2023). A role activated through PIM will not grant access to quarantine actions. You need a permanently assigned Security Administrator role or a dedicated break-glass service account for quarantine operations.

Pitfall 5: Outbound spam is a completely different problem.

If EOP is blocking outbound messages from one of your users (compromised account sending spam), the account ends up on the Restricted Entities list at security.microsoft.com/restrictedentities. This is an entirely different mechanism from inbound quarantine — none of the above steps apply. The correct procedure: reset the user password immediately, audit mailbox forwarding rules (a classic indicator of account compromise), remove the account from Restricted Entities, and review the Unified Audit Log for unauthorized activity before considering the incident closed.

Summary

  • First check: Service Health — a Microsoft-side incident means you wait and manually release messages, not reconfigure your environment
  • Diagnose the cause: X-Forefront-Antispam-Report header (SCL=, CAT=, SFV=) and Authentication-Results (compauth=fail reason=001 = sender DNS misconfiguration)
  • Immediate fix: Get-QuarantineMessage | Release-QuarantineMessage -ReleaseToAll — then submit the false positive to Microsoft via Submit-QuarantineMessage
  • Short-term protection: Tenant Allow/Block List (watch the 45-day expiry and 500/1000/5000 entry limits by license) or an SCL -1 transport rule (does not work for HPHISH classification)
  • Permanent solution: correct SPF + DKIM + DMARC on the sender's domain eliminates the root cause and improves outbound deliverability for everyone