Connecting an EWS application

Your application keeps sending EWS SOAP requests. You change the endpoint and the credential; the bridge performs each request through Microsoft Graph with an app-only token of your Microsoft 365 organization and answers exactly like Exchange, including Exchange error codes.

1. Licence key and console

Buy a plan on the pricing page: the licence key (FRG-EWSB-…) arrives by e-mail a few minutes after payment. Open the console and sign in with the key and your e-mail. The first sign-in creates your bridge and shows a bridge key (brg_ews_…) once; copy it into your application’s secret store. You can issue more keys and revoke old ones in the console at any time.

In the console press Connect Microsoft 365. A Global Administrator or Privileged Role Administrator of your organization signs in with Microsoft (this proves which organization it is — the bridge takes the tenant from the signed Microsoft token, never from a URL), then grants admin consent to the “Avakode EWS Bridge” application. One Microsoft 365 organization can be connected to one bridge only.

The application asks for these Microsoft Graph application permissions (they act without a signed-in user, for the whole organization unless you scope them — see step 3):

PermissionWhy the bridge needs it
Mail.ReadWriteMessages and mail folders: FindItem, GetItem, CreateItem (drafts), UpdateItem, DeleteItem, MoveItem, CopyItem, MarkAllItemsAsRead, attachments, FindFolder/GetFolder.
Mail.SendSending: SendItem and CreateItem/UpdateItem with MessageDisposition SendOnly or SendAndSaveCopy.
Calendars.ReadWriteCalendar items (FindItem CalendarView, GetItem, CreateItem, UpdateItem, DeleteItem, meeting cancellations) and GetUserAvailability (free/busy).
Contacts.ReadWriteContacts and contact folders; the contacts part of ResolveNames.
User.Read.AllResolveNames against the directory and finding a mailbox whose SMTP address differs from the user principal name. Reads directory profiles only, no mail.
MailboxSettings.ReadWriteGetUserOofSettings and SetUserOofSettings (automatic replies); GetInboxRules and UpdateInboxRules (Inbox rules).
Place.Read.AllGetRoomLists and GetRooms (room lists and rooms from Microsoft 365 places). Reads room names and addresses only.
GroupMember.Read.AllExpandDL (members of a distribution group). Reads group memberships, not mail.

The administrator sign-in itself only asks for openid profile — the bridge reads the tenant id and the administrator role from the token and keeps nothing else.

3. Limit access to some mailboxes (recommended)

Exchange Online offers two ways to restrict which mailboxes an application can reach. Run them in Exchange Online PowerShell (Connect-ExchangeOnline). Use the application (client) id and the service principal object id shown under Enterprise applications → Avakode EWS Bridge in Microsoft Entra.

Application Access Policy — works with the consent from step 2

New-ApplicationAccessPolicy -AppId <application-id> -PolicyScopeGroupId ews-bridge@contoso.com -AccessRight RestrictAccess -Description "Avakode EWS Bridge"
Test-ApplicationAccessPolicy -Identity alex@contoso.com -AppId <application-id>

The group is a mail-enabled security group with the mailboxes the bridge may use. Microsoft describes Application Access Policies as being replaced by RBAC for Applications, but they still constrain permissions granted through Microsoft Entra consent.

RBAC for Applications — Microsoft’s replacement

New-ServicePrincipal -AppId <application-id> -ObjectId <service-principal-object-id> -DisplayName "Avakode EWS Bridge"
New-ManagementScope -Name "EWS bridge mailboxes" -RecipientRestrictionFilter "MemberOfGroup -eq '<distinguished name of the group>'"
New-ManagementRoleAssignment -App <service-principal-object-id> -Role "Application Mail.ReadWrite" -CustomResourceScope "EWS bridge mailboxes"
# repeat for Application Mail.Send, Application Calendars.ReadWrite, Application Contacts.ReadWrite, Application MailboxSettings.ReadWrite
Test-ServicePrincipalAuthorization -Identity <service-principal-object-id> -Resource alex@contoso.com

Important: RBAC grants add to Microsoft Entra consent. For the scope to take effect, remove the organization-wide mail, calendar, contacts and mailbox-settings permissions from the Entra consent (keep User.Read.All). Changes can take 30 minutes to 2 hours to apply.

4. Point the application at the bridge

Set the EWS URL to https://ews.avakode.com/EWS/Exchange.asmx — for the EWS Managed API that is service.Url = new Uri("https://ews.avakode.com/EWS/Exchange.asmx"); for raw SOAP clients, the POST target. SOAP 1.1 and 1.2 are both accepted; the answer uses the version you sent.

