Microsoft 365 Email & Collaboration

Tuning Exchange Online Protection: Anti-Spam, Connection Filtering, and Quarantine Policies

Exchange Online Protection is on by default in every tenant, which is exactly why most orgs never tune it. The defaults are junk-folder-heavy and admin-only on quarantine — fine for 50 seats, quietly wrong for an enterprise where bulk mail floods inboxes, false positives vanish into admin-only quarantine, and one IP allow entry silently disables your own DMARC. This guide engineers the EOP inbound stack deliberately: connection filtering, the anti-spam policy with correct SCL/BCL handling, ASF, and quarantine policies that let users self-serve without lowering protection.

Everything here drives from the Exchange Online PowerShell V3 module (ExchangeOnlineManagement). The GUI lives in the Defender portal under Email & collaboration > Policies & rules > Threat policies, but anti-spam config is security configuration — script it so it is reviewable and identical across tenants.

Install-Module ExchangeOnlineManagement -Scope CurrentUser
Connect-ExchangeOnline -UserPrincipalName admin@contoso.com

1. The EOP inbound pipeline: filtering then verdict actions

Internalize the order before changing anything. EOP is not one filter; it is a sequence of stages, and every setting plugs into a specific one. A message arriving at *.mail.protection.outlook.com traverses:

  1. Connection filtering — the HostedConnectionFilterPolicy (one policy, named Default). The source IP is checked against your IP Allow List and IP Block List. An allow-list hit stamps SCL -1 (bypass spam filtering); a block-list hit drops the connection.
  2. Anti-malware — payload scanning, independent of spam scoring.
  3. Anti-spam (content filtering) — the HostedContentFilterPolicy assigns a Spam Confidence Level (SCL) and a Bulk Complaint Level (BCL), evaluates Advanced Spam Filter (ASF) rules, and reaches one of several verdicts: spam, high-confidence spam, phishing, high-confidence phishing, or bulk.
  4. Verdict action — each verdict maps to an action (move to Junk, quarantine, redirect, delete) and, when quarantined, to a quarantine policy that decides what the end user may do.
Stage Object What it decides
Connection filter HostedConnectionFilterPolicy (Default) Allow/block by source IP; SCL -1 bypass
Anti-spam HostedContentFilterPolicy + Rule SCL/BCL, ASF, per-verdict action
Quarantine handling QuarantinePolicy End-user view/release/request-release rights
Outbound HostedOutboundSpamFilterPolicy + Rule Send limits, auto-forward control, restricted-sender trigger

The single most important fact: an SCL of -1 means “skip filtering entirely.” Connection-filter allow entries, and a transport rule that sets SCL -1, both do this. They are not “trust a bit more” — they are “turn anti-spam off for this path.” Treat every SCL -1 source as a deliberate, audited exception.

2. Connection filtering: IP allow/block and the safe-list trap

There is exactly one connection filter policy per tenant — Default. You cannot create more, and it has no recipient scope; it applies to all inbound mail.

# Inspect the live policy first
Get-HostedConnectionFilterPolicy -Identity Default |
  Format-List IPAllowList, IPBlockList, EnableSafeList

# Block a noisy /24; allow a specific application relay that must never be filtered
Set-HostedConnectionFilterPolicy -Identity Default `
  -IPBlockList @{ Add = '198.51.100.0/24' } `
  -IPAllowList @{ Add = '203.0.113.25' }

Three correctness points that bite people:

# Confirm the safe-list trap is closed
Set-HostedConnectionFilterPolicy -Identity Default -EnableSafeList $false

Do not use the allow list as a shortcut for “stop quarantining this vendor.” It is a blunt, tenant-wide, filtering-off switch. For a single false positive, use a scoped Tenant Allow/Block List entry or fix the sender’s authentication.

3. Anti-spam policy: SCL/BCL, bulk thresholds, and verdict actions

