Migrating from EWS to Microsoft Graph
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.
Table of contents
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 contentsMail.ReadWrite
- full read/write access to mailboxMail.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:
New Graph permissions:
Example of permission changes for an application operating in unattended (application) mode:
Previous EWS permissions:
New Graph permissions:
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.