Migrating from EWS to Microsoft Graph

  |   Lukas Matyska

Microsoft will retire Exchange Web Services (EWS) in Exchange Online on October 1, 2026. The recommended way to access Exchange Online (Microsoft 365) going forward is via the Microsoft Graph API. This guide describes how to migrate from EWS to Microsoft Graph in order to access Exchange Online, and demonstrates how the same mailbox operations can be performed using Rebex EWS and Rebex Graph libraries for .NET.

Working directly with Microsoft Graph API means dealing with raw HTTP requests and handling many low-level details of the REST protocol. Rebex Graph provides a high-level API for accessing Exchange Online via Graph - without requiring deep knowledge of the protocol itself.

Key advantages of using Rebex Graph library:

  • A developer-friendly easy-to-use API that abstracts away the complexity of raw Graph requests.
  • Built-in S/MIME support, including parsing and working with signed or encrypted messages.
  • Automatic handling of throttling responses (HTTP 429/503/504 error codes) with retries logic.
  • Wide platform support, including .NET 5-10, .NET Framework 3.5-4.8 on Windows 7/8/10/11
    (even .NET Compact Framework 3.5-3.9 is still supported).

Note: Microsoft Graph does not offer full feature parity with EWS. Some advanced or specialized operations may not yet be available or may require workarounds.

Configuring Graph access to Exchange Online

Migrating from EWS to Microsoft Graph is not just a matter of switching API calls — it also requires updates to your authentication and permission setup. In this section, we'll highlight two essential changes you need to make before your application can access Exchange Online via Graph.


Changing application permissions

To access Exchange Online, your application needs to obtain an OAuth 2.0 token. Since Microsoft started disabling Basic Authentication for Exchange Online in October 2022, your app should already be registered in Azure Portal using an App Registration.

To switch to Microsoft Graph, you must configure new Graph-specific permissions in the Azure Portal based on your application's functionality. Here are some commonly used permissions when working with emails:

  • Mail.Read - read mailbox contents
  • Mail.ReadWrite - full read/write access to mailbox
  • Mail.Send - send mail as a user

Depending on you application type choose either Delegated or Application permission type.

Example of permission changes for an application operating in delegated (signed-in user) mode:

Previous EWS permissions: App permissions - delegated

New Graph permissions: App permissions - delegated

Example of permission changes for an application operating in unattended (application) mode:

Previous EWS permissions: App permissions - application

New Graph permissions: App permissions - application

If you prefer to configure new Azure App Registration from start, you can follow our blog posts to configure Graph access in delegated (signed-in user) mode or unattended (application) mode.


Changing OAuth scopes

When moving from EWS to Microsoft Graph, you also need to update the OAuth scope used for token requests.

Previous EWS scopes: commonly include either https://outlook.office365.com/.default or https://outlook.office365.com/EWS.AccessAsUser.All

New Graph scopes: the simplest option is to use https://graph.microsoft.com/.default - this instructs the Microsoft identity platform to issue a token based on the permissions configured in the App Registration. Alternatively, you can explicitly request only a subset of those permissions using scopes such as:

  • https://graph.microsoft.com/Mail.Read
  • https://graph.microsoft.com/Mail.ReadWrite
  • https://graph.microsoft.com/Mail.Send

Find more about scopes and permissions in the Microsoft identity platform.


Sample applications

To verify that your configuration works, you can use our sample applications available on GitHub.

To test access in delegated (signed-in user) mode, use the GraphOAuthWpfApp sample.
To test access in unattended (application) mode, use the GraphOAuthAppOnlyConsole sample.
 

Migrating to Graph

In this section, we’ll demonstrate how common mailbox operations were implemented using Rebex EWS and how they can be implemented using Rebex Graph.

Connecting and authenticating to Exchange Online

With Rebex EWS, connecting and authenticating looks like this:

// create EWS client instance
var client = new Rebex.Net.Ews();

// connect and authenticate to Exchange Online server
client.Connect("outlook.office365.com");
client.Login(token, EwsAuthentication.OAuth20);

With Rebex Graph, the code for connecting and authenticating looks similar:

// create Graph client instance
var client = new Rebex.Net.GraphClient();