The core. The HostedContentFilterPolicy holds the settings; a paired HostedContentFilterRule binds it to recipients with a priority (lower wins; the built-in Default policy always applies last to anyone unmatched). Build a custom policy rather than editing Default, so you can scope and version it.

New-HostedContentFilterPolicy -Name "AS-Standard" `
  -SpamAction Quarantine `
  -HighConfidenceSpamAction Quarantine `
  -PhishSpamAction Quarantine `
  -HighConfidencePhishAction Quarantine `
  -BulkSpamAction MoveToJmf `
  -BulkThreshold 6 `
  -MarkAsSpamBulkMail On `
  -QuarantineRetentionPeriod 30 `
  -SpamQuarantineTag "AS-SpamSelfRelease" `
  -HighConfidenceSpamQuarantineTag "AdminOnlyAccessPolicy" `
  -PhishQuarantineTag "AdminOnlyAccessPolicy" `
  -HighConfidencePhishQuarantineTag "AdminOnlyAccessPolicy" `
  -BulkQuarantineTag "DefaultFullAccessWithNotificationPolicy" `
  -InlineSafetyTipsEnabled $true `
  -SpamZapEnabled $true `
  -PhishZapEnabled $true

New-HostedContentFilterRule -Name "AS-Standard" `
  -HostedContentFilterPolicy "AS-Standard" `
  -RecipientDomainIs "contoso.com" `
  -Priority 0

How the two scores work:

Verdict Valid actions
Spam MoveToJmf, AddXHeader, ModifySubject, Redirect, Delete, Quarantine
High-confidence spam MoveToJmf, AddXHeader, ModifySubject, Redirect, Delete, Quarantine
Phishing MoveToJmf, Redirect, Quarantine, Delete
High-confidence phishing Quarantine (forced — cannot be downgraded)
Bulk MoveToJmf, AddXHeader, ModifySubject, Redirect, Delete, Quarantine

The tiering shown above quarantines the dangerous verdicts but sends bulk to a notification-enabled quarantine policy so users self-serve the newsletter they wanted — removing most “where did my email go” tickets without weakening protection.

QuarantineRetentionPeriod maxes at 30 days and is set on the content filter policy, not the quarantine policy. After it expires, items are purged and unrecoverable — keep it at 30; shorter retention has burned teams whose users return from leave to an empty quarantine.

4. Advanced Spam Filter (ASF) settings

ASF is a set of granular content tests — HTML frames, embedded tags, numeric IPs in URLs, SPF hard-fail, and similar. Each rule has three states: Off, On (apply the action — increase SCL or mark as spam), and Test (take a test action only, to measure impact without affecting delivery). Most are off by default because several are high false-positive.

# Turn select ASF rules to Test mode first to measure impact
Set-HostedContentFilterPolicy -Identity "AS-Standard" `
  -MarkAsSpamSpfRecordHardFail Test `
  -MarkAsSpamFromAddressAuthFail Test `
  -MarkAsSpamNdrBackscatter Test `
  -IncreaseScoreWithNumericIps Test `
  -IncreaseScoreWithRedirectToOtherPort Test

Guidance from production:

# After a measurement window, promote only what proved clean
Set-HostedContentFilterPolicy -Identity "AS-Standard" `
  -IncreaseScoreWithNumericIps On `
  -IncreaseScoreWithRedirectToOtherPort On

5. Spoof and high-confidence phishing handling

A subtle boundary: in modern EOP, spoof intelligence and impersonation protection live in the anti-phishing policy (Set-AntiPhishPolicy), not the content filter. The content filter owns the phishing verdict actions (where phish-classified mail goes); the anti-phish policy owns spoof detection and unauthenticated-sender handling. Both are EOP — just different objects, and that split confuses people who expect one “spam” policy.

In practice, set PhishSpamAction/HighConfidencePhishAction in the content filter (section 3) to control where caught phish lands — quarantine with an admin-only tag, almost always — and set spoof handling in the anti-phish policy:

