Skip to main content

Setting Up Outlook and Microsoft 365 (Delegated Access)

This guide shows you how to set up Outlook OAuth2 authentication with EmailEngine using delegated access, where each user signs in interactively through Microsoft's OAuth2 consent flow. This works with Outlook.com, Hotmail.com, and Microsoft 365 accounts. You can use either IMAP/SMTP or Microsoft Graph API as the email backend.

Looking for Application Access?

If you need to access Microsoft 365 mailboxes without interactive user login (e.g., for automated systems, helpdesk integrations, or centralized management), see Outlook Application Access (Client Credentials) instead. Application access lets an admin grant access to any mailbox in the organization using a single set of credentials.

Overview​

EmailEngine supports two types of Outlook OAuth2 integrations:

  • Delegated access (this page) - Each user signs in and grants access to their own mailbox. Supports both IMAP/SMTP and MS Graph API backends. Works with personal Microsoft accounts and Microsoft 365.
  • Application access (separate guide) - An admin grants the application access to any mailbox in the organization. Uses MS Graph API only. Microsoft 365 only, no personal accounts.

Email Backend Options​

With delegated access, you can choose between two backend options:

IMAP/SMTP Backend:

  • Standard email protocols
  • Works like any other email account
  • Simpler setup

Microsoft Graph API Backend:

  • REST calls instead of IMAP and SMTP sessions
  • Outlook categories, exposed as labels
  • The backend of application access, and the only one on which a shared mailbox receives new mail without a signed-in user

This guide covers both options.

Choosing IMAP/SMTP vs MS Graph API​

FeatureIMAP/SMTPMS Graph API
Azure permissionsIMAP.AccessAsUser.All, SMTP.SendMail.ReadWrite, Mail.Send
Change detectionIMAP IDLEGraph change notifications, which need a Service URL that Microsoft can reach, plus a periodic pass for notifications Graph dropped (since v2.82.1)
SearchIMAP SEARCHGraph $filter, or $search with useOutlookSearch (see Searching Messages)
Shared mailboxesDelegated and direct accessApplication access; with delegated or direct access the mailbox does not receive new mail (see Shared Mailboxes)
Outlook categoriesNot availableExposed as labels
Works with other providersYesNo
MS Graph search differs from IMAP

The default $filter mode cannot search to, cc, bcc, body text or message size. The $search mode, enabled per request with useOutlookSearch, reaches those fields but returns at most 1,000 results, reports no total, and orders by relevance rather than date. Searching Messages lists which search terms each backend supports.

Recommendation:

  • Use IMAP/SMTP for compatibility with other providers, and when you need IMAP's search
  • Use MS Graph API for application access, for shared mailboxes that must receive mail, and for Outlook categories

Step 1: Create Azure AD Application​

Go to Azure Portal and navigate to Microsoft Entra ID > App Registrations.

Navigate to Azure AD App Registrations

Click New registration.

Click New registration button

Step 2: Configure Application Registration​

Application registration form

Application Name​

Choose a name that users will see in the authorization form. Make it clear and recognizable (e.g., "EmailEngine" or "YourApp Email Integration").

Supported Account Types​

Choose who can use this application:

Personal Microsoft accounts only:

  • Only free @hotmail.com, @outlook.com, @live.com accounts
  • Select: "Personal Microsoft accounts only"

Single tenant (organization only):

  • Only accounts from your Microsoft 365 organization
  • Select: "Accounts in this organizational directory only"

Multi-tenant (any organization):

  • Any Microsoft 365 organization account
  • Does NOT include personal Microsoft accounts
  • Select: "Accounts in any organizational directory"

Multi-tenant + personal accounts:

  • Both Microsoft 365 and personal accounts
  • Most flexible option
  • Select: "Accounts in any organizational directory and personal Microsoft accounts"
Recommended Setting

For maximum compatibility, select "Accounts in any organizational directory and personal Microsoft accounts" unless you have specific requirements.

Microsoft Cloud Environments​

EmailEngine supports multiple Microsoft cloud environments for government and regional deployments (since v2.46.0). Select the appropriate cloud in the Azure cloud environment field when creating an Outlook OAuth2 application to use the correct endpoints.

