Sunsetless.Ews
2.1.0
dotnet add package Sunsetless.Ews --version 2.1.0
NuGet\Install-Package Sunsetless.Ews -Version 2.1.0
<PackageReference Include="Sunsetless.Ews" Version="2.1.0" />
<PackageVersion Include="Sunsetless.Ews" Version="2.1.0" />
<PackageReference Include="Sunsetless.Ews" />
paket add Sunsetless.Ews --version 2.1.0
#r "nuget: Sunsetless.Ews, 2.1.0"
#:package Sunsetless.Ews@2.1.0
#addin nuget:?package=Sunsetless.Ews&version=2.1.0
#tool nuget:?package=Sunsetless.Ews&version=2.1.0
Sunsetless EWS
Keep your EWS Managed API code and run it on Microsoft Graph.
Microsoft is retiring Exchange Web Services (EWS) in Exchange Online. From October 10, 2026, a tenant that keeps EWS on (EWSEnabled set to True) has to list the apps allowed to use it in EWSAllowedAppIDs. In the other tenants Microsoft switches EWS off in waves, each announced in the Message Center 7 days ahead. On April 1, 2027 EWS is retired. Sunsetless EWS is a source-compatible build of the EWS Managed API 2.2 (namespace Microsoft.Exchange.WebServices.Data, the same types and methods). Inside your application it translates calls to Exchange Online into Microsoft Graph calls. Calls to on-premises Exchange keep using EWS, so hybrid setups keep working.
Sunsetless is independent: not affiliated with, endorsed by or sponsored by Microsoft.
Install
Replace the Microsoft package with this one and rebuild your application. Your code compiles unchanged.
dotnet remove package Microsoft.Exchange.WebServices
dotnet add package Sunsetless.Ews
Projects can target .NET Framework 3.5 or newer, or .NET 8, 9 and 10. Routing to Microsoft Graph needs .NET Framework 4.7.2 or newer on the computer; for applications built for .NET Framework 3.5 to 4.6.1, see below. The assemblies are strong-named (public key token 1b8c25c6fc94dcf1), so strong-named applications and components can reference them on .NET Framework too.
If Microsoft.Exchange.WebServices stays in the build, referenced directly or through another package, the build uses this package's Microsoft.Exchange.WebServices.dll and shows warning SUNSETLESS001. A library compiled against Microsoft's package then loads this file on .NET 8, 9 and 10. On .NET Framework it fails with FileLoadException, because it asks for Microsoft's signed assembly.
.NET Framework: binding redirects
On .NET Framework 4.6.2 and newer the package's dependencies (System.Text.Json, Microsoft.Identity.Client) need assembly binding redirects in the application's config file. Set <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects> in the application's project and the build writes them to YourApp.exe.config. A web application reads them from web.config, which the build does not change: add the redirects there by hand.
A missing redirect shows as Could not load file or assembly 'System.Memory, ...'. The located assembly's manifest definition does not match the assembly reference.
.NET Framework 3.5 to 4.6.1
Projects that target .NET Framework 3.5, 4.0, 4.5, 4.6 or 4.6.1 get a Microsoft.Exchange.WebServices.dll without dependencies, so they need no binding redirects. It is the only file the package adds to the application, also with packages.config. The part that talks to Microsoft Graph is inside it, with its own private copies of System.Text.Json and Microsoft.Identity.Client, so the application can use any version of those libraries itself. That part loads the first time a request goes to Microsoft Graph. Requests that stay on EWS, such as those to Exchange Server, never load it.
It needs:
- .NET Framework 4.7.2 or newer installed on the computer. Applications built for .NET Framework 4.0 to 4.6.1 run on it unchanged.
- The .NET Framework 4 runtime. An application built for .NET Framework 3.5 runs on the .NET Framework 2.0 runtime unless its config file asks for version 4 first:
<configuration>
<startup>
<supportedRuntime version="v4.0" />
<supportedRuntime version="v2.0.50727" />
</startup>
</configuration>
Without these, requests to Microsoft Graph fail with a ServiceLocalException that names what is missing. Requests that stay on EWS keep working, and Autodiscover goes to Exchange.
Microsoft Graph and Microsoft Entra ID accept only TLS 1.2 or newer, which applications built for .NET Framework 4.5 or older do not offer by default. The library's own connections to them ask for TLS 1.2 themselves. ServicePointManager.SecurityProtocol and the application's other connections stay as they are.
Configure
Nothing changes in your code. The library calls Microsoft Graph as an app registration in your Microsoft Entra tenant, so it needs that app's ID and a way to sign in as it. Set four environment variables for the process:
| Variable | Meaning |
|---|---|
SUNSETLESS_TENANT_ID |
Your Microsoft Entra tenant ID |
SUNSETLESS_CLIENT_ID |
The application (client) ID of the app registration |
SUNSETLESS_CLIENT_SECRET |
A client secret of the app registration |
SUNSETLESS_LICENSE_KEY |
Your license key or trial key |
Certificate instead of a client secret
Microsoft recommends a certificate for production. Leave SUNSETLESS_CLIENT_SECRET out and set one of these:
| Variable | Meaning |
|---|---|
SUNSETLESS_CERT_THUMBPRINT |
Thumbprint of the app's certificate in the Windows certificate store (CurrentUser\My or LocalMachine\My) |
SUNSETLESS_CERT_PATH, SUNSETLESS_CERT_PASSWORD |
A .pfx file with the app's certificate, and its password |
Optional settings
| Variable | Meaning |
|---|---|
SUNSETLESS_DEFAULT_MAILBOX |
The mailbox for calls that set no ImpersonatedUserId |
SUNSETLESS_DENY_CROSS_MAILBOX_ACCESS |
true refuses to open a mailbox other than the request's own (the impersonated user, else the X-AnchorMailbox header, else SUNSETLESS_DEFAULT_MAILBOX), also for out-of-office settings, inbox rules and SetUserPhoto, as EWS does for a user without delegate rights |
SUNSETLESS_BRIDGED_HOSTS |
The EWS hosts routed to Graph; default outlook.office365.com,outlook.office.com |
SUNSETLESS_BRIDGE_ALL_HOSTS |
true routes every host to Graph, on-premises Exchange Server included; see below |
SUNSETLESS_DISABLED |
1 switches the translation off; the package then works like the original library |
SUNSETLESS_AUTODISCOVER |
passthrough sends Autodiscover to Exchange instead of answering it from Microsoft Graph |
SUNSETLESS_SCAN_LIMIT |
The largest folder (in items) in which the library itself evaluates filters, sort orders and query strings Microsoft Graph cannot; default 20000. A larger folder answers such a query with a clear error |
Configuration in code
Instead of environment variables, before the first ExchangeService call:
Sunsetless.Ews.SunsetlessEws.Configure(new Sunsetless.Ews.Core.SunsetlessEwsOptions
{
TenantId = "...",
ClientId = "...",
ClientSecret = "...", // or CertificateThumbprint, or CertificatePath and CertificatePassword
LicenseKey = "...",
});
Exchange Server URL, mailboxes in Exchange Online
By default only calls to Exchange Online go to Microsoft Graph, and calls to any other host stay on EWS. When the mailboxes have moved to Exchange Online but the application still calls an Exchange Server URL, such as https://mail.contoso.com/EWS/Exchange.asmx, set SUNSETLESS_BRIDGE_ALL_HOSTS=true (or BridgeAllHosts = true in the options). Calls to every host then go to Microsoft Graph, and the application's configuration stays as it is.
The library signs in as the app registration, so the user name and password the code sets for Exchange Server are not used. Name the mailbox with ImpersonatedUserId or SUNSETLESS_DEFAULT_MAILBOX. Autodiscover is unaffected: it is answered from Microsoft Graph for Exchange Online mailboxes and goes to Exchange for the rest.
Many tenants in one process
A service that works for several Microsoft 365 tenants, such as a hosted product with many customers, configures each ExchangeService with the values of its tenant. The call replaces the process-wide configuration for that service only:
var service = new ExchangeService(ExchangeVersion.Exchange2013_SP1) { Url = new Uri("https://outlook.office365.com/EWS/Exchange.asmx") };
Sunsetless.Ews.SunsetlessEws.Configure(service, new Sunsetless.Ews.Core.SunsetlessEwsOptions
{
TenantId = customer.TenantId,
ClientId = "...",
CertificateThumbprint = "...",
});
Services with the same options share one Microsoft Graph sign-in and token cache, so creating a service per request is fine. Changing the options object afterwards does not affect a configured service. Without LicenseKey the options use the process-wide key or SUNSETLESS_LICENSE_KEY; an ISV key is not tied to a tenant, so one key serves every customer.
Inside a component
A component or SDK that ships this package inside it, for other developers to build on, supplies its own license key in its code:
Sunsetless.Ews.SunsetlessEws.UseComponentLicense("...");
The call routes nothing by itself: until the application configures a tenant, every ExchangeService keeps using EWS, and the application needs no key of its own. A key issued for a component works only when the assembly that makes the call has the strong-name public key token the key names, so make the call from the component's own assembly, not through reflection. The same key in SUNSETLESS_LICENSE_KEY or LicenseKey is refused. When the application sets a key of its own, that key is checked first.
Permissions
The app registration needs Microsoft Graph application permissions for what your code does:
| Permission | For |
|---|---|
Mail.ReadWrite, Mail.Send |
mail, mail folders, search folders, conversations, sync and notifications |
Calendars.ReadWrite |
calendars, meetings, availability, delegates |
Contacts.ReadWrite (Contacts.Read to only read) |
contacts, contact pictures, contacts folders, FindPeople in a contacts folder |
Tasks.ReadWrite.All |
tasks (Microsoft To Do) |
MailboxSettings.ReadWrite |
out-of-office settings, inbox rules, delegates' meeting requests, the CategoryList and WorkHours user configurations, Autodiscover (MailboxSettings.Read is enough for it) |
User.Read.All, Group.Read.All |
ResolveNames, ExpandDL, FindPeople in the directory, user photos, GetPasswordExpirationDate; User.Read.All also for Autodiscover |
ProfilePhoto.ReadWrite.All |
SetUserPhoto |
Place.Read.All |
room lists |
MailboxFolder.Read.All |
folders of every kind (calendar, contacts, tasks, notes, journal) when listing folders, also inside mail folders and Deleted Items; calendars made through EWS or MAPI, which Graph's calendar list leaves out; the Notes, Journal and Root folders and their items (sticky notes, journal entries); for FindPeople, a person's contacts in hidden contact folders such as Recipient Cache |
MailboxFolder.ReadWrite.All |
optional: renaming, extended properties and deleting of calendars made through EWS or MAPI; new calendars inside another folder; MoveFolder and CopyFolder of calendars (with MailboxItem.ImportExport.All) |
MailboxItem.Read.All |
contact groups (read); task properties To Do does not carry (size, percent complete, work and billing fields, extended properties); contacts, calendar items and tasks that lie in Deleted Items or Recoverable Items, also to report a deleted one as moved into Recoverable Items in notifications |
MailboxItem.ImportExport.All |
what Microsoft Graph has no call of its own for, done through the mailbox export and import: MoveItem and CopyItem of contacts, tasks and calendar items; posts (PostItem, PostReplyItem); contact groups (create, change, delete); meetings saved, changed, moved or deleted with SendToNone; task fields To Do does not keep (any percent complete, work, mileage, billing, companies, sensitivity, extended properties, regenerating recurrence); file attachments of contacts; attached messages that carry attachments of their own or are given as MIME; an .eml imported as a received message; SuppressReadReceipts; SoftDelete of contacts, tasks and appointments; ICalUid in UpdateItem |
MailboxConfigItem.ReadWrite |
optional: user configurations of every name (GetUserConfiguration, CreateUserConfiguration, UpdateUserConfiguration, DeleteUserConfiguration), GetUserRetentionPolicyTags and the RetentionDate of a message given a retention tag, through Microsoft Graph's userConfiguration API (Graph beta) |
Mail-Advanced.ReadWrite.All |
changing the subject, body, recipients or extended properties of a message that is not a draft (UpdateItem on received or sent mail): Microsoft Graph requires it for that from 31 December 2026 |
Domain.Read.All |
optional: Autodiscover GetDomainSettings and GetAppMarketplaceUrl |
Without the last seven the rest works; the library then lists only mail folders under the root and under mail folders and only the calendars Graph lists under Calendar, reads tasks from To Do alone, shows no contact groups, reports deleted calendar items, contacts and tasks as DeletedEvent, serves only CategoryList and WorkHours as user configurations, and answers the Notes and Journal folders, the calendar writes listed for MailboxFolder.ReadWrite.All, other user configurations, GetUserRetentionPolicyTags, the operations listed for MailboxItem.ImportExport.All and, once Microsoft Graph enforces it, the message changes listed for Mail-Advanced.ReadWrite.All with an error that names the missing permission.
Autodiscover (AutodiscoverUrl, GetUserSettings, GetDomainSettings) is answered from Microsoft Graph, because Exchange Online refuses app-only tokens there. The answer needs User.Read.All and MailboxSettings.Read; without them Autodiscover goes to Exchange. SUNSETLESS_AUTODISCOVER=passthrough sends it to Exchange instead.
The library calls Microsoft Graph with the app's own permissions, not with the rights of the user it impersonates, and an item or folder ID opens the mailbox it belongs to. Limit the mailboxes the app can open with RBAC for Applications in Exchange Online. An application that takes item IDs, folder IDs or mailbox addresses from its users should also set SUNSETLESS_DENY_CROSS_MAILBOX_ACCESS=true.
License
Sunsetless EWS is commercial software. You get a license key at https://sunsetless.com/pricing, or a free trial key for testing at https://sunsetless.com/trial. Without a valid key the package does not call Microsoft Graph, and calls fail with ErrorInvalidLicense. The key is checked offline, and the package sends no telemetry. The terms are at https://sunsetless.com/terms.
What is covered
The supported operations, what was verified against live Exchange Online, and the known differences between EWS and Graph are listed at https://sunsetless.com/compatibility. To check an application, open its EWS usage report in the checker at https://sunsetless.com/coverage-check. The report stays in your browser. Without the report, the free tool ews-scan (https://github.com/sunset-less/ews-scan) lists the EWS operations in the compiled application: dotnet tool install --global Sunsetless.EwsScan.
This version has no preview operations. Operations added in later versions before they are verified against live Exchange Online will be off by default and listed on the compatibility page; SUNSETLESS_PREVIEW_FEATURES=true will switch them on.
Support
contact@sunsetless.com
Third-party code
Built on the EWS Managed API source code, Copyright (c) Microsoft Corporation, MIT License. Its license text is in licenses/ews-managed-api-license.txt in this package.
For .NET Framework 3.5 to 4.6.1, Microsoft.Exchange.WebServices.dll also contains System.Text.Json, Microsoft.Identity.Client and their dependencies (MIT License). Their notices are in licenses/third-party-notices.txt.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
| .NET Framework | net35 is compatible. net40 is compatible. net403 was computed. net45 is compatible. net451 was computed. net452 was computed. net46 was computed. net461 was computed. net462 is compatible. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 is compatible. net481 was computed. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen40 was computed. tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETFramework 3.5
- No dependencies.
-
.NETFramework 4.0
- No dependencies.
-
.NETFramework 4.5
- No dependencies.
-
.NETFramework 4.6.2
- Microsoft.Identity.Client (>= 4.90.1)
- System.Text.Json (>= 10.0.12)
-
.NETFramework 4.8
- Microsoft.Identity.Client (>= 4.90.1)
- System.Text.Json (>= 10.0.12)
-
.NETStandard 2.0
- Microsoft.Identity.Client (>= 4.90.1)
- System.DirectoryServices (>= 10.0.12)
- System.Drawing.Common (>= 10.0.12)
- System.Security.Cryptography.Xml (>= 10.0.12)
- System.Text.Json (>= 10.0.12)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
Calendars an application created through EWS or MAPI, which Microsoft Graph's calendar list leaves out, work like any other calendar: with the optional MailboxFolder.Read.All permission they appear in folder listings and FindItem, CalendarView, GetItem, SyncFolderItems and notifications read them; renaming, moving, copying and deleting them, and creating a calendar inside a folder other than Calendar, need MailboxFolder.ReadWrite.All. MoveFolder and CopyFolder work for calendars, with their items and subfolders; a moved calendar and its items get new IDs. With MailboxFolder.Read.All, FindFolder and SyncFolderHierarchy list folders of every kind under any folder, also under mail folders, Deleted Items and Contacts, as EWS does. Notifications: a deleted item arrives as a move to Recoverable Items, as in Exchange (for calendar items, contacts and tasks with the optional MailboxItem.Read.All permission), and so do the items of a deleted folder and an item recovered from Recoverable Items; a folder whose items changed gets a ModifiedEvent with the UnreadCount Exchange gives; a subscription to all folders also covers calendars outside the Calendar folder; subscriptions made one after another start from the same watermark. User configuration objects of every name, in any folder, with dictionary, XML and binary data, through Microsoft Graph's userConfiguration API, which is in preview (optional MailboxConfigItem.ReadWrite permission); without it CategoryList and WorkHours work as before. With the same permission GetUserRetentionPolicyTags returns the mailbox's retention policy tags, and a message gets its RetentionDate as soon as it is tagged. MarkAsJunk puts the sender on the blocked senders list or takes them off, also with MoveItem=false. FindPeople makes one persona of the contacts Exchange links as one person, also across contact folders, with Exchange's persona ID; the query searches the contact fields Exchange searches, and search results are sorted by display name. GetUserPhoto returns the stored photo whatever size is requested, as Exchange Online does. IconIndex names the SMS icons. A new post no longer shows up for a moment as a message in its folder. The license check no longer compares the key's update date with the package's release date. The README lists Mail-Advanced.ReadWrite.All, which Microsoft Graph requires from 31 December 2026 to change the subject, body, recipients or extended properties of received and sent messages. Security: the types in Sunsetless.Ews.Core.dll are internal apart from SunsetlessEwsOptions, so the library runs only through ExchangeService. SUNSETLESS_DENY_CROSS_MAILBOX_ACCESS also covers out-of-office settings, inbox rules named by a mailbox address and SetUserPhoto, and without impersonation it takes the request's own mailbox from X-AnchorMailbox or SUNSETLESS_DEFAULT_MAILBOX. EWS traces with HTTP headers show the Authorization header redacted. Characters that XML does not allow are removed from responses instead of failing them. A redirect from Microsoft Graph never carries the token to another host, and a request with a body does not leave Graph. A POST is not retried after 504, or after 503 without Retry-After, so a message cannot go out twice. The token is requested for the host in GraphBaseUri. A certificate read from a file keeps its key in memory only on Windows. Cookies are not shared between tenants, the Autodiscover and per-mailbox caches have an upper bound, and XML parsing refuses DTDs.