Set-AntiPhishPolicy -Identity "Office365 AntiPhish Default" `
  -EnableSpoofIntelligence $true `
  -AuthenticationFailAction Quarantine `
  -EnableUnauthenticatedSender $true `
  -EnableViaTag $true `
  -HighConfidencePhishAction Quarantine

The most common audit finding here is a high-confidence-phish verdict mapped to a quarantine policy that allows end-user release — letting a user click “release” on the message the engine is most confident is an attack. Always bind high-confidence phish and malware to admin-only release.

6. Designing quarantine policies: permissions and end-user release

Quarantine policies are the lever that most reduces help-desk load without lowering protection — and the one almost nobody configures. A quarantine policy controls, per verdict, what an end user can do with a quarantined message: nothing, request release, or release directly, plus preview, allow/block-sender, and delete. Three built-in policies cover the extremes:

Built-in policy End-user experience
AdminOnlyAccessPolicy No access; admin release only (malware, high-confidence phish)
DefaultFullAccessPolicy View, preview, release directly, allow/block sender
DefaultFullAccessWithNotificationPolicy Full access plus quarantine notification emails

The middle ground most enterprises actually want — let users request release but require an admin to approve — needs a custom policy. Build the permission set explicitly with New-QuarantinePermissions, then attach it:

# Request-to-release: user can view/preview and REQUEST, but not release
$perm = New-QuarantinePermissions `
  -PermissionToViewHeader $true `
  -PermissionToPreview $true `
  -PermissionToAllowSender $false `
  -PermissionToBlockSender $true `
  -PermissionToDelete $true `
  -PermissionToRequestRelease $true `
  -PermissionToRelease $false

New-QuarantinePolicy -Name "AS-SpamSelfRelease" `
  -EndUserQuarantinePermissions $perm `
  -ESNEnabled $true

The portal shortcuts map to fixed decimal bitmask values worth memorizing:

PermissionToRequestRelease and PermissionToRelease are mutually exclusive in any sane design — request-to-release means the user asks and an admin approves; full release means the user self-serves. Reserve full release for the lowest-risk verdicts (bulk, ordinary spam), request-to-release for medium risk, and admin-only for malware and high-confidence phish.

7. Quarantine notifications, retention, and per-policy assignment

End-user spam notifications (ESN) are the digest emails telling users “you have N quarantined messages.” In modern EOP this is per quarantine policy via -ESNEnabled $true (the old EnableEndUserSpamNotifications on the content filter is deprecated). The frequency and branding are global:

# Global quarantine notification settings (frequency, branding, custom sender)
Set-QuarantinePolicy -QuarantinePolicyType GlobalQuarantinePolicy `
  -EndUserSpamNotificationFrequency 04:00:00 `
  -EndUserSpamNotificationCustomFromAddress "quarantine@contoso.com" `
  -OrganizationBrandingEnabled $true

EndUserSpamNotificationFrequency accepts 04:00:00 (every 4 hours), 1.00:00:00 (daily), or 7.00:00:00 (weekly). Every-4-hours balances finding legitimate mail before purge against notification fatigue.

Per-policy assignment ties it together: the quarantine policy is referenced by name in the content filter’s *QuarantineTag parameters (section 3). Mapping verdicts to tags is the actual design decision:

Verdict Recommended quarantine policy
Bulk DefaultFullAccessWithNotificationPolicy (or self-release)
Spam custom self-release / request-to-release
High-confidence spam AdminOnlyAccessPolicy
Phishing AdminOnlyAccessPolicy
High-confidence phishing AdminOnlyAccessPolicy (forced quarantine)
# Re-assert the verdict-to-policy mapping after building custom policies
Set-HostedContentFilterPolicy -Identity "AS-Standard" `
  -SpamQuarantineTag "AS-SpamSelfRelease" `
  -BulkQuarantineTag "DefaultFullAccessWithNotificationPolicy" `
  -HighConfidenceSpamQuarantineTag "AdminOnlyAccessPolicy" `
  -PhishQuarantineTag "AdminOnlyAccessPolicy" `
  -HighConfidencePhishQuarantineTag "AdminOnlyAccessPolicy"