CloudValueUse Case
Azure global serviceglobalStandard Microsoft 365 (default)
GCC Highgcc-highUS Government L4
DoDdodUS Department of Defense L5
Azure ChinachinaChina (operated by 21Vianet)

Cloud Environment Details​

Azure global service (default)

The standard commercial Microsoft 365 environment used by most organizations:

  • Entra ID Endpoint: https://login.microsoftonline.com
  • MS Graph API: https://graph.microsoft.com
  • IMAP Host: outlook.office365.com
  • SMTP Host: smtp.office365.com
  • Azure Portal: portal.azure.com

GCC High (US Government L4)

For US government agencies and contractors requiring FedRAMP High + DoD SRG Impact Level 4 compliance:

  • Entra ID Endpoint: https://login.microsoftonline.us
  • MS Graph API: https://graph.microsoft.us
  • IMAP Host: outlook.office365.us
  • SMTP Host: smtp.office365.us
  • Azure Portal: portal.azure.us

DoD (US Department of Defense L5)

For US Department of Defense requiring Impact Level 5 (ITAR and DoD SRG):

  • Entra ID Endpoint: https://login.microsoftonline.us
  • MS Graph API: https://dod-graph.microsoft.us
  • IMAP Host: outlook-dod.office365.us
  • SMTP Host: outlook-dod.office365.us
  • Azure Portal: portal.azure.us

Azure China (21Vianet)

Microsoft Azure operated by 21Vianet in China for compliance with Chinese regulations:

  • Entra ID Endpoint: https://login.chinacloudapi.cn
  • MS Graph API: https://microsoftgraph.chinacloudapi.cn
  • IMAP Host: partner.outlook.cn
  • SMTP Host: partner.outlook.cn
  • Azure Portal: portal.azure.cn

Configuring Cloud Environment via API​

When creating an Outlook OAuth2 application via API, specify the cloud parameter:

curl -X POST "https://emailengine.example.com/v1/oauth2" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Outlook GCC High",
"provider": "outlook",
"enabled": true,
"cloud": "gcc-high",
"clientId": "YOUR_CLIENT_ID",
"clientSecret": "YOUR_CLIENT_SECRET",
"redirectUrl": "https://emailengine.example.com/oauth",
"authority": "organizations"
}'

Valid values for cloud:

  • global (default if not specified)
  • gcc-high
  • dod
  • china

OAuth2 Scopes by Cloud​

Each cloud environment uses different scope URLs. EmailEngine automatically uses the correct scopes based on the selected cloud:

IMAP/SMTP Scopes:

CloudIMAP ScopeSMTP Scope
Globalhttps://outlook.office.com/IMAP.AccessAsUser.Allhttps://outlook.office.com/SMTP.Send
GCC Highhttps://outlook.office365.us/IMAP.AccessAsUser.Allhttps://outlook.office365.us/SMTP.Send
DoDhttps://outlook.office365.us/IMAP.AccessAsUser.Allhttps://outlook.office365.us/SMTP.Send
Chinahttps://partner.outlook.cn/IMAP.AccessAsUser.Allhttps://partner.outlook.cn/SMTP.Send

MS Graph API Scopes:

CloudMail.ReadWriteMail.Send
Globalhttps://graph.microsoft.com/Mail.ReadWritehttps://graph.microsoft.com/Mail.Send
GCC Highhttps://graph.microsoft.us/Mail.ReadWritehttps://graph.microsoft.us/Mail.Send
DoDhttps://dod-graph.microsoft.us/Mail.ReadWritehttps://dod-graph.microsoft.us/Mail.Send
Chinahttps://microsoftgraph.chinacloudapi.cn/Mail.ReadWritehttps://microsoftgraph.chinacloudapi.cn/Mail.Send
Government Cloud Registration

For government clouds (GCC High, DoD) and sovereign clouds (China), you must:

  1. Register your Azure AD application in the corresponding Azure portal for that cloud environment
  2. Your organization must have a subscription in that cloud environment
  3. Use the correct Azure portal URL for app registration

