Exchange Online reporting

How to find unused mailboxes in Exchange Online

List mailboxes with no recent activity with Microsoft Graph PowerShell, export Microsoft's Mailbox usage report from the admin centre, or run Altivane's Inactive Mailboxes report for a rated list.

By the Published Updated

To find unused mailboxes in Exchange Online, read the mailbox usage report and keep the mailboxes whose last activity date is older than your limit, such as 90 days. The report counts email and meeting activity only, and names mailboxes only when the tenant shows user names. Microsoft's inactive mailbox is a different thing: a deleted user's mailbox kept by a hold.

Choose the method that fits the job
MethodBest fit
PowerShellA CSV of mailboxes with no activity for a limit you choose.
Admin centreMicrosoft's Mailbox usage report exported to CSV, then sorted by last activity date.
AltivaneRecommended for regular clean-ups, with each mailbox rated and its licence and forwarding shown.

Before you start

To read the report mailbox by mailbox, use a role that shows user details, such as Reports Reader or Exchange Administrator; Global Reader sees tenant totals only. The PowerShell example requests Reports.Read.All, which may need administrator consent.

Here, unused means a mailbox whose account still exists but has not been used for a long time. Microsoft's inactive mailbox, a deleted user's mailbox kept by a hold, is not covered.

The report usually covers up to the last 24 to 48 hours. Its last activity date is the last time someone sent or read email or worked with meetings, so mail arriving does not set it. The examples read and export and change nothing.

Option 1

Find unused mailboxes with PowerShell

Install the modules once. The script saves the report for the last 180 days to a temporary file, keeps mailboxes with no activity for 90 days or more, and exports them. Change $limitDays for another limit.

PowerShell
# Install once, if the modules are not already installed.
Install-Module Microsoft.Graph.Authentication, Microsoft.Graph.Reports -Scope CurrentUser
PowerShell
$ErrorActionPreference = 'Stop'
$limitDays = 90
$invariant = [System.Globalization.CultureInfo]::InvariantCulture
$utc = [System.Globalization.DateTimeStyles]::AssumeUniversal -bor
    [System.Globalization.DateTimeStyles]::AdjustToUniversal

# The report gives whole days in UTC. Blank or unreadable text gives $null.
function ConvertTo-UtcDay([string]$Text) {
    $day = [datetime]::MinValue
    if ([datetime]::TryParse($Text, $invariant, $utc, [ref]$day)) { return $day }
    return $null
}

Connect-MgGraph -Scopes 'Reports.Read.All' -NoWelcome
$file = New-TemporaryFile
try {
    Get-MgReportMailboxUsageDetail -Period D180 -OutFile $file.FullName
    $rows = @(Import-Csv -Path $file.FullName)
    if ($rows.Count -eq 0) { throw 'The usage report listed no mailboxes.' }
    if (-not ($rows | Where-Object { $_.'User Principal Name' -like '*@*' })) {
        throw 'The report conceals user names, so the mailboxes cannot be named.'
    }
    $now = (Get-Date).ToUniversalTime()
    $report = @(foreach ($row in $rows) {
        if ($row.'Is Deleted' -eq 'True') { continue }
        $last = ConvertTo-UtcDay $row.'Last Activity Date'
        $created = ConvertTo-UtcDay $row.'Created Date'
        $idle = if ($last) { [int][math]::Floor(($now - $last).TotalDays) } else { $null }
        if ($null -ne $idle -and $idle -lt $limitDays) { continue }
        $isNew = $created -and ($now - $created).TotalDays -lt $limitDays
        if ($null -eq $idle -and $isNew) { continue }
        [pscustomobject]@{
            DisplayName = $row.'Display Name'
            UserPrincipalName = $row.'User Principal Name'
            LastActivityDate = if ($last) { $last.ToString('yyyy-MM-dd') } else { '' }
            DaysInactive = $idle
            Created = if ($created) { $created.ToString('yyyy-MM-dd') } else { '' }
            ItemCount = $row.'Item Count'
            SizeGB = [math]::Round([double]$row.'Storage Used (Byte)' / 1GB, 2)
        }
    })
    if ($report.Count -eq 0) {
        Write-Output "No mailbox has been unused for $limitDays days or more."
    } else {
        $byIdle = { if ($null -eq $_.DaysInactive) { [int]::MaxValue } else { $_.DaysInactive } }
        $report | Sort-Object -Property $byIdle -Descending |
            Export-Csv -Path './unused-mailboxes.csv' -NoTypeInformation -Encoding utf8
        $asOf = $rows[0].'Report Refresh Date'
        Write-Output "$($report.Count) mailboxes exported. Report data as of $asOf."
    }
} finally {
    Remove-Item -Path $file.FullName -ErrorAction SilentlyContinue
    Disconnect-MgGraph | Out-Null
}

The CSV lists each mailbox past the limit, longest idle first. A mailbox with no date comes first unless it is newer than the limit. The report mixes user and shared mailboxes, and a mailbox it does not list cannot appear.

Check a single mailbox

After Connect-ExchangeOnline, read Exchange's own last logon time for 1 mailbox.

PowerShell
# Run while connected to Exchange Online. Replace the example mailbox.
Get-EXOMailboxStatistics -Identity 'person@example.com' -Properties LastLogonTime |
    Select-Object DisplayName,LastLogonTime

LastLogonTime records logons, not email activity, so it can differ from the report. Do not use LastUserActionTime, which Microsoft is deprecating.

Option 2

Export Microsoft's Mailbox usage report

  1. Open the Microsoft 365 admin centre for the correct tenant.
  2. Go to Reports > Usage, and under Reports select Exchange.
  3. On the Email activity tab, select View More, then choose Exchange > Mailbox usage from the list.
  4. Choose Export to download the usage of every mailbox as a CSV file.
  5. In Excel, sort by last activity date, oldest first, and use the recipient type and deleted columns to filter.

This suits a one-off look at a small tenant. It does not rate mailboxes against a limit.

Common questions

Is an unused mailbox the same as an inactive mailbox?

No. Microsoft's inactive mailbox is a soft-deleted mailbox kept by a hold after its user's account was deleted; an unused mailbox belongs to a live account. Altivane lists the unused kind in its Inactive Mailboxes report and mailboxes kept by a hold in its Deleted Mailboxes report.

Why does the script stop with a message about user names?

Usage reports hide user names by default. A Global Administrator can show them in the admin centre: go to Settings > Org settings > Services > Reports, clear Conceal user, group, and site names in all reports and select Save. The setting applies to every usage report in the tenant, and showing names is recorded in the Microsoft Purview audit log. Tick the box again to hide them.

Does no mailbox activity mean the person has left?

No. The date counts email and meeting activity, so someone who works only in Teams or SharePoint can look unused. A shared mailbox has no activity independent of a user mailbox, so ask its users before removing it. For sign-ins, see how to find inactive users in Microsoft Entra ID.

Microsoft references

Technical references checked on 5 October 2026. The example scripts have been reviewed and syntax checked; they have not been run against a live tenant.

Talk to us about Altivane

Questions, or pricing? Get in touch.

Contact us