8. Outbound spam policy and restricted-sender remediation

EOP also protects your outbound reputation. A compromised mailbox blasting spam gets your tenant’s shared IPs blocklisted; the HostedOutboundSpamFilterPolicy is the circuit breaker. Set send limits and, critically, control auto-forwarding.

New-HostedOutboundSpamFilterPolicy -Name "OS-Standard" `
  -RecipientLimitExternalPerHour 400 `
  -RecipientLimitInternalPerHour 800 `
  -RecipientLimitPerDay 800 `
  -ActionWhenThresholdReached BlockUserForToday `
  -AutoForwardingMode Off `
  -BccSuspiciousOutboundMail $true `
  -BccSuspiciousOutboundAdditionalRecipients "soc-bcc@contoso.com" `
  -NotifyOutboundSpam $true `
  -NotifyOutboundSpamRecipients "soc-alerts@contoso.com"

New-HostedOutboundSpamFilterRule -Name "OS-Standard" `
  -HostedOutboundSpamFilterPolicy "OS-Standard" `
  -SenderDomains "contoso.com" `
  -Priority 0

Two decisions that matter:

A user who trips outbound limits is added to the Restricted entities list and cannot send until remediated. After you reset the credential and clear the cause:

# Find restricted senders, then unblock after remediation
Get-BlockedSenderAddress
Remove-BlockedSenderAddress -SenderAddress "compromised.user@contoso.com"

Removing a restricted sender before fixing the root cause (reset password, revoked sessions, killed the malicious forward/inbox rule) just lets them resume spamming and get re-blocked, with your reputation more damaged. Remediate first, unblock second.

Enterprise scenario

A retail group (~40k mailboxes) onboarded a marketing-automation vendor sending transactional receipts and promotional newsletters from a shared ESP. Within a day the help desk was buried: receipts were landing in quarantine, and because the org had never touched quarantine policies, every spam-verdict message used the default admin-only experience — users could see nothing and just filed tickets. The marketing team’s instinct was to add the ESP’s IPs to the connection-filter IP Allow List, which would have set SCL -1 and disabled spoof/DMARC checks for a shared relay other tenants also use. A textbook way to inherit someone else’s spam.

The constraint: receipts had to reach inboxes today, promotional bulk could tolerate quarantine-with-self-release, and security would not accept a tenant-wide filtering bypass on a shared ESP IP.

The fix was a verdict-aware split. Receipts passed DMARC once the vendor aligned them, so they stopped being quarantined. The promotional stream scored as bulk (BCL above threshold) and was routed to a self-release quarantine policy — users got a 4-hour digest and released the newsletter themselves, no ticket:

# Self-release for bulk only; receipts handled by DMARC alignment, not an allow entry
$bulkPerm = New-QuarantinePermissions `
  -PermissionToViewHeader $true -PermissionToPreview $true `
  -PermissionToRelease $true -PermissionToRequestRelease $false `
  -PermissionToBlockSender $true -PermissionToDelete $true

New-QuarantinePolicy -Name "Marketing-BulkSelfRelease" `
  -EndUserQuarantinePermissions $bulkPerm -ESNEnabled $true