You cannot use an app registered in the global Azure portal for government or China cloud environments.

Redirect URI​

Platform: Select Web

URI: Your EmailEngine URL with /oauth path:

  • http://localhost:3000/oauth (for local testing)
  • https://emailengine.example.com/oauth (for production)
Redirect URL Must Match Exactly

The redirect URL must match exactly what you'll configure in EmailEngine later. Mismatches will cause OAuth failures.

Click Register to create the application.

Step 3: Copy Application (Client) ID​

Application overview page with client ID

On the application overview page, find and copy the Application (client) ID. You'll need this for EmailEngine configuration.

Keep this page open - you'll come back to it.

Step 4: Configure API Permissions​

Click API permissions in the left menu.

API permissions page

By default, only User.Read permission exists. Click Add a permission.

Adding permission button

Select Microsoft Graph and then Delegated permissions.

Select Delegated Permissions, Not Application Permissions

Make sure you select the Delegated permissions tab, not Application permissions. Application permissions are used for application access (client credentials), which is a different setup flow. Delegated permissions allow the app to act on behalf of a signed-in user.

For IMAP/SMTP Backend​

Add these permissions:

Searching for and selecting IMAP permissions

  • IMAP.AccessAsUser.All - For IMAP access
  • SMTP.Send - For sending emails via SMTP
  • offline_access - For token refresh

EmailEngine also requests the OpenID Connect scopes openid and profile with this set, to read the signed-in user's address and name from the ID token.

offline_access Required

The offline_access scope is required in both cases. It allows EmailEngine to renew access tokens in the background without user interaction.

For MS Graph API Backend​

Add these permissions instead:

  • Mail.ReadWrite - For reading and managing emails
  • Mail.Send - For sending emails
  • offline_access - For token refresh
  • User.Read - Present on every new registration; EmailEngine requests it to read the signed-in user's profile
One backend per OAuth2 app

An EmailEngine OAuth2 app requests one set of scopes, selected by its Base scope setting: either the IMAP/SMTP scopes or the Mail.* scopes. Register two OAuth2 apps in EmailEngine if you need both backends against the same Azure application.

Verify all required permissions are listed, then continue to the next step.

Step 5: Create Client Secret​

Click Certificates & secrets in the left menu.

Certificates & secrets page

Click New client secret.

New client secret

Configure Secret​

Description: Give it a meaningful name (e.g., "EmailEngine Secret")

Expires: Choose an expiration period you're comfortable with

  • 6 months (default)
  • 12 months
  • 24 months
  • Custom
Secret Expiration

When the secret expires, your application will stop working until you generate a new secret and update EmailEngine. Choose a longer expiration for production, but set a reminder to rotate it before expiry.

Click Add.

Copy Secret Value​

Copy Secret Now

The secret value is only shown once. Copy it immediately - you cannot retrieve it later. If you lose it, you'll need to generate a new secret.

Copy the value from the Value column (NOT the "Secret ID").

Step 6: Enable IMAP/SMTP (If Using IMAP Backend)​

Only for IMAP/SMTP Backend

Skip this step if you're using MS Graph API as your backend.

If you're managing a Microsoft 365 organization and EmailEngine cannot connect via IMAP/SMTP, you may need to enable these protocols manually.

Enabling IMAP and SMTP in Microsoft 365 admin center

  1. Navigate to https://admin.microsoft.com/
  2. Go to Users > Active users
  3. Select a user
  4. Navigate to Mail settings
  5. Enable IMAP and SMTP AUTH

You may need to do this for each user or configure organization-wide settings.

Step 7: Configure EmailEngine​

Now configure EmailEngine with your Azure application credentials.

Add Outlook OAuth2 Application​

Creating Outlook OAuth2 application in EmailEngine

  1. Open EmailEngine dashboard
  2. Navigate to Integrations > OAuth2 Apps
  3. Click Create OAuth2 app
  4. Select Outlook (delegated) from the dropdown

Configure OAuth2 Settings​

Outlook OAuth2 configuration form

