Sending Very Large Attachments with Exchange Online
The message size limit for Exchange Online is 150 MB. In other words, Exchange Online will accept and deliver messages of up to the limit if the sending and receiving mailboxes are capable of handling such large messages. If the send and receive limits for the mailboxes are configured with smaller values, Exchange issues non-delivery notifications like the one shown in Figure 1. In this case, the MaxReceiveSize configured for the target mailbox is 36 MB, and it was unable to accept an incoming message of 119 MB.

Looking at some newly-created mailboxes, it seems like enterprise accounts have maximum receive sizes of 125 MB and maximum send sizes of 150 MB. These are the values contained in the ExchangeOnlineEnterprise-8fc1c029-5e32-485e-9810-179fb470144 mailbox plan. Lower send and receive limits are given to different forms of mailboxes, and it’s possible that user mailboxes might have received lower limits in the past. To check, run:
Get-ExoMailbox -ResultSize Unlimited -Properties MaxSendSize, MaxReceiveSize | Format-Table DisplayName, MaxReceiveSize, MaxSendSize
Handling Large Attachments
Sending very large messages with an Outlook client is relatively easy. Create a new message, find some large files to attach, and send it off. Programmatically, the same concept applies, but a 3 MB limit applied for uploading a single attachment to an Outlook message. Larger attachments required special handling. The steps described by Microsoft are “create an upload session and iteratively use PUT to upload ranges of bytes of the file until you have uploaded the entire file.”
Since late 2024 (or so it appears – I cannot find an official announcement for the change), the previous 3 MB limit is no longer in effect. At the time of writing, the documentation still mentions the 3 MB limit, but it’s now possible to create and send messages with large attachments with the Send-MgUserMail cmdlet.
For example, I was able to send a message with 10 attachments of 119 MB in total (Figure 2), the largest being an 89 MB PowerPoint presentation from a TEC conference keynote. The operation works perfectly if the network connection is reliable. You’ll wait for the data to upload from the workstation to Exchange Online, but the attachments will arrive and be sent with the message.

