Architecture

Migrate EWS apps to Microsoft Graph before Exchange Online turns off EWS

Move mail, calendar and contact integrations from EWS to Microsoft Graph: map operations, replace impersonation with scoped permissions, convert stored IDs and plan for gaps.

13 min read
On this page

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 operationMicrosoft Graph replacement
FindItemList messages (GET /users/{id}/messages or a folder's messages)
GetItemGet message
CreateItem / SendItemCreate message, then send; or sendMail
UpdateItem, MoveItem, CopyItem, DeleteItemUpdate, move, copy and delete message
GetFolder, CreateFolder, UpdateFolder, MoveFolder, DeleteFolderMail folder APIs (get, create, update, move, delete)
SyncFolderItemsMessage delta (messages/delta on a folder)
SyncFolderHierarchyMail folder delta
Subscribe (push) / UnsubscribeCreate and delete subscription
GetEvents (pull notifications)Message delta
GetUserAvailabilitycalendar/getSchedule
GetAttachment, CreateAttachment, DeleteAttachmentAttachment APIs (upload session above 3 MB)
GetInboxRules, UpdateInboxRulesMessage rule APIs
GetUserOofSettings, SetUserOofSettingsGet and update mailboxSettings
GetMailTipsgetMailTips
ConvertIdtranslateExchangeIds
ResolveNamesList 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 round

Know 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 $params

Convert 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:

GapTarget
Notes (IPM.StickyNote), contact lists, additional contact propertiesQ3 CY2026
Import and export for archive, public folder and Microsoft 365 Group mailboxesQ4 CY2026
Generic CRUD on existing In-Place Archive itemsQ4 CY2026
Exchange Admin API (including folder permissions)Q4 CY2026
Report message as junk, phishing or not junkQ4 CY2026
Non-draft MIME create and updateQ4 CY2026
User configuration objects (folder-associated items)Q4 CY2026
Mark all items as read in a folderQ4 CY2026
Exchange workload APIs in supported sovereign cloudsQ4 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

  1. Run the app against a test mailbox in scope and confirm every migrated feature, including attachments over 3 MB and recurring meetings.
  2. Run Test-ServicePrincipalAuthorization against an out-of-scope mailbox and confirm InScope is False; then confirm a real call to that mailbox fails.
  3. Confirm the Entra ID app registration no longer has full_access_as_app and that no Application EWS.AccessAsApp role assignment remains in Exchange.
  4. 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

Questions people ask

When does EWS stop working in Exchange Online?

Microsoft's deprecation page states that EWS starts to be disabled globally for all organizations in October 2026 and is fully disabled in April 2027. EWS in Exchange Server on-premises is not part of this retirement, and Microsoft Graph is not supported for on-premises mailboxes.

What replaces EWS impersonation in Microsoft Graph?

Instead of full_access_as_app plus impersonation, a daemon app uses Graph application permissions such as Mail.Read or Calendars.ReadWrite and calls /users/{id} paths directly. To limit which mailboxes the app can reach, grant those permissions through RBAC for Applications in Exchange Online with a management scope or administrative unit.

How do I convert EWS item IDs that my app stored in a database?

Call the translateExchangeIds function with sourceIdType ewsId and a targetIdType such as restImmutableEntryId. Each call accepts up to 1,000 IDs, and all IDs in a call must be for items in the same mailbox and use the same source format.

Is there a Graph equivalent for every EWS feature?

No. Microsoft publishes a roadmap of parity gaps, such as archive and public folder import and export, notes and user configuration objects, and a list of capabilities that won't be added, including generic public folder CRUD and generic Microsoft 365 Group mailbox CRUD.

Exchange OnlineEWSMicrosoft GraphMicrosoft Entra ID
  1. Fix Microsoft Graph 429 throttling with Retry-After, batching and delta

    Stop 429 Too Many Requests errors from Microsoft Graph: honour Retry-After, tune the SDK retry handler, batch correctly, cut request cost and replace polling with delta queries.

    Architecture13 min read
  2. Multi-tenant SaaS on Entra ID: admin consent, tenant allowlist, app lock

    Register a multi-tenant SaaS app in Microsoft Entra ID, onboard customer tenants through admin consent, enforce a tenant allowlist in token validation and lock service principal credentials with app instance property lock.

    Architecture12 min read
  3. Dataverse Application Users: Server-to-Server Auth with an Entra App

    Register a Microsoft Entra app, create a Dataverse application user with a least-privilege security role, get a client credentials token and call the Web API, then fix common errors.

    Architecture10 min read