Application name: Give it a descriptive name (e.g., "Outlook OAuth2")

Enable this app: Leave this box checked (it is checked by default for a new app). An app that is not enabled is not offered in the hosted authentication form

Base scope: Select the tab that matches your Azure permissions:

  • IMAP and SMTP (baseScopes: "imap") - If you added IMAP.AccessAsUser.All and SMTP.Send
  • MS Graph API (baseScopes: "api") - If you added Mail.ReadWrite and Mail.Send. The MS Graph API panel also lists the two endpoints Microsoft Graph posts change notifications to
Base scope must match the Azure permissions

The base scope you select here must match the permissions you configured in Azure. Mismatches will cause authentication failures. The base scope cannot be changed once the app is created.

Application (client) ID: Paste the Application (client) ID from Azure

Client secret: Paste the secret value you copied earlier

Azure cloud environment: Azure global service unless the tenant lives in a government or regional cloud

Supported account types: Choose the option that matches your Azure registration. The API field is authority, and each option maps to one value:

Option in EmailEngineAzure registrationauthority value
Work and school accounts from any organizationAccounts in any organizational directoryorganizations
Work, school and personal Microsoft accountsAccounts in any organizational directory and personal Microsoft accountscommon
Personal Microsoft accounts onlyPersonal Microsoft accounts onlyconsumers
Work and school accounts from one organizationAccounts in this organizational directory onlyThe Directory (tenant) ID, a UUID like f8cdef31-a31e-4b4a-93e4-5f571e91255a, entered in the Directory (tenant) ID field that this option reveals

Redirect URL: Must match exactly what you entered in Azure:

  • Example: https://emailengine.example.com/oauth

Click Register app to save.

Limiting Scopes (MS Graph API)​

With MS Graph API as the base scope, the MS Graph API panel of the Base scope card shows four Scope preset buttons. Each button fills in the Additional scopes (API field extraScopes) and Disabled scopes (skipScopes) fields in the Custom scopes section at the bottom of the form; scopes you added by hand are kept:

PresetAdditional scopesDisabled scopesResult
Full accessnonenoneMail.ReadWrite, Mail.Send, offline_access, User.Read
Read-onlyMail.ReadMail.ReadWrite, Mail.SendRead without sending or modifying
Read-only + sendMail.ReadMail.ReadWriteRead and send without modifying
Send-onlynoneMail.ReadWriteSend without reading

EmailEngine classifies a delegated account by the scopes Microsoft granted: read access is Mail.Read or Mail.ReadWrite, send access is Mail.Send. An account with send access and no read access runs in send-only mode, reports sendOnly: true, and creates no change subscription, so no messages are synced and no message webhooks fire. The admin interface shows a Send-only badge on its page. Application access accounts always count as full access, because the .default scope does not enumerate permissions.

Changing the scopes on the application affects new authorizations only. Existing accounts keep the scopes granted when they signed in until the user signs in again.

Step 8: Test the Setup​

Add an Outlook account to test the OAuth2 flow.

Via Hosted Authentication Form​

Using hosted authentication form

  1. In EmailEngine, open Accounts, click Add account, fill in the name, and click Continue
  2. On the hosted authentication form, click the Microsoft sign-in button of your OAuth2 app
  3. Complete the OAuth2 consent flow
  4. EmailEngine will store the credentials and connect

The account should enter "connected" state within moments.

Via API​

Generate an authentication form URL:

curl -X POST https://emailengine.example.com/v1/authentication/form \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"account": "user123",
"email": "user@outlook.com",
"redirectUrl": "https://myapp.com/settings"
}'

Direct the user to the returned URL.

Learn more about hosted authentication →

Direct API Account Registration​

If you already have OAuth2 tokens:

curl -X POST https://emailengine.example.com/v1/account \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"account": "user123",
"name": "John Doe",
"email": "user@outlook.com",
"oauth2": {
"provider": "AAABlf_0iLgAAAAQ",
"accessToken": "ACCESS_TOKEN_FROM_MICROSOFT",
"refreshToken": "REFRESH_TOKEN_FROM_MICROSOFT",
"auth": {
"user": "user@outlook.com"
}
}
}'
Provider ID