If your application uses Autodiscover, point it at the bridge explicitly: normal Autodiscover follows your e-mail domain to Microsoft. The bridge answers all three Autodiscover forms and always returns its own EWS URL:

  • POX: POST https://ews.avakode.com/autodiscover/autodiscover.xml
  • JSON: GET https://ews.avakode.com/autodiscover/autodiscover.json?Email=alex@contoso.com&Protocol=EWS
  • SOAP (GetUserSettings): POST https://ews.avakode.com/autodiscover/autodiscover.svc

5. Authentication and mailbox selection

  • Basic: login = the mailbox address, password = the bridge key. The login becomes the mailbox when the request names no other.
  • Bearer: Authorization: Bearer <bridge key>.

The mailbox a request works on is taken, in this order, from the SOAP header ExchangeImpersonation/ConnectingSID (PrimarySmtpAddress, SmtpAddress or PrincipalName), the X-AnchorMailbox HTTP header, and the Basic login when it is an e-mail address. Without any of them the bridge answers ErrorMissingEmailAddress. A Mailbox inside a DistinguishedFolderId still addresses another mailbox (delegate access). Impersonation by SID is not supported.

What is translated

41 of 96 EWS operations are answered through Microsoft Graph today: 31 fully, 10 partly. The coverage table is generated from the bridge’s operation registry — the same list the bridge dispatches on — with every limit and every operation that is not translated. “Partial” in a single answer (a field Graph cannot provide, an unmapped extended property) is reported per request in your gap report; operations not translated answer with a clear EWS error and are counted there too — never an empty success.

Reading the gap report

The console shows the last 30 days per operation: requests, translated, partial, unsupported, errors and the top reason (for example fields: item:Culture or ErrorServerBusy); Download JSON gives the same data. The “answered from upstream” share is (translated + partial) / requests. The bridge records only operation names, statuses, reasons and latency — use the report to see which calls your application still makes that the bridge cannot serve. The free EWS log analyzer gives the same view from your Exchange logs before you switch.

Limits and plans

  • 60 requests per second per bridge; above that 429 with Retry-After.
  • Plans: tenant up to 50,000 requests a month, tenant-plus up to 500,000, selfhosted without a limit (pricing). Going over the monthly volume never blocks traffic; we get in touch.
  • Microsoft Graph throttling becomes the EWS answer your client already handles (see below).

Typical errors and what to do

AnswerMeaningWhat to do
SOAP Fault ErrorServerBusy with BackOffMilliseconds (HTTP 500)Microsoft Graph throttled the request (or was briefly unreachable).Wait the back-off and retry — the EWS Managed API raises ServerBusyException with BackOffMilliseconds.
ErrorConnectionFailed “Microsoft Graph is not connected” or “could not obtain a Microsoft Graph token”No Microsoft 365 organization is connected yet, or the admin consent was revoked.Connect Microsoft 365 in the console (step 2) — an administrator signs in and consents again.
ErrorAccessDeniedGraph refused this call: the permission it needs is not consented (for example Mail.Send removed from the consent), or the mailbox is outside your Application Access Policy / RBAC scope.Grant consent again from the console; check Test-ApplicationAccessPolicy / Test-ServicePrincipalAuthorization for that mailbox.
ErrorInvalidIdMalformedAn id the bridge cannot use — typically an attachment id stored from the old Exchange integration. Item and folder ids stored from Exchange are translated automatically.Read the item again (GetItem) and use the attachment ids it returns.
ErrorMissingEmailAddressThe request names no mailbox.Use a mailbox address as the Basic login, or send X-AnchorMailbox / ExchangeImpersonation.
ErrorInvalidRequest “Not translated by Avakode EWS Bridge: …”The operation or option is not translated (see the table).Check the gap report; tell us which operations matter to you.
HTTP 401Missing, wrong or revoked bridge key.Use a current key from the console.
HTTP 403 “Tenant suspended”The licence is not active (expired, cancelled or refunded).Renew the licence; the bridge reactivates within a day.

Privacy

The bridge stores: your bridge (hashes of bridge keys, never the keys), the Microsoft 365 tenant id and settings sealed with AES-GCM, daily request counters, and coverage events with operation name, status, reason and latency. It does not store message bodies, subjects, addresses, attachments, contacts or calendar content — they pass through memory only. Logs carry no request URLs or content.

Self-hosted

The same bridge runs on your own server as a Docker image (licence plan selfhosted, no tenant limit, data in one volume). Ask us for the self-hosted guide.