Key takeaway: To move on-premises Exchange mailbox data into Microsoft 365 without a hybrid mailbox move, export each mailbox to PST, upload the PST files to the Azure Blob storage that Microsoft provides for your tenant using AzCopy, and let the Microsoft Purview Import service load them into mailboxes. You do not need an Azure subscription. The Azure Blob tools in most migration guides (Storage Mover, Data Box, zip-and-unzip pipelines) are for moving files into storage accounts you own, and they do not feed Exchange Online directly.
In This Article
- What "Blob Storage" Means in an Exchange Migration
- When to Use This Method, and When Not To
- What About Compressing the Data First?
- Limits to Know Before You Start
- Step 1: Plan and Inventory the Source
- Step 2: Prepare Microsoft 365
- Step 3: Export Mailboxes from On-Premises Exchange to PST
- Step 4: Validate the PST Files Before You Upload
- Step 5: Get the SAS URL and AzCopy
- Step 6: Upload the PST Files with AzCopy
- Step 7: Create the Mapping File
- Step 8: Create the Import Job
- Step 9: Filter and Start the Import
- Step 10: After the Import
- Mixed Environments: On-Premises and Exchange Online Together
- If You Want Blob Storage You Own: Archive Copies and File Data
- Troubleshooting
- The Complete Checklist
Most advice about "using Azure Blob Storage for a migration" is written for file data. Exchange is different: the destination is a mailbox, not a container. This guide explains when the blob-based PST import is the right tool, then walks through every step from the on-premises Exchange server to a finished mailbox in Microsoft 365, including the checks that are easy to skip. For a related method that applies to Public Folders, see Migrate Exchange Public Folders to a Shared Mailbox.
What "Blob Storage" Means in an Exchange Migration
Two different things get called "Azure Blob storage," and mixing them up causes most of the confusion:
- The Microsoft-provided staging area. When you create an import job in the Microsoft Purview portal, Microsoft gives you a private container (named
ingestiondata) in the same region as your tenant, plus a shared access signature (SAS) URL to upload into it. You do not buy it or manage it, and you do not need an Azure subscription. The Import service reads PST files from this container and writes them into mailboxes. - Storage accounts you own. You can create your own Azure storage account to hold file shares, backups, or archive copies. Azure Storage Mover, Azure Data Box, and AzCopy can move file data into these. The Import service can use one of these as a *source* for the upload, but it cannot write mailbox data directly from it.
This article is about the first. The last section covers the second.
When to Use This Method, and When Not To
| Situation | Best approach |
|---|---|
| Live mailboxes, hybrid configured, users must keep working | Hybrid mailbox moves. They keep mail flow, free/busy, and Outlook profiles intact |
| Live mailboxes, small organization, no hybrid | Cutover or staged migration, or a third-party migration tool |
| Old archives, PSTs scattered on file shares and laptops | PST import (this guide) |
| Decommissioning an Exchange server and keeping the history | PST import |
| Former employees whose data must be kept | PST import into an inactive mailbox |
| A user's cloud archive when their primary mailbox is still on-premises | PST import into the cloud archive |
| Public Folder content | Not supported by the Import service. Move it to a mailbox first (see the Public Folders article linked above) |
| Calendars and contacts that must stay synchronized live | A mailbox move, not PST |
The PST method copies a point-in-time snapshot. Mail that arrives after the export is not included, so for live mailboxes you would need a second export and import of the gap, or you would use a mailbox move instead. Treat PST import as the right tool for history, not for a live cutover.
What About Compressing the Data First?
A common idea is to zip the data locally, upload the smaller files to Azure, and unzip them in the cloud to get around a slow or throttled link. For Exchange data, that does not work the way people hope:
- The Import service only reads PST files. It does not unzip anything. Zipped files placed in the Microsoft-provided container are not extracted.
- You cannot run code against the Microsoft-provided container. It is not in your subscription, so an Azure Function or Data Factory pipeline cannot be attached to it.
- It is technically possible to chain it. You could upload zips to your own storage account, unzip them there with an Azure Function or Data Factory, and then copy the extracted PSTs server-to-server into the Microsoft container (AzCopy accepts an Azure Storage location as a source). That works, but it adds a storage account, compute, a second copy of sensitive mail data, and function time limits, and the saving is often small.
- PSTs usually compress modestly. Most of the bulk in mailboxes is attachments that are already compressed, such as Office documents, PDFs, and images. Measure on a sample before building anything.
Better ways to get more out of a slow link are described in Step 6: trim what you export, schedule uploads off-hours, cap the bandwidth so you do not starve the business, and let AzCopy resume interrupted transfers. Use the version of AzCopy that the Purview Import page links to. Microsoft does not support other versions for this process.
Limits to Know Before You Start
These come from Microsoft's current documentation for the Import service. Check the live page before you plan around them.
| Limit | Value |
|---|---|
| PST file size | Keep each file at or below 20 GB. Larger files hurt import performance |
| PST file names | Must be unique across the import job, and are case-sensitive in the mapping file |
| Rows per mapping file | 500. For more PSTs, create more mapping files and more jobs |
| Import speed | About 24 GB per day per mailbox, not guaranteed. Different mailboxes import in parallel. PSTs for the same mailbox import one at a time |
| Archive imports | No more than 100 GB into a user's archive in a single import. Split larger imports and allow the archive to auto-expand between them |
| Item size | Items over 150 MB are skipped, because that is the Exchange Online message limit |
| Folder depth | PSTs with 300 or more nested folder levels cannot be imported |
| Formats | Unicode PST is preferred. ANSI is supported (with a code page option for double-byte languages). Outlook 2007 and later |
| Public folders and groups | Cannot be targets |
| Staging area cleanup | Uploaded PSTs are removed automatically 30 days after the most recent import job was created. You cannot delete them yourself |
| Retention hold | Turned on for the mailbox after import, indefinitely, until you turn it off |
| Cost and availability | Network upload is free and available to all commercial licensing agreements. Drive shipping has costs and is limited to certain agreements and regions |
Step 1: Plan and Inventory the Source
Capture the size and item count of every mailbox, including archives. These numbers are your reconciliation baseline at the end.
# Run in the Exchange Management Shell
Get-Mailbox -ResultSize Unlimited | ForEach-Object {
$p = Get-MailboxStatistics $_
$a = if ($_.ArchiveDatabase) { Get-MailboxStatistics $_ -Archive } else { $null }
[pscustomobject]@{
Email = $_.PrimarySmtpAddress
Items = $p.ItemCount
SizeGB = [math]::Round($p.TotalItemSize.Value.ToBytes() / 1GB, 2)
ArchiveItems = $a.ItemCount
ArchiveGB = if ($a) { [math]::Round($a.TotalItemSize.Value.ToBytes() / 1GB, 2) } else { 0 }
}
} | Export-Csv C:\Migration\Source-Inventory.csv -NoTypeInformationFrom the inventory, decide:
- Who is in scope and what each person's target mailbox address will be in Microsoft 365.
- How much data you will upload, which sets the schedule. At 100 Mbps, a terabyte takes roughly a day of continuous upload, so size your windows accordingly.
- What to leave behind. Old Deleted Items, duplicate archives, and decade-old mail nobody needs are cheaper to skip than to move.
- Which mailboxes exceed 20 GB and will need to be split into several PST files.
Step 2: Prepare Microsoft 365
- Create the destination mailboxes and assign licenses. Users must exist in Entra ID with an Exchange Online mailbox before you import. Directory synchronization from on-premises, if you use it, must have completed.
- Enable archives for anyone whose PSTs will go to the archive. The import fails for that user if the archive is not enabled:
Connect-ExchangeOnline
Enable-Mailbox jsmith@contoso.com -Archive
# For archives that may exceed the base size, make sure auto-expanding archiving is on
Set-OrganizationConfig -AutoExpandingArchive- Give the importing admin the right roles. You need the *Mailbox Import Export* role and the *Mail Recipients* role in Exchange Online, or Global Administrator. No role group has *Mailbox Import Export* by default. A dedicated role group keeps this narrow:
New-RoleGroup -Name "PST Import" `
-Roles "Mailbox Import Export","Mail Recipients" `
-Members admin@contoso.com- Check mailbox size limits. Imported data counts toward the mailbox and archive quotas. Make sure the licenses provide enough room.
- Decide how to handle retention. The Import service puts the mailbox on retention hold after import. If your tenant has retention policies that delete or archive old mail, plan to review them (see Step 10).
Step 3: Export Mailboxes from On-Premises Exchange to PST
Exchange has a server-side cmdlet, New-MailboxExportRequest, that writes a mailbox to a PST on a network share. It needs a few things set up first.
- Give your admin account the export role on the on-premises server:
New-ManagementRoleAssignment -Role "Mailbox Import Export" -User "Administrator"Log off and on again (or restart the Exchange Management Shell) so the new role takes effect.
- Create a staging share with enough free space for all the PSTs. Grant the Exchange Trusted Subsystem security group *Read/Write* (modify) permission on both the share and the folder. Without it, the export fails with an access denied error. Also restrict access to the folder, because PSTs hold sensitive mail.
- Export one mailbox and watch it complete. Name the file after the email alias in lowercase, and give archive PSTs a clearly different name:
New-MailboxExportRequest -Mailbox jsmith -Name "export-jsmith" `
-FilePath "\\fs01\pst-staging\jsmith.pst" -BadItemLimit 10
New-MailboxExportRequest -Mailbox jsmith -IsArchive -Name "export-jsmith-archive" `
-FilePath "\\fs01\pst-staging\jsmith_archive.pst" -BadItemLimit 10
Get-MailboxExportRequest | Get-MailboxExportRequestStatistics |
Format-Table Name, Status, PercentComplete, BytesTransferred, ItemsTransferred- Split anything over 20 GB by date range, giving each part a unique name:
New-MailboxExportRequest -Mailbox jsmith -Name "export-jsmith-old" `
-ContentFilter "(Received -lt '01/01/2020')" `
-FilePath "\\fs01\pst-staging\jsmith_pre2020.pst"
New-MailboxExportRequest -Mailbox jsmith -Name "export-jsmith-recent" `
-ContentFilter "(Received -ge '01/01/2020')" `
-FilePath "\\fs01\pst-staging\jsmith_2020plus.pst"- Bulk export from a CSV of mailbox aliases once the pilot works:
Import-Csv C:\Migration\mailboxes.csv | ForEach-Object {
New-MailboxExportRequest -Mailbox $_.Alias -Name "export-$($_.Alias)" `
-FilePath "\\fs01\pst-staging\$($_.Alias).pst" -BadItemLimit 10
}- Clean up finished requests so the queue stays readable:
Get-MailboxExportRequest -Status Completed | Remove-MailboxExportRequest -Confirm:$falseNotes: exports include the Recoverable Items folder unless you add -ExcludeDumpster. Exporting many mailboxes at once loads the server, so run them in controlled batches. If a mailbox has unusually many corrupt items, the export stops after the -BadItemLimit, so review any failures rather than raising the limit blindly.
Step 4: Validate the PST Files Before You Upload
Uploading is the slow part, so catch problems first.
- Counts. Compare each export's
ItemsTransferredwith the Items column inSource-Inventory.csv. They should match within an explainable difference (such as items skipped as bad). - Names. Every file name is unique across the whole job, and you have recorded the exact case. The mapping file is case-sensitive.
- Sizes. Nothing is over 20 GB.
- Integrity. Open one or two PSTs in Outlook, or scan them with the Inbox Repair Tool, and spot-check old and new messages with attachments.
- Large items. Items over 150 MB will be skipped by Exchange Online. This is rare on-premises, since most servers cap messages lower, but check your source limits.
- Security. Run a malware scan on the staging share, and keep the folder access-controlled for the duration.
Step 5: Get the SAS URL and AzCopy
- Sign in to the Microsoft Purview portal as an account with the roles from Step 2.
- Go to Data lifecycle management, Microsoft 365, Import.
- Select New import job. Name it using lowercase letters, numbers, hyphens, and underscores only. Spaces and capitals are not allowed.
- Choose Upload your data.
- On the Import data page, choose Show network upload SAS URL, then Copy to clipboard. Save it in a protected place.
- Select Download Azure AzCopy. This is a single executable with nothing to install. Use this version. It is the one Microsoft supports for this process.
The SAS URL is a credential. Anyone who has it can upload to your tenant's import staging area, so treat it like a password: store it in a password manager or an encrypted file, and do not paste it into tickets or chat.
Step 6: Upload the PST Files with AzCopy
- Open a Command Prompt on the machine that can reach the staging share.
- Run AzCopy with the folder as the source. You cannot give it one PST file, and every PST in the folder is uploaded:
azcopy.exe copy "\\fs01\pst-staging" "<SAS URL>" --recursiveIf you want the files in a subfolder of the staging area (helpful when you run several batches), add the subfolder to the SAS URL after ingestiondata:
azcopy.exe copy "\\fs01\pst-staging" "https://xxxx.blob.core.windows.net/ingestiondata/batch1?<SAS query>" --recursiveIf you do this, remember the subfolder name. It goes into the FilePath column of the mapping file and its capitalization must match exactly. If you leave --recursive on and the source has subfolders, the files land in matching subfolders in Azure, and the FilePath has to reflect that.
- Quote the whole SAS URL (double quotes in Command Prompt, single quotes in PowerShell). In a batch file, the characters
%and&in the URL must be written as%%and^&. - Pilot with one or two PSTs first, so you can find mistakes in the command and mapping before committing hours of upload time.
- Protect your business bandwidth. AzCopy will use all the bandwidth it can. Cap it during working hours and run uncapped overnight:
azcopy.exe copy "\\fs01\pst-staging" "<SAS URL>" --recursive --cap-mbps 200- Handle interruptions. AzCopy writes a job log under your user profile (
.azcopy). List jobs withazcopy jobs listand resume withazcopy jobs resume <job-id>, supplying the SAS token again if prompted. Run the upload from a stable server close to the data, not from a laptop that sleeps. - Reduce the volume first. The Import service's filters run *after* upload, so they do not save bandwidth. To reduce data crossing the wire, use
-ContentFilterin Step 3 to exclude data you do not need, and skip empty or duplicate PSTs.
When the command finishes, it prints the number of files uploaded. Compare it to the number you expected.
### Optional: See What Was Uploaded
You can attach the free Microsoft Azure Storage Explorer to the container to confirm names and subfolder paths:
- Choose Blob container, then the Shared access signature (SAS) authentication method.
- Paste the SAS URL into Blob container SAS URL. The display name fills in as
ingestiondata. - Review the files, then right-click
ingestiondataand choose Detach when finished. If you stay attached you will get an error next time.
Storage Explorer is for viewing only. You cannot upload PSTs or delete them with it. Use AzCopy for uploads.
Step 7: Create the Mapping File
The mapping file is a CSV that tells the Import service which PST goes to which mailbox. Download Microsoft's template rather than typing the header yourself. Do not change the header row.
Workload,FilePath,Name,Mailbox,IsArchive,TargetRootFolder,ContentCodePage,SPFileContainer,SPManifestContainer,SPSiteUrl
Exchange,,jsmith.pst,jsmith@contoso.com,FALSE,/,,,,
Exchange,,jsmith_archive.pst,jsmith@contoso.com,TRUE,/,,,,
Exchange,batch1,mjones.pst,mjones@contoso.com,FALSE,/ImportedPst,,,,| Column | What to put in it |
|---|---|
Workload | Exchange |
FilePath | The subfolder in the staging area, or blank if you uploaded to the root. Never include ingestiondata. Case-sensitive, and it must match the case you used in the SAS URL |
Name | The PST file name, exactly as uploaded, including case. Unique within the job |
Mailbox | The target email address. Use the mailbox GUID instead for an inactive or soft-deleted mailbox, or when two mailboxes share an address |
IsArchive | FALSE or blank for the primary mailbox. TRUE for the archive, which must already be enabled |
TargetRootFolder | Blank creates an Imported folder at the root of the mailbox. / merges the PST's folders into the existing structure (Inbox into Inbox, and so on). /FolderName puts everything under that folder |
ContentCodePage | Only for ANSI PSTs in double-byte languages such as Japanese. Otherwise blank |
SPFileContainer, SPManifestContainer, SPSiteUrl | Leave blank |
Practical advice:
- Use
/ImportedPst(or similar) for the pilot, so you can inspect the result and delete the folder if you do not like the layout. Use/for the real run when you want a seamless mailbox. - Get the mailbox GUID with
Get-Mailbox <identity> | FL ExchangeGuid(add-InactiveMailboxOnlyor-SoftDeletedMailboxas appropriate). - To import into a cloud archive for a user whose primary mailbox is still on-premises in a hybrid deployment, use their on-premises email address and set
IsArchivetoTRUE. - Maximum 500 rows. For more PSTs, make more mapping files and more jobs.
Step 8: Create the Import Job
- Return to the Import page and the job you started in Step 5 (or begin a new one, repeating the name and Upload your data choices).
- On step 4 of the Import data page, tick I'm done uploading my files and I have access to the mapping file, then Next.
- Choose Select mapping file and pick your CSV.
- Choose Validate. The file name turns green when it passes. If it fails, open View log. Typical errors are a name or path that does not match the upload (check the case), an archive that is not enabled, or a mailbox address that does not exist.
- Accept the terms and choose Save, then Close.
The job shows Analysis in progress. Select Refresh until it reads Analysis completed. Select the job to see the status of each PST.
Step 9: Filter and Start the Import
- Select the job, then Import to Microsoft 365.
- On the Filter your data page, you see the analysis: the age of items and the types of content. Choose Yes, I want to filter it before importing to trim by age, message type, or folder, or No, I want to import everything.
- Select Import data to start.
Watch the Status column. Large PSTs take hours, and the 24 GB-per-day guideline per mailbox applies. Different mailboxes run in parallel, so spread data across mailboxes rather than queueing many PSTs for one person. If an import fails for one user (for example, because their archive was not enabled), the rest of the job continues, so fix that row and import it in a follow-up job.
Step 10: After the Import
- Reconcile counts. Compare mailbox item counts in Exchange Online to the Step 1 inventory. Imported items will normally add to what is already in the mailbox, so compare the imported folder if you used a target root folder.
Get-MailboxStatistics jsmith@contoso.com | Format-List ItemCount, TotalItemSize
Get-MailboxFolderStatistics jsmith@contoso.com -FolderScope All |
Format-Table FolderPath, ItemsInFolder- Manage the retention hold. The import turns on retention hold indefinitely, which prevents the retention policy from processing the mailbox. Decide what you want, then act. Either turn it off after a review period, or set an end date:
Set-Mailbox jsmith@contoso.com -EndDateForRetentionHold "12/31/2026"
# or, once you are sure the retention settings will not purge the imported mail
Set-Mailbox jsmith@contoso.com -RetentionHoldEnabled $falseIf a retention policy would delete or archive mail older than a given age, lengthen that age before you lift the hold, or the old imported messages may be removed.
- Check the receive limit. If an imported PST contained an item over 35 MB, the service raises the mailbox's
MaxReceiveSizeto 150 MB. - Test with users. Have a few users open Outlook and confirm the folders and attachments, find old messages through search, and check the archive.
- Confirm archive behavior. If a user's imported archive is large, confirm auto-expanding archiving is enabled and check the archive size.
- Keep a rollback copy (see below), then remove the PSTs from the staging share after the agreed period. Microsoft clears its staging container 30 days after the most recent import job.
- Retire the SAS URL. Delete saved copies from notes, scripts, and password managers.
- Decommission the source only after validation is signed off and the retention period for the backups has passed.
Mixed Environments: On-Premises and Exchange Online Together
| Situation | What to do |
|---|---|
| The mailbox is already in Exchange Online | Use its Exchange Online address in the mapping file. This is the standard case |
| The mailbox is on-premises in a hybrid deployment | You can import a PST into the user's cloud archive by using the on-premises address with IsArchive set to TRUE. For the primary mailbox, move the mailbox first |
| The user does not exist yet | Create and license the user, wait for the mailbox, then import |
| The person has left | Import into the inactive mailbox by using its GUID |
| A shared mailbox | Import works for a shared mailbox that exists in Exchange Online. Create it first |
| Public Folder content | Not supported as a target. Use the Public Folders method to move it into a mailbox |
If You Want Blob Storage You Own: Archive Copies and File Data
You can still use your own storage account to support the project.
- A rollback copy of the PSTs. After the import, copy the PST folder to your own storage account with AzCopy and keep it in the *Cool* or *Archive* access tier for 30 to 90 days, then delete it. Use a SAS token with only the permissions you need (write and list for the upload, and then revoke it). Encrypt, restrict, and expire access, because PSTs contain real mail.
azcopy copy "\\fs01\pst-staging" "https://yourstorage.blob.core.windows.net/pst-rollback?<SAS>" --recursive- Moving file data, not mail. For file shares, scripts, and other non-mailbox data, the right tools are AzCopy for smaller jobs, Azure Storage Mover for large managed transfers (including from Amazon S3), and Azure Data Box for volumes where the network is too slow. For SharePoint and OneDrive, the SharePoint Migration Tool is built for the job.
- Plan for tiers. Data in an archive tier (Azure Archive or Amazon Glacier) must be restored (rehydrated) to a hot or cool tier before migration tools can read it.
- These tools copy, they do not move. The source stays in place until you validate and delete it yourself.
Troubleshooting
| Symptom | Likely cause |
|---|---|
New-MailboxExportRequest is not recognized | The Mailbox Import Export role is not assigned, or the shell was not restarted after you assigned it |
| Export fails with access denied to the share | Exchange Trusted Subsystem lacks Read/Write on the share and folder |
| AzCopy reports an authorization error | The SAS URL was truncated, not quoted, or & and % were not escaped in a batch file |
| Mapping file validation fails for a PST | The name or FilePath does not match the uploaded file's case, or the file is not in the container |
| Import fails for one user only | Their archive is not enabled, the address does not match a mailbox, or two mailboxes share the address (use the GUID) |
| Imported mail seems to vanish after a few weeks | Retention hold was lifted and a retention policy processed the old mail. Adjust the policy first |
| Folder names look garbled for ANSI PSTs | A ContentCodePage value is needed for double-byte languages |
| Some items are missing | Items over 150 MB are skipped. Compare the item counts to the export statistics |
| Storage Explorer will not connect the second time | The container was not detached after the last session |
| The import takes much longer than expected | Several large PSTs target the same mailbox and run in sequence. Spread them across mailboxes where possible |
The Complete Checklist
- [ ] Source inventory saved, scope and target addresses decided
- [ ] Microsoft 365 users, licenses, and mailboxes ready, archives enabled where needed
- [ ] Import roles assigned in Exchange Online, export role assigned on-premises
- [ ] Staging share created with Exchange Trusted Subsystem permissions and restricted access
- [ ] PSTs exported, each under 20 GB, uniquely named, with counts checked
- [ ] PSTs scanned and spot-checked
- [ ] Import job created, SAS URL stored securely, supported AzCopy downloaded
- [ ] Pilot upload and import completed and reviewed
- [ ] Full upload completed and verified
- [ ] Mapping file validated (case, paths, archives, GUIDs)
- [ ] Analysis completed, filters chosen, import completed
- [ ] Item counts reconciled and users tested
- [ ] Retention hold reviewed and retention policies checked
- [ ] Rollback copy kept for an agreed period, then deleted
- [ ] SAS URL discarded, staging share cleaned, source decommissioned
If you want help planning a mailbox migration, scoping a hybrid project, or recovering history from an old Exchange server, get in touch.