The provider field should be the OAuth2 application ID from EmailEngine, which is a base64url encoded string like AAABlf_0iLgAAAAQ. You can find this ID in Integrations > OAuth2 Apps in the EmailEngine interface. This is NOT the Application (client) ID from Azure AD.

See full API documentation →

Shared Mailboxes​

Microsoft 365 shared mailboxes are mailboxes not bound to a specific user. Multiple users can access them using their own credentials.

Recommended: Application Access

For shared mailboxes, Outlook Application Access (Client Credentials) is the simplest approach - no interactive login, no extra scopes, one API call per mailbox.

With delegated access, EmailEngine also supports shared mailboxes through:

  • Delegated access - Add a main account, then add shared mailboxes that reference its credentials
  • Direct access - Add a shared mailbox with its own OAuth2 authentication

For detailed setup instructions covering all three approaches, see the Shared Mailboxes guide.

MS Graph API Backend​

These notes apply to every account on the MS Graph API backend, whether it was added with delegated access or with application access.

Change Notifications​

EmailEngine learns about new and changed messages through Microsoft Graph change notifications, which Microsoft delivers to two endpoints under the Service URL:

  • {serviceUrl}/oauth/msg/notification - message change notifications
  • {serviceUrl}/oauth/msg/lifecycle - lifecycle events (reauthorizationRequired, subscriptionRemoved, missed)

Both must be reachable from Microsoft's servers over HTTPS. Microsoft validates the notification URL when the subscription is created. Notifications are the only way EmailEngine learns about changed and deleted messages. An account without a working subscription finds new messages only through the periodic recovery pass described below, or syncs nothing at all if its mailbox cannot be read. An instance behind a firewall or on a private network needs a reverse proxy or a tunnel.

A subscription is created when the account initializes, with the longest lifetime Graph allows for mail (4230 minutes, just under three days). An hourly pass renews it once less than 24 hours remain, and recreates it when it is missing or has expired. A creation or renewal that Graph refuses is retried three times, 30, 60 and 120 seconds apart. Since v2.80.0 the hourly pass then starts a new round of retries; earlier versions waited for a reconnect, so a tenant-side problem fixed an hour later went unnoticed.

Subscription failures​

Once the fast retries are spent, EmailEngine checks whether it can still list the mailbox. Since v2.82.2 that check decides what happens next:

  • The mailbox can be listed and the periodic recovery pass is on: the account stays connected, and new messages are found by that pass. Changes to and deletions of existing messages are not reported until a subscription works again. The failure stays visible in outlookSubscription.state (state: "error" and the reason in error), and the account page shows it as a warning.
  • The mailbox cannot be listed, or the periodic pass is turned off: the account reports a connectError with the code SubscriptionSetupError (since v2.80.0), syncs nothing, and its API operations answer HTTP 503.

The hourly pass repeats the check while the subscription keeps failing, so an account moves between the two as the tenant changes, and the connectError is lifted as soon as a subscription is created, renewed or found to be healthy, or the mailbox can be listed again. It is a connection error rather than an authentication error because the credential is not at fault: a tenant that has disabled the Exchange Online service principal (AADSTS500014) still refreshes tokens, and re-authorizing cannot help. Before v2.82.2 every subscription failure was reported as connectError, including one where the mailbox could still be read. A missing Service URL is not reported as an account error, since it applies to every account at once; since v2.82.2 it shows in outlookSubscription.state as Subscription failed: the Service URL is not set, where earlier versions only logged it. Before v2.80.0 such an account kept reporting connected while receiving nothing.

GET /v1/account/{account} reports the subscription as outlookSubscription: its id, its expirationDateTime, and a state object carrying state (creating, created, renewing or error), time, error, retryCount and createRetryCount.

Recovering Missed Notifications​