// connect and authenticate to Exchange Online server
client.Connect();
client.Login(token);

In EWS SOAP API, client connects to https://outlook.office365.com/EWS/Exchange.asmx.
In Graph REST API, all requests go to https://graph.microsoft.com/.

In both cases, client authenticates using the Authorization header with a Bearer token.

Note: EWS supports both on-premises Exchange servers and Exchange Online, whereas Microsoft Graph is available exclusively for Exchange Online.
Note: EWS supports various authentication methods, including Basic Auth, NTLM, and Kerberos, whereas Microsoft Graph relies exclusively on OAuth 2.0.


Listing Inbox messages

With Rebex EWS, listing messages looks like this:

// create, connect and authenticate EWS client instance
var client = new Rebex.Net.Ews();
client.Connect("outlook.office365.com");
client.Login(token, EwsAuthentication.OAuth20);

var messages = client.GetMessageList(EwsFolderId.Inbox);

With Rebex Graph, the code for listing messages looks similar:

// create, connect and authenticate Graph client instance
var client = new Rebex.Net.GraphClient();
client.Connect();
client.Login(token);

var messages = client.GetMessageList(GraphFolderId.Inbox);

In EWS SOAP API, messages were listed using the FindItems operation.
In Graph REST API, messages from the Inbox are retrieved by requesting:

GET https://graph.microsoft.com/v1.0/me/mailFolders/inbox/messages

For details, see List messages in the Microsoft documentation.


Searching for messages

Microsoft Graph API provides two kinds of searching: $filter and full-text $search.

With Rebex EWS, the equivalent code for a $filter search and full-text $search (uses AQS search) looks like this:

// create, connect and authenticate EWS client instance
var client = new Rebex.Net.Ews();
client.Connect("outlook.office365.com");
client.Login(token, EwsAuthentication.OAuth20);

// perform EWS message search
var messages = client.Search(EwsFolderId.Inbox, EwsSearchParameter.Subject("invoice"));

// perform full-text AQS search
var fullText = client.Search(EwsFolderId.Inbox, EwsItemFields.Envelope, new EwsListView(), "subject:invoice");

With Rebex Graph, searching for messages looks like this:

// create, connect and authenticate Graph client instance
var client = new Rebex.Net.GraphClient();
client.Connect();
client.Login(token);

// perform the `$filter` search
var messages = client.Search("inbox", GraphMessageSearchParameter.Subject("invoice"));

// perform full-text `$search`
var query = new GraphMessageSearchQuery()
{
    RawSearch = "subject:invoice"
};
var fullText = client.Search("inbox", query);

In EWS SOAP API, messages were searched using the FindItems operation with search filters.
In Graph REST API, search can be performed using the $filter query parameter:

GET https://graph.microsoft.com/v1.0/me/messages?$filter=contains(subject,'invoice')

or the $search query parameter for full-text search:

GET https://graph.microsoft.com/v1.0/me/messages?$search="subject:invoice"

Note: Microsoft Graph $search works only for a limited set of message properties (approximately 15).
Note: Microsoft Graph cannot apply $filter to properties that are collections of complex objects (such as toRecipients, ccRecipients or bccRecipients). Use $search for filtering based on recipients instead.
Note: Microsoft Graph does not support combining $filter and $search in a single query, which makes it difficult to filter messages effectively - especially when filtering by recipients.
Note: Microsoft Graph imposes limitations on the use of the $orderby parameter. In some cases, this may result in the The restriction or sort order is too complex for this operation error. For details, see Using filter and orderby in the same query in the Microsoft documentation.

For details, see Filter and Search in the Microsoft documentation.


Downloading e-mail messages

With Rebex EWS, downloading messages looks like this:

// create, connect and authenticate EWS client instance
var client = new Rebex.Net.Ews();
client.Connect("outlook.office365.com");
client.Login(token, EwsAuthentication.OAuth20);

// get the message ID
EwsItemId messageId = ...

// download the message directly to a file on disk
client.GetMessage(messageId, "mail.eml");

// download the message and parse it for further processing
MailMessage message = client.GetMailMessage(messageId);

With Rebex Graph, the code for downloading messages looks similar:

// create, connect and authenticate Graph client instance
var client = new Rebex.Net.GraphClient();
client.Connect();
client.Login(token);

// get the message ID
GraphMessageId messageId = ...

// download the message directly to a file on disk
client.GetMessage(messageId, "mail.eml");

// download the message and parse it for further processing
MailMessage message = client.GetMailMessage(messageId);

In EWS SOAP API, a message was retrieved using the GetItem operation.
In Graph REST API, a message is retrieved by requesting its $value:

GET https://graph.microsoft.com/v1.0/me/messages/{messageId}/$value

For details, see Get message in the Microsoft documentation.


Sending messages

With Rebex EWS, sending messages looks like this:

// create, connect and authenticate EWS client instance
var client = new Rebex.Net.Ews();
client.Connect("outlook.office365.com");
client.Login(token, EwsAuthentication.OAuth20);

// compose message
MailMessage message = ...

// send the message
client.SendMessage(message);

With Rebex Graph, the code for sending messages looks similar:

// create, connect and authenticate Graph client instance
var client = new Rebex.Net.GraphClient();
client.Connect();
client.Login(token);

// compose message
var message = new MailMessage();
message.To = "to@example.com";
message.Subject = "Hello from Rebex Graph";
message.BodyHtml = "This message was sent using <b>Rebex Graph</b>";

// send the message
client.SendMessage(message);

In EWS SOAP API, the CreateItem operation with SendAndSaveCopy was used.
In Graph REST API, sending a message requires a POST request:

POST https://graph.microsoft.com/v1.0/me/sendMail

and setting desired Content-Type header and request body content.

Note: This operation requires Mail.Send permission to be configured in Azure App Registration.
Note: Microsoft occasionally changes the limits of Microsoft Graph API. In 2024, the size limit for a MIME message sent via sendMail was 3 MB. However, as of September 2025, we were able to successfully send a 45 MB message using sendMail. The message size limits can be configured in the Microsoft 365 Exchange admin center (up to 150 MB).

For details, see Send mail in the Microsoft documentation.


Updating messages

With Rebex EWS, updating messages looks like this:

// create, connect and authenticate EWS client instance
var client = new Rebex.Net.Ews();
client.Connect("outlook.office365.com");
client.Login(token, EwsAuthentication.OAuth20);

// prepare message updates
var updates = new EwsMessageMetadata()
{
    Flag = new EwsFlag(EwsFlagStatus.Completed) { CompleteDate = DateTime.Today },
    Categories = new EwsCategoryCollection("Invoice", "Processed"),
    Importance = MailPriority.Low,
    IsRead = true,
};

// apply updates
client.UpdateItem(messageId, updates);

With Rebex Graph, the code for updating messages looks similar:

// create, connect and authenticate Graph client instance
var client = new Rebex.Net.GraphClient();
client.Connect();
client.Login(token);

// prepare message updates
var updates = new GraphMessageData()
{
    Flag = GraphFlag.CreateCompleted(DateTime.Today),
    Categories = new GraphCategoryCollection("Invoice", "Processed"),
    Importance = GraphImportance.Low,
    IsRead = true,
};

// apply updates
client.UpdateMessage(messageId, updates);

In EWS SOAP API, message updates were made using the UpdateItem operation.
In Graph REST API, messages are updated using a PATCH request:

PATCH https://graph.microsoft.com/v1.0/me/messages/{messageId}

and setting desired Content-Type header and request body content.

Note: This operation requires Mail.ReadWrite permission to be configured in Azure App Registration.
Note: The GraphClient.UpdateMessage() API will be available in upcoming 8.0 release (check-it out at NuGet.org as RC1).

For details, see Update message in the Microsoft documentation.


Deleting messages

With Rebex EWS, deleting messages looks like this:

// create, connect and authenticate EWS client instance
var client = new Rebex.Net.Ews();
client.Connect("outlook.office365.com");
client.Login(token, EwsAuthentication.OAuth20);

// get the message ID
EwsItemId messageId = ...

// delete message
client.DeleteItem(messageId, EwsDeleteMode.Permanent);

With Rebex Graph, the code for deleting messages looks similar:

// create, connect and authenticate Graph client instance
var client = new Rebex.Net.GraphClient();
client.Connect();
client.Login(token);

// get the message ID
GraphMessageId messageId = ...

// delete message
client.DeleteMessage(messageId, permanent: true);

In EWS SOAP API, the DeleteItem operation supported soft and hard deletes.
In Graph REST API, messages are soft-deleted using a DELETE request:

DELETE https://graph.microsoft.com/v1.0/me/messages/{messageId}

And hard-deleted using a POST request:

POST https://graph.microsoft.com/v1.0/me/messages/{messageId}/permanentDelete

Note: This operation requires Mail.ReadWrite permission to be configured in Azure App Registration.

For more details about soft-delete see Delete message and hard delete, see Permanent delete in the Microsoft documentation.


Listing folders

With Rebex EWS, listing folders looks like this:

// create, connect and authenticate EWS client instance
var client = new Rebex.Net.Ews();
client.Connect("outlook.office365.com");
client.Login(token, EwsAuthentication.OAuth20);

// list folders in the root folder
var rootFolders = client.GetFolderList();

// list folders in the 'Inbox' folder
var inboxFolders = client.GetFolderList(EwsFolderId.Inbox);

With Rebex Graph, the code for listing folders looks similar:

// create, connect and authenticate Graph client instance
var client = new Rebex.Net.GraphClient();
client.Connect();
client.Login(token);

// list folders in the root folder
var rootFolders = client.GetFolderList();

// list folders in the 'Inbox' folder
var inboxFolders = client.GetFolderList(GraphFolderId.Inbox);

In EWS SOAP API, listing folders was done using the FindFolder operation.
In Graph REST API, folders can be listed by requesting:

GET https://graph.microsoft.com/v1.0/me/mailFolders
or
GET https://graph.microsoft.com/v1.0/me/mailFolders/{folderId}/childFolders

For details, see List mail folders and List child folders in the Microsoft documentation.


Creating folders

With Rebex EWS, creating folders looks like this:

// create, connect and authenticate EWS client instance
var client = new Rebex.Net.Ews();
client.Connect("outlook.office365.com");
client.Login(token, EwsAuthentication.OAuth20);

// create new folder 'Orders' under 'Inbox'
EwsFolderId folderId = client.CreateFolder(EwsFolderId.Inbox, "Orders");

With Rebex Graph, the code for creating folders looks similar:

// create, connect and authenticate Graph client instance
var client = new Rebex.Net.GraphClient();
client.Connect();
client.Login(token);

// create new folder 'Orders' under 'Inbox'
GraphFolderInfo folder = client.CreateFolder(GraphFolderId.Inbox, "Orders");

In EWS SOAP API, folders were created using the CreateFolder operation.
In Graph REST API, folders can be created using POST request:

POST https://graph.microsoft.com/v1.0/me/mailFolders
or
POST https://graph.microsoft.com/v1.0/me/mailFolders/{folderId}/childFolders

and setting Content-Type: application/json header and request body content.

Note: This operation requires Mail.ReadWrite permission to be configured in Azure App Registration.

For details, see Create mail folder and Create child folder in the Microsoft documentation.


Deleting folders

With Rebex EWS, deleting folders looks like this:

// create, connect and authenticate EWS client instance
var client = new Rebex.Net.Ews();
client.Connect("outlook.office365.com");
client.Login(token, EwsAuthentication.OAuth20);

// get the folder ID
EwsFolderId folderId = ...

// delete folder
client.DeleteFolder(folderId);

With Rebex Graph, the code for deleting folders looks similar:

// create, connect and authenticate Graph client instance
var client = new Rebex.Net.GraphClient();
client.Connect();
client.Login(token);

// get the folder ID
GraphFolderId folderId = ...

// delete folder
client.DeleteFolder(folderId);

In EWS SOAP API, folders were deleted using the DeleteFolder operation.
In Graph REST API, folders can be deleted using DELETE request:

DELETE https://graph.microsoft.com/v1.0/me/mailFolders/{folderId}

Note: This operation requires Mail.ReadWrite permission to be configured in Azure App Registration.

For details, see Delete mail folder in the Microsoft documentation.