However, when networks aren’t quite so reliable, using an upload session is a good idea because it allows the upload to be split into chunks and resumed if the transmission of a chunk fails.
Uploading a Large Attachment in Chunks
I am indebted to MVP Glen Scales for his help in figuring out the complexities of uploading a large attachment for an Exchange Online message. Glen pointed me to PowerShell script that he had written for the task. Microsoft documents the process of uploading large attachments, but not in PowerShell. Having a solid example to work from is always a good thing.
Some of the maximum size for an attachment is taken up by encoding, so the practical limit for an individual file is smaller. The maximum size of an Exchange Online message includes the message body and all attachments, plus some overhead for encoding. Care must therefore be taken to ensure that the total size of a message doesn’t exceed 150 MB.
The process to upload large attachments in chunks is as follows:
- Create a draft message by constructing a message structure and running the New-MgUserMessage cmdlet. After the cmdlet completes, a draft message exists in the message sender’s mailbox.
- Run the New-MgUserMessageAttachmentUploadSession cmdlet to create a new upload session. The response is an upload URL that includes the necessary authentication token to allow the process to upload the attachment to the target mailbox. The authentication token is time-limited by an expiration time (usually two hours after the session starts) that determines by when the upload session must complete processing the attachment. If the complete content for the attachment is not successfully transmitted and received by this time, the upload session terminates and Exchange Online discards any uploaded data.
- Process the attachment content by sending a sequence of PUT requests to upload binary chunks of 320 KB until the complete file is processed. Larger chunks up to a maximum of 4 MB can be used if they are multiples of 320 KB.
- Run the Send-MgUserMessage to send the draft message complete with the uploaded attachment.
Let’s assume that you have a message structure populated with details like the recipient, from address, message body, subject, and so on. To prepare to upload a large attachment, we create a hash table containing details of the attachment like its file name and size.
$AttachmentFile = "C:\Temp\PowerShellBook.pdf" [int32]$MaxAttachmentSize = 146800640 $FileStream = New-Object System.IO.StreamReader($AttachmentFile) $FileSize = $FileStream.BaseStream.Length # The maxiumum size of an attachment is 150 MB (157286400 bytes). There's some overhead, so we'll restrict this to 140 MB (146800640 bytes). If ($FileSize -gt $MaxAttachmentSize) { Write-Host ("Attachment {0} is too large ({1} bytes). Maximum size is {2} bytes (140 MB)" -f $AttachmentFile, $FileSize, $MaxAttachmentSize) Break } # Build structure for the attachment $AttachmentDetail = @{} $AttachmentDetail.Add("attachmentType", "file") $AttachmentDetail.Add("name", [System.IO.Path]::GetFileName($AttachmentFile)) $AttachmentDetail.Add("size", $FileSize) $AttachmentParams = @{} $AttachmentParams.Add("AttachmentItem", $AttachmentDetail)
The next step creates a new draft message in the sender’s mailbox and sets up a session to upload the attachment to Exchange Online:
$NewMessage = New-MgUserMessage -UserId $MsgFrom -Body $MsgParams -ToRecipients $EmailRecipient -Subject $MsgSubject $UploadSession = New-MgUserMessageAttachmentUploadSession -UserId $MsgFrom -MessageId $NewMessage.Id -BodyParameter $AttachmentParams
To upload the file for the attachment, a Do/While loop splits the file into 960 KB chunks (a multiple of the standard 320 KB to reduce the number of operations) and uploads each chunk until the end of the file is reached:
[Int32]$uploadChunkSize = 983040 $FileOffsetStart = 0 $FileBuffer = [byte[]]::new($UploadChunkSize) do { $FileChunkByteCount = $FileStream.BaseStream.Read($FileBuffer, 0, $FileBuffer.Length) $FileOffsetEnd = $FileStream.BaseStream.Position - 1 If ($FileChunkByteCount -gt 0) { $UploadRangeHeader = "bytes " + $FileOffsetStart + "-" + $FileOffsetEnd + "/" + $FileSize $FileOffsetStart = $fileStream.BaseStream.Position $BinaryContent = New-Object System.Net.Http.ByteArrayContent -ArgumentList @($FileBuffer, 0, $FileChunkByteCount) $FileBuffer = [byte[]]::new($uploadChunkSize) $Headers = @{ 'AnchorMailbox' = $MsgFrom 'Content-Range' = $UploadRangeHeader } $Result = (Invoke-RestMethod -Method Put -Uri $UploadSession.UploadUrl -UserAgent "UploadAgent" -Headers $Headers -Body $BinaryContent.ReadAsByteArrayAsync().Result -ContentType "application/octet-stream") } } while ($FileChunkByteCount -ne 0)
The headers for each PUT operation include details of the byte range within the file represented by the current chunk. Byte chunks must be processed in order. Knowing which chunk is being processed if an error occurs means that it’s possible to resume transmission from the last known chunk if the connection drops. The URL used with the Invoke-RestMethod cmdlet is returned by the New-MgUserMessageAttachmentUploadSession cmdlet and points to the https://outlook.office365.com endpoint. The URL includes details of the user account, tenant, and draft message.
The final step is to send the draft message, which now includes the attachment.
Send-MgUserMessage -UserId $MsgFrom -MessageId $NewMessage.Id
Sending Very Large Attachments: Maybe Not an Everyday Requirement
Most of our readers probably use reliable networks that can upload large attachments without issues. On the other hand, situations do exist where networks are less robust, and outages do occur. That’s where upload chunking comes into its own.
You can download the example code I used from GitHub. Remember that the script is not an off-the-shelf solution. It’s intended to illustrate the principles behind uploading a large attachment for an Exchange Online message. You might never have to do this, but if you do, you’ll know how to do the job.