When Microsoft reports a missed lifecycle event, EmailEngine lists the messages received over the previous four hours, which is how long Graph retries a delivery before giving up, oldest first, and announces the ones it has not announced in the last six hours. Since v2.81.2 the request is stored before the webhook is answered, so it waits for the account's worker and is retried with backoff like any other failed change; Run sync on the account page and PUT /v1/account/{account}/sync queue the same recovery. Earlier versions (since v2.67.0) looked back only to the last notification they had processed, dropped a second request within five minutes, and lost the request when no worker held the account.

Graph can also drop a notification without sending a missed event, while other notifications for the same mailbox keep arriving. Since v2.82.1 every connected account with read access runs the same recovery on a timer, every 10 minutes by default, starting where the previous pass finished, so a pass that finds nothing is a single listing request. EENGINE_OUTLOOK_FALLBACK_POLL_INTERVAL sets the interval, and 0 turns the pass off. Before v2.82.1, a message whose notification Graph dropped was announced only if someone ran a sync within four hours of its arrival.

To look further back than the usual window, send since with PUT /v1/account/{account}/sync (since v2.82.1, Graph accounts only, at most 30 days back). Messages announced more than about six hours earlier are no longer remembered, so they are announced again with a new messageNew webhook.

A notification whose follow-up request fails with a 408, 429, 5xx or network error is deferred and retried after 1, 4, 16, 60, 60 and 60 minutes, surviving a restart (since v2.81.2). A message given up on after those attempts is reported as a syncWarning on the account change stream.

Sending and Uploading​

  • A message is sent through /sendMail as raw MIME, which ignores the from address. Set useStructuredFormat=true on the submit request to send as structured JSON, which honors from but does not preserve calendar invites and other special MIME parts; see Sending API.
  • Since v2.82.0, a reply or forward uploaded with POST /v1/account/{account}/message keeps its In-Reply-To and References headers, set through the MAPI properties Exchange builds them from. Earlier versions rejected the upload with InvalidInternetMessageHeader, because Graph accepts only x- prefixed names in internetMessageHeaders.

Performance Considerations​

IMAP/SMTP Limits​

Exchange Online caps the number of concurrent IMAP connections per mailbox and rate-limits SMTP submission; the current figures are in Microsoft's Exchange Online limits. Every sub-connection is one more IMAP connection against that cap, so enable them only for folders that need real-time notifications, and narrow the monitored folders on large mailboxes.

MS Graph API Limits​

Microsoft Graph throttles per app and per mailbox rather than by connection; see Microsoft Graph throttling limits. EmailEngine retries a throttled request with backoff on its own.

Learn more about performance tuning →

Production Considerations​

For single-tenant or organization apps, an admin can grant consent for all users:

  1. In Azure AD, go to your app registration
  2. Click API permissions
  3. Click Grant admin consent for [Organization]

This pre-approves the app for all users in the organization.

Token Management​

EmailEngine renews the access token with the stored refresh token whenever a connection or an API request needs one, and stores both tokens in Redis (encrypted when EENGINE_SECRET is set). When Microsoft rejects the refresh token, the account enters authenticationError and stays there until the user signs in again; EmailEngine cannot re-authorize on the user's behalf. A token endpoint that answers 408, 429 or a 5xx is treated as unavailable rather than as a refused credential: the attempt is retried and no authenticationError is sent (since v2.80.0). Deleting an account discards its tokens, so re-adding it later takes a new consent flow.

An account whose refresh keeps failing for longer than EENGINE_MAX_IMAP_AUTH_FAILURE_TIME (three days by default) is switched off (since v2.79.3 for OAuth2 accounts) and reports the unset state with a non-null authFailureDisabledAt. Re-authorizing it through the hosted authentication form or the Re-authenticate button on its page in the admin interface brings it back; see Accounts switched off after authentication failures. A shared mailbox added with delegated access is never switched off on its own: the failing credential is the main account's.

Which events invalidate a Microsoft refresh token, and how long an idle one lives, is covered on the OAuth2 token management page.

Client Secret Expiration

Microsoft OAuth2 client secrets expire; the Azure portal offers lifetimes of up to 24 months. When the secret expires, all accounts using that OAuth2 app fail at their next token refresh. Monitor expiration dates in Azure AD and rotate secrets before they expire.

Learn more about OAuth2 token management →

See Also​