To migrate an Exchange Web Services (EWS) application to Microsoft Graph, map each EWS operation the app uses to its Graph equivalent (for example FindItem to list messages, SyncFolderItems to message delta, GetUserAvailability to getSchedule), replace full_access_as_app and impersonation with Graph application permissions scoped through RBAC for Applications, and convert any stored EWS item IDs with translateExchangeIds. Microsoft has stated that EWS starts to be disabled for all Exchange Online organizations in October 2026 and is fully disabled in April 2027, so integrations that still speak SOAP need to move now. Some EWS capabilities have no Graph equivalent yet, and a few never will, so the first job is to find out which of your scenarios fall into those groups.
Who this is for and what you will have at the end
This guide is for developers and Microsoft 365 administrators who own in-house or vendor applications that read or write Exchange Online mailboxes through EWS: mail processors, room and calendar booking tools, CRM and ticketing integrations, archiving tools and sync services. At the end you will have:
- A per-application map from EWS operations to Microsoft Graph APIs.
- An app registration with least-privilege Graph permissions, scoped to the mailboxes it actually needs.
- Working patterns for search, incremental sync, availability, attachments and custom properties.
- A method for converting stored EWS IDs to stable Graph IDs.
- A list of gaps to escalate to vendors or design around.
Timeline and scope
Microsoft announced in 2018 that EWS would no longer receive feature updates, and in 2023 set October 2026 as the disablement date. The January 2024 Midnight Blizzard incident, which involved EWS, widened the scope to Microsoft's own applications as well. The current timeline on Microsoft's deprecation page is:
- October 2026: EWS starts to be disabled globally for all organizations.
- April 2027: EWS is fully disabled.
Two scope points shape the design. First, the retirement applies to Exchange Online; Microsoft Graph is not supported for Exchange on-premises, so an app that serves on-premises mailboxes in a hybrid organization needs to keep EWS for those mailboxes. Second, Microsoft explicitly advises not to wait for every parity gap to close: start with the applications you can migrate today.
Prerequisites
- An inventory of EWS applications. Start with the EWS usage report in the Microsoft 365 admin center, which Microsoft recommends as the first step for finding the EWS footprint in your organization. Microsoft also publishes an EWS code analyzer tool and an AI-assisted refactoring tutorial for source code you own.
- Source code or a vendor commitment. For third-party products, ask the vendor for their Graph migration date and the version that ships it.
- An app registration in Microsoft Entra ID for each migrated application, with a certificate credential for daemon apps.
- Exchange Online PowerShell and membership in the Organization Management role group (plus the Exchange Administrator role in Entra ID) to create RBAC for Applications assignments.
- The Microsoft Graph PowerShell SDK for testing the calls in this guide.
Step 1: Map EWS operations to Graph APIs
Search the codebase for the EWS operations it calls (or the EWS Managed API methods that wrap them) and map each one. Microsoft's mapping article covers the common operations:
| EWS operation | Microsoft Graph replacement |
|---|---|
FindItem | List messages (GET /users/{id}/messages or a folder's messages) |
GetItem | Get message |
CreateItem / SendItem | Create message, then send; or sendMail |
UpdateItem, MoveItem, CopyItem, DeleteItem | Update, move, copy and delete message |
GetFolder, CreateFolder, UpdateFolder, MoveFolder, DeleteFolder | Mail folder APIs (get, create, update, move, delete) |
SyncFolderItems | Message delta (messages/delta on a folder) |
SyncFolderHierarchy | Mail folder delta |
Subscribe (push) / Unsubscribe | Create and delete subscription |
GetEvents (pull notifications) | Message delta |
GetUserAvailability | calendar/getSchedule |
GetAttachment, CreateAttachment, DeleteAttachment | Attachment APIs (upload session above 3 MB) |
GetInboxRules, UpdateInboxRules | Message rule APIs |
GetUserOofSettings, SetUserOofSettings | Get and update mailboxSettings |
GetMailTips | getMailTips |
ConvertId | translateExchangeIds |
ResolveNames | List people |
Mark every operation that does not appear in Microsoft's mapping or in the parity roadmap (Step 6) as a risk. Microsoft's guidance is blunt: if a capability is not in the roadmap table, don't plan on a Graph or Exchange admin API equivalent arriving before EWS is fully disabled.
Step 2: Replace impersonation with scoped application permissions
A typical EWS daemon registers the full_access_as_app application permission on Office 365 Exchange Online, requests a token for https://outlook.office365.com/.default, and impersonates each mailbox, sending the X-AnchorMailbox header with the mailbox address. That model grants access to every mailbox in the tenant. Microsoft positions Graph's granular permissions as the security improvement over EWS's "all or none" access.
In Graph, the same daemon requests a token for https://graph.microsoft.com/.default, and addresses mailboxes directly with /users/{id | userPrincipalName}/.... Pick the narrowest application permissions for the workload, for example Mail.Read, Mail.ReadWrite, Mail.Send, Calendars.ReadWrite, Contacts.ReadWrite or MailboxSettings.ReadWrite.
To restrict those permissions to a subset of mailboxes, use RBAC for Applications in Exchange Online, which replaces Application Access Policies. The grant is made in Exchange, not in Entra ID:
Connect-ExchangeOnline
# 1. Pointer to the Entra service principal (use the Enterprise applications IDs)
New-ServicePrincipal -AppId '<application (client) ID>' -ObjectId '<service principal object ID>' `
-DisplayName 'Room booking sync'
# 2. Which mailboxes the app may touch
New-ManagementScope -Name 'Room mailboxes' -RecipientRestrictionFilter "CustomAttribute1 -eq 'RoomSync'"
# 3. What it may do there
New-ManagementRoleAssignment -App '<service principal object ID>' `
-Role 'Application Calendars.ReadWrite' -CustomResourceScope 'Room mailboxes'
# 4. Test against one in-scope and one out-of-scope mailbox
Test-ServicePrincipalAuthorization -Identity 'Room booking sync' -Resource 'room-a@contoso.com'Take the IDs from the Enterprise applications page, not App registrations, which shows different values. You can scope by an Entra administrative unit instead of a management scope with -RecipientAdministrativeUnitScope.
The most common mistake is leaving the same permission consented in Entra ID. Microsoft documents that permissions from Entra ID and from RBAC for Applications are combined as a union, so an unscoped Calendars.ReadWrite grant in Entra ID makes the Exchange scope meaningless. Remove the Entra consent for any permission you grant through RBAC for Applications. Also allow for the permission cache: changes take between 30 minutes and 2 hours to apply to a running app, although Test-ServicePrincipalAuthorization bypasses the cache.
Step 3: Rewrite the data access patterns
Most EWS code translates directly once you think in REST resources instead of SOAP requests. These are the patterns that need the most care.
Search and incremental sync
FindItem loops become GET requests with $select (return only the properties you use) and paging. SyncFolderItems maps to delta query, which works per folder: to track a folder hierarchy you track each folder individually. The first call returns all items in pages linked by @odata.nextLink; the last page returns an @odata.deltaLink that you store and call next time to get only changes.
Connect-MgGraph -ClientId '<app ID>' -TenantId '<tenant ID>' -CertificateThumbprint '<thumbprint>'
$uri = "https://graph.microsoft.com/v1.0/users/ops@contoso.com/mailFolders/inbox/messages/delta?`$select=subject,from,receivedDateTime,isRead"
$headers = @{ Prefer = 'IdType="ImmutableId"' }
do {
$page = Invoke-MgGraphRequest -Method GET -Uri $uri -Headers $headers
foreach ($msg in $page.value) {
if ($msg.'@removed') { "Deleted: $($msg.id)" } else { "$($msg.receivedDateTime) $($msg.subject)" }
}
$uri = $page.'@odata.nextLink'
$deltaLink = $page.'@odata.deltaLink'
} while ($uri)
# Persist $deltaLink and use it as the starting URI for the next sync roundKnow the delta limits before you rely on them: message delta supports $select, $top and $expand; $filter only on receivedDateTime with ge or gt, and a filtered delta returns at most 5,000 messages; $orderby only as receivedDateTime desc; and $search is not supported. Deleted items come back with an @removed property.
Calendar reads and availability
An EWS CalendarView on FindItem becomes GET /users/{id}/calendarView?startDateTime=...&endDateTime=..., which expands recurring meetings into occurrences and exceptions. The start and end values are interpreted with the offset you include, or as UTC if there is none; the Prefer: outlook.timezone header only changes the time zone of the returned events. Free/busy lookups through GetUserAvailability move to getSchedule.
Attachments
Files under 3 MB can be added with a single POST to the item's attachments. Between 3 MB and 150 MB, create an upload session and PUT the file in byte ranges in order, keeping each range under 4 MB for performance. The upload URL is pre-authenticated, so don't send an Authorization header with those PUT requests. Trying to create an upload session for a file under 3 MB returns ErrorAttachmentSizeShouldNotBeLessThanMinimumSize.
Custom properties
If the EWS app reads or writes MAPI properties through ExtendedPropertyDefinition, use Graph extended properties (singleValueExtendedProperties and multiValueExtendedProperties). The ID formats are "{type} {guid} Name {name}", "{type} {guid} Id {id}" or "{type} {proptag}", for example String {8ECCC264-6880-4EBE-992F-8888D2EEAA1D} Name TestProperty. Keep the same format the EWS app used, because Microsoft advises accessing an extended property only through the format you chose. For new custom data, open extensions are the recommended option.
Step 4: Replace EWS notifications
EWS push, pull and streaming notifications have no one-to-one equivalent. Graph offers change notification subscriptions for push delivery, and delta query replaces pull notifications. The robust pattern combines both: a subscription tells you that something changed, and a delta call fetches exactly what changed and catches anything a missed notification would have lost. The subscription lifecycle, validation and renewal are covered in Microsoft Graph webhooks: subscriptions, lifecycle events and renewal.
Step 5: Convert stored EWS IDs
Applications that store EWS item IDs (in a CRM link table, for example) must convert them, because Graph uses a different ID format. translateExchangeIds converts between ewsId, entryId, immutableEntryId, restId and restImmutableEntryId. Each call accepts up to 1,000 IDs, all with the same source type and all for items in the same mailbox. With application permissions, call it on /users/{id}/translateExchangeIds; the least-privileged application permission is User.Read.All.
$params = @{
inputIds = @('<EWS item ID 1>', '<EWS item ID 2>')
sourceIdType = 'ewsId'
targetIdType = 'restImmutableEntryId'
}
Invoke-MgTranslateUserExchangeId -UserId 'ops@contoso.com' -BodyParameter $paramsConvert to the immutable format and send Prefer: IdType="ImmutableId" on every Graph request from then on. Default Graph IDs change when an item moves between folders; immutable IDs stay the same while the item stays in the same mailbox. They still change if the item moves to an archive mailbox or is exported and re-imported, and they are case-sensitive, so compare them exactly. Note that translateExchangeIds is not available in the US Government L4 and L5 (DoD) clouds.
Step 6: Plan for the gaps
Microsoft's deprecation page lists the gaps it is prioritising, with target dates that can change:
| Gap | Target |
|---|---|
Notes (IPM.StickyNote), contact lists, additional contact properties | Q3 CY2026 |
| Import and export for archive, public folder and Microsoft 365 Group mailboxes | Q4 CY2026 |
| Generic CRUD on existing In-Place Archive items | Q4 CY2026 |
| Exchange Admin API (including folder permissions) | Q4 CY2026 |
| Report message as junk, phishing or not junk | Q4 CY2026 |
| Non-draft MIME create and update | Q4 CY2026 |
| User configuration objects (folder-associated items) | Q4 CY2026 |
| Mark all items as read in a folder | Q4 CY2026 |
| Exchange workload APIs in supported sovereign clouds | Q4 CY2026 |
Microsoft has also confirmed three capabilities that won't be added: generic public folder CRUD, generic Microsoft 365 Group mailbox CRUD (use the group conversation, thread and post APIs instead) and access to legacy Discovery Mailboxes (use Microsoft Purview eDiscovery).
For full-fidelity copies of items, which EWS apps did with ExportItems and UploadItems, use the mailbox import and export APIs. They expose mailbox folders and items in a uniform format, export items as an opaque full-fidelity stream, and the overview page says they support primary, shared and archive mailboxes, although the deprecation roadmap still lists archive import and export as a gap, so test against an archive mailbox before you depend on it. Microsoft states they are not designed for backup and restore; Microsoft 365 Backup covers that. The matching RBAC application roles include Application MailboxItem.Export and Application MailboxItem.ImportExport.
Verification
- Run the app against a test mailbox in scope and confirm every migrated feature, including attachments over 3 MB and recurring meetings.
- Run
Test-ServicePrincipalAuthorizationagainst an out-of-scope mailbox and confirmInScopeisFalse; then confirm a real call to that mailbox fails. - Confirm the Entra ID app registration no longer has
full_access_as_appand that noApplication EWS.AccessAsApprole assignment remains in Exchange. - Watch the EWS usage report until the application no longer appears in it.
Troubleshooting
The app can still read mailboxes outside its management scope. An unscoped permission is still consented in Entra ID. Permissions from the two systems are combined, so remove the Entra ID grant.
Scope changes don't take effect. RBAC for Applications changes are cached for 30 minutes to 2 hours depending on how active the app is. Use Test-ServicePrincipalAuthorization to check the configuration without waiting.
Autodiscover calls fail for the app. Microsoft documents that Autodiscover can't be accessed when using RBAC application roles. Graph doesn't need Autodiscover; remove that code path.
Stored IDs no longer match. Graph IDs are case-sensitive, default IDs change when items move between folders, and immutable IDs change when items move to the archive. Switch to immutable IDs and re-translate any stragglers.
translateExchangeIds returns errors for a batch. Check that every ID in the call is for the same mailbox and uses the same source format, and that the batch has no more than 1,000 IDs.
Delta returns fewer items than expected. A $filter on a delta query returns at most 5,000 messages, and delta is per folder. Remove the filter for the initial sync or run delta for each folder.
A feature has no Graph API. Check the roadmap table. If the capability isn't listed, design around it now rather than waiting.
Checklist
- Every EWS application identified from the usage report, with an owner and a target date.
- EWS operations mapped to Graph APIs; gaps flagged.
- App registration uses Graph application permissions, not
full_access_as_app. - Mailbox access scoped with RBAC for Applications and the matching Entra ID consent removed.
- Sync rewritten with per-folder delta; notifications rebuilt as subscriptions plus delta.
- Stored IDs translated to
restImmutableEntryId;Prefer: IdType="ImmutableId"sent on every request. - Attachments above 3 MB use upload sessions.
- Vendors confirmed their Graph release for each third-party app.
- EWS permissions and role assignments removed after cutover.
References
- Deprecation of Exchange Web Services in Exchange Online
- Migrate EWS apps to Microsoft Graph
- EWS to Microsoft Graph API mappings
- Authenticate an EWS application by using OAuth
- Role Based Access Control for Applications in Exchange Online
- Get incremental changes to messages in a folder
- List calendarView
- Attach large files to Outlook messages or events
- Outlook extended properties overview
- Obtain immutable identifiers for Outlook resources
- user: translateExchangeIds
- Overview of the mailbox import and export APIs
- Use Microsoft Graph PowerShell authentication commands
- Invoke-MgGraphRequest