Set-HostedContentFilterPolicy -Identity "AS-Standard" `
  -BulkThreshold 6 -MarkAsSpamBulkMail On `
  -BulkSpamAction Quarantine `
  -BulkQuarantineTag "Marketing-BulkSelfRelease"

Within the notification window ticket volume collapsed, security kept full filtering and DMARC on the ESP, and the durable fix — vendor DMARC alignment plus a Tenant Allow/Block List entry for the one stubborn newsletter domain — replaced any temptation to allow-list a shared IP. The lesson: quarantine policy design, not the connection-filter allow list, is the correct lever for “legitimate mail is being held.”

Verify

Confirm the stack is live and correctly scoped before declaring victory.

# 1. Connection filter: lists explicit, safe list OFF
Get-HostedConnectionFilterPolicy -Identity Default |
  Format-List IPAllowList, IPBlockList, EnableSafeList

# 2. Anti-spam policy: verdict actions, bulk threshold, quarantine tags
Get-HostedContentFilterPolicy -Identity "AS-Standard" |
  Format-List SpamAction, HighConfidenceSpamAction, PhishSpamAction, `
    HighConfidencePhishAction, BulkSpamAction, BulkThreshold, `
    MarkAsSpamBulkMail, QuarantineRetentionPeriod, *QuarantineTag

# 3. Rule binding and priority (Default is always last)
Get-HostedContentFilterRule | Sort-Object Priority |
  Format-Table Name, Priority, State, RecipientDomainIs

# 4. Quarantine policies and the permission bitmask in effect
Get-QuarantinePolicy | Format-Table Name, ESNEnabled, EndUserQuarantinePermissionsValue

# 5. Outbound limits and auto-forward control
Get-HostedOutboundSpamFilterPolicy | Format-List Name, AutoForwardingMode, `
  RecipientLimitExternalPerHour, ActionWhenThresholdReached

# 6. Anyone currently restricted from sending
Get-BlockedSenderAddress

Read the message header of a delivered or quarantined sample — headers are the ground truth for what EOP decided:

Trace and inspect quarantine from PowerShell:

# Recent inbound mail with status, last ~2 hours
Get-MessageTraceV2 -RecipientAddress "user@contoso.com" `
  -StartDate (Get-Date).AddHours(-2) -EndDate (Get-Date) |
  Select-Object Received, SenderAddress, Subject, Status

# List quarantined items and their applied policy / verdict
Get-QuarantineMessage -StartReceivedDate (Get-Date).AddDays(-1) `
  -EndReceivedDate (Get-Date) |
  Format-Table ReceivedTime, SenderAddress, Type, QuarantineTypes, PolicyName

In the quarantine portal (Defender portal > Review > Quarantine), confirm a low-risk test message (a bulk-classified newsletter) shows the self-release option for a normal user, and a high-confidence-phish test shows no release — that proves your verdict-to-policy mapping is wired correctly.

Checklist

Pitfalls

The failure modes are predictable and mostly self-inflicted. The connection-filter allow list as a convenience switch is the worst: SCL -1, tenant-wide, and it disables your own spoof/DMARC checks for that source — use the Tenant Allow/Block List or fix sender authentication instead. EnableSafeList $true sounds protective and is the opposite; leave it off. Admin-only quarantine on everything (the unconfigured default) generates tickets for every benign newsletter — tier your verdicts. Promoting ASF straight to On manufactures false positives; always pass through Test. Downgrading high-confidence phish is impossible, and giving users release on it is an audit finding. Unblocking a restricted sender before remediating just resumes the spam and erodes your shared-IP reputation further.

Treat the EOP stack as one system: connection filtering decides who is even scored, the anti-spam policy decides the verdict, and the quarantine policy decides who controls the outcome. Source-control the PowerShell above, diff it on every change, and let DMARC alignment and the Tenant Allow/Block List carry your exceptions.

Next steps

Pair this with anti-phishing impersonation tuning and the Tenant Allow/Block List workflow in the Defender for Office 365 companion guide, wire DMARC to p=reject once accurate authentication results are confirmed, and route end-user “Report message” submissions to a SOC mailbox so quarantine tuning is driven by real reports, not guesswork.

Exchange OnlineEOPAnti-SpamConnection FilteringQuarantine

Comments

Keep Reading