From the user's own "Recover deleted items" button to PowerShell bulk restores, and how to find out who deleted what, and when.
"My old emails are gone." Every Microsoft 365 admin hears this sooner or later. The good news is that in Exchange Online, a deleted email is rarely gone for good. Before it is permanently purged, it passes through several safety nets, and with the right tools you can bring it back. You can also prove how it disappeared.
This guide walks through the full process:
- Understand where deleted emails actually go
- Check whether the mail is really deleted (or just hidden or moved)
- Find out who deleted it, when, and from which app
- Restore it: self-service, then PowerShell for bulk and targeted restores
- Prevent it from happening again
1. The lifecycle of a deleted email
Understanding this flow is what turns "it's gone" into "it's recoverable".
| Stage | Who can see it | How long it stays | Audit operation |
|---|---|---|---|
| Deleted Items | The user | Until emptied, or removed by a retention tag | MoveToDeletedItems |
| Recoverable Items\Deletions | The user (via "Recover deleted items") and admins | 14 days by default, configurable up to 30 (RetainDeletedItemsFor) | SoftDelete |
| Recoverable Items\Purges | Admins only | Removed at the end of the retention period, unless the mailbox is on hold | HardDelete |
2. Confirm the mail is really deleted
A lot of "missing email" reports are not deletions at all. Rule these out first. It takes five minutes.
| Check | Why it matters |
|---|---|
| Open Outlook on the web (outlook.office.com) and look for the mail | If it's visible on the web, the problem is the desktop client (cache, filters, collapsed date groups), not the server |
| Clear filters (Unread, Flagged), switch Focused/Other, and expand collapsed "Older" groups | Very common cause of "only recent mail is visible" |
| Search all folders for a known old message | Inbox rules or Sweep may have moved it, not deleted it |
| Check the Online Archive (In-Place Archive) | A "Move to Archive" retention tag silently moves older mail there |
Server-side check with PowerShell
The quickest way to see where items actually are is to compare folder item counts and oldest-item dates:
Connect-ExchangeOnline -UserPrincipalName admin@contoso.com
$upn = "user@contoso.com"
# Item count and oldest item per folder (includes Recoverable Items)
Get-MailboxFolderStatistics $upn -IncludeOldestAndNewestItems |
Where-Object { $_.ItemsInFolder -gt 0 } |
Select-Object FolderPath, ItemsInFolder, OldestItemReceivedDate |
Sort-Object ItemsInFolder -Descending | Format-Table -AutoSize
# Archive, retention policy and holds
Get-Mailbox $upn | Select-Object ArchiveStatus, RetentionPolicy, LitigationHoldEnabled, InPlaceHolds, RetainDeletedItemsFor
/Deletions holds a lot of items going back years, the mail was deleted and then Deleted Items was emptied. It is recoverable.Run the same command again the next day. If the Inbox's oldest date moves forward, something is deleting mail on a rolling schedule (Sweep, a client rule, a retention policy). If it stays fixed, it was a one-time bulk delete.
3. Find out who deleted the mail, and when
Before you restore anything, find the cause. If a phone app or a client-side rule is deleting mail, it will delete the restored mail again.
Option A: Microsoft Purview audit search (portal)
- Go to purview.microsoft.com → Audit → Search.
- Set the date range (Audit Standard keeps 180 days by default).
- In Activities – operation names, enter:
MoveToDeletedItems,SoftDelete,HardDelete - Add the affected user and click Search.
- Open any record and check
ClientInfoString,LogonType,Folder.PathandAffectedItems.
Option B: PowerShell summary (per day, folder and app)
The portal shows one event at a time. For a large investigation, this script summarizes the last 180 days by date, folder, client and logon type, keeping only the summary in memory:
$upn = "user@contoso.com"
$summary = @{}
foreach ($day in 0..179) {
$start = (Get-Date).Date.AddDays(-180 + $day)
$end = $start.AddDays(1)
$sid = [guid]::NewGuid().ToString()
do {
$page = Search-UnifiedAuditLog -StartDate $start -EndDate $end -UserIds $upn `
-Operations MoveToDeletedItems,SoftDelete,HardDelete `
-SessionId $sid -SessionCommand ReturnLargeSet -ResultSize 5000
foreach ($r in $page) {
$d = $r.AuditData | ConvertFrom-Json
$client = if ($d.ClientInfoString -match "MacOutlook") { "Outlook-Mac" } elseif ($d.ClientInfoString -match "OWA") { "OWA" } elseif ($d.ClientInfoString -match "iOS|Android") { "Mobile" } else { "Other" }
$key = "{0}|{1}|{2}|{3}|{4}" -f $start.ToString("yyyy-MM-dd"), $r.Operations, $d.Folder.Path, $client, $d.LogonType
if ($summary.ContainsKey($key)) {
$summary[$key] += @($d.AffectedItems).Count
} else {
$summary[$key] = @($d.AffectedItems).Count
}
}
} while ($page -and $page.Count -eq 5000)
Write-Host "$($start.ToString('yyyy-MM-dd')) done"
}
$report = $summary.GetEnumerator() | ForEach-Object {
$p = $_.Key -split "\|"
[pscustomobject]@{ Date = $p[0]; Operation = $p[1]; Folder = $p[2]; Client = $p[3]; LogonType = $p[4]; Items = $_.Value }
}
# Biggest delete actions first
$report | Sort-Object Items -Descending | Select-Object -First 25 | Format-Table -AutoSize
| Field | What it tells you |
|---|---|
LogonType | 0 = mailbox owner, 1 = admin, 2 = delegate |
Client | The app that performed the delete: desktop Outlook, OWA, mobile, or an app using REST/Graph |
Folder | The folder the items were deleted from. Filter on \Inbox to separate real mail from alert-folder cleanup. |
| Spike days | A single day with a huge Items value means a bulk delete. Steady daily counts mean a habit or an automated rule. |
Move operation) are not audited by default. If the audit log shows only a handful of Inbox deletions but the Inbox is empty, the mail may have been moved to another folder first and deleted from there.4. Restore the emails
Option 1: Self-service in Outlook on the web (small volumes)
- Open outlook.office.com and select Deleted Items.
- Click Recover items deleted from this folder at the top of the list.
- Search or sort by date, select the messages, and click Restore.
This works well for hundreds or a few thousand items. Beyond that, use PowerShell.
Option 2: PowerShell bulk restore (targeted, admin)
Step 1: Grant the role. Get-RecoverableItems and Restore-RecoverableItems are only available with the Mailbox Import Export role, which is not assigned to anyone by default. In the Exchange admin center, go to Roles → Admin roles → Organization Management → Permissions, tick Mailbox Import Export, and save. Or run:
New-ManagementRoleAssignment -Name "MIE-Admin" -Role "Mailbox Import Export" -User "admin@contoso.com"
Get-Command Get-RecoverableItems, Restore-RecoverableItems.Step 2: Restore only what you need. A mailbox's Recoverable Items folder can hold a huge number of items, mostly notification noise. Restoring everything would flood the user's mailbox. Instead, filter by:
-LastParentFolderID: the folder the item was deleted from (for example, Inbox only)-FilterStartTime/-FilterEndTime: these match the item's LastModifiedTime, which is roughly when it was deleted-FilterItemType IPM.Note: email messages only
Step 3: Get the Inbox ID in the right format. This is the part most guides skip. Get-MailboxFolderStatistics returns a base64 FolderId, but -LastParentFolderID expects a 44-character hex string (the store GUID plus the folder counter). You can convert one to the other:
$upn = "user@contoso.com"
$inboxFid = (Get-MailboxFolderStatistics $upn -FolderScope Inbox | Select-Object -First 1).FolderId
$bytes = [Convert]::FromBase64String($inboxFid)
$inboxId = ($bytes[23..44] | ForEach-Object { $_.ToString("X2") }) -join ""
$inboxId # 44 hex characters
Step 4: Preview first, in small date windows. One query across a large Recoverable Items folder often fails with a server-side timeout. Query one folder and one day at a time, with retries:
$start = (Get-Date).Date.AddDays(-60)
$end = Get-Date
$preview = [System.Collections.Generic.List[object]]::new()
foreach ($src in "DeletedItems", "RecoverableItems") {
$s = $start
while ($s -lt $end) {
$e = $s.AddDays(1)
$ok = $false
foreach ($try in 1..3) {
try {
$r = Get-RecoverableItems -Identity $upn -SourceFolder $src -FilterItemType IPM.Note `
-LastParentFolderID $inboxId -FilterStartTime $s -FilterEndTime $e -ResultSize 1000 -ErrorAction Stop
if ($r) { $r | ForEach-Object { $preview.Add($_) } }
$ok = $true
break
} catch {
Start-Sleep -Seconds 20
}
}
if (-not $ok) {
Write-Host "$src $($s.ToString('yyyy-MM-dd')) FAILED after 3 tries" -ForegroundColor Red
}
$s = $e
}
}
$preview.Count
$preview | Group-Object LastParentFolderID | Select-Object Count, Name # should be ONE row = $inboxId
$preview | Select-Object Subject, LastModifiedTime | Export-Csv .\restore-preview.csv -NoTypeInformation
Step 5: Restore, using the same windows. Items go back to their original folder:
foreach ($src in "DeletedItems", "RecoverableItems") {
$s = $start
while ($s -lt $end) {
$e = $s.AddDays(1)
try {
Restore-RecoverableItems -Identity $upn -SourceFolder $src -FilterItemType IPM.Note `
-LastParentFolderID $inboxId -FilterStartTime $s -FilterEndTime $e -ResultSize 1000 -NoOutput -ErrorAction Stop
Write-Host "$src $($s.ToString('yyyy-MM-dd')) restored"
} catch {
Write-Host "$src $($s.ToString('yyyy-MM-dd')) FAILED - $($_.Exception.Message)" -ForegroundColor Red
}
$s = $e
}
}
Step 6: Verify.
Get-MailboxFolderStatistics $upn -FolderScope Inbox -IncludeOldestAndNewestItems |
Select-Object FolderPath, ItemsInFolder, OldestItemReceivedDate -First 1
Install-Module ExchangeOnlineManagement -Force), check $Error[0].Exception | Format-List * -Force for the underlying message, and use smaller date windows. If it still fails, run the same commands in Windows PowerShell 5.1.Option 3: Purview eDiscovery (last resort)
If neither option works, create an eDiscovery search scoped to the mailbox (it includes Recoverable Items), export the results to PST, and import the PST back. It's slower and heavier, but it doesn't depend on the restore cmdlets.
5. Choosing the right method
| Scenario | Best method |
|---|---|
| A few emails, deleted recently | The user restores them in Outlook on the web |
| Thousands of emails, one folder, a known date range | Restore-RecoverableItems with -LastParentFolderID and date windows |
| Restore into a separate folder for review | Restore-RecoverableItems -RestoreTargetFolder "/Recovered" (Exchange Online only) |
| Past the retention period, no hold | Not recoverable from Exchange. Check backups if your organization has a third-party backup. |
| Legal or compliance export needed | Purview eDiscovery search and export |
6. Prevent it from happening again
| Action | Why |
|---|---|
Set RetainDeletedItemsFor to 30 days | Doubles the default safety window: Set-Mailbox user@contoso.com -RetainDeletedItemsFor 30 |
| Review client-side rules and Sweep | Rules created in desktop or mobile Outlook, and Sweep, may not appear in Get-InboxRule |
| Disable "Empty Deleted Items on exit" for heavy deleters | Stops instant emptying of Deleted Items |
| Route alert and notification email to a dedicated folder or shared mailbox | Users won't mass-delete from the folders where their real mail lives |
| Enable an Online Archive with a sensible "Move to Archive" tag | Keeps the primary mailbox lean, so users don't feel the need to bulk delete |
| Use a retention policy for critical mailboxes | Deleted items stay recoverable for as long as the policy requires |
Key takeaways
- Deleted mail goes Deleted Items → Recoverable Items\Deletions → Purges. With a hold, it stays recoverable much longer.
- Always investigate before restoring. The audit log tells you who, when, from which folder, and with which app.
- Restore selectively. Filter by the original folder and date, and preview before you restore.
- Convert the base64
FolderIdto the hexLastParentFolderIDto target one folder precisely. - Query in small date windows to avoid server-side timeouts on large mailboxes.
- Don't touch holds until the restore is complete.
No comments:
Post a Comment