Skip to main content

IMAP access to an Office 365 (Exchange Online) mailbox

This tutorial explains steps necessary to connect to an Office 365 mailbox by IMAP protocol.

Microsoft has deprecated basic authentication access to Exchange Online. Consequently, OAuth2 authentication is now required. This scenario pertains to background applications (or daemons) that do not require user consent to access the mailbox and do not have a web interface. These applications are referred to as service principals. We employ the Client Credentials Grant Flow in OAuth2, facilitated by the Microsoft.Identity.Client library available through NuGet.

Microsoft Entra portal setup

Go to Microsoft Entra portal, to the section Applications > App registrations.

Click "New registration". App registrations

In the dialog, you can choose whatever name for your aplication (1). If you need to connect to a mailbox inside your company, you select "single tenant" in (2). Since we are dealing with a background application, there are no redirect URIs (3), and thus, this field will remain empty. App registration dialog

Navigate to the new application details page and take note of the Application (Client) ID, Object ID, and Directory (Tenant) ID. These details will be required in the code later. App registration detail

Go to API permissions section of your app. Add a permission IMAP.AccessAsApp (1) and then grant admin access (2). API Permissions setup

Powershell setup

To grant access to the desired mailbox for our service account, you need the assistance of an Office 365 administrator, who should perform this action from a Windows computer. We use the "old" Windows PowerShell (x64), as attempts with the x86 version of Windows PowerShell, PowerShell 7, or other operating systems were unsuccessful.

You need to start Powershell as administrator.

First, install the required modules by responding 'Y' (Yes) to prompts regarding the installation of the NuGet provider and trusting the repository, as it is an official Microsoft repository:

PS> Install-Module AzureAD
PS> Install-Module ExchangeOnlineManagement

Next, we log in to Azure. This action opens a webpage requesting consent. You will need to make note of the TenantId from the output for later use.

PS> Import-Module AzureAD
PS> Connect-AzureAD

Account Environment TenantId TenantDomain AccountType
------- ----------- -------- ------------- -----------
admin@example.com AzureCloud 665ds.... example.com User

Next, we connect to Exchange Online.

# we need to temporarily allow execution policy of Powershell scripts, you can disable it after connecting to Exchange online
PS> Set-ExecutionPolicy -ExecutionPolicy RemoteSigned
Execution Policy Change
The execution policy helps protect you from scripts that you do not trust. Changing the execution policy might expose
you to the security risks described in the about_Execution_Policies help topic at
https:/go.microsoft.com/fwlink/?LinkID=135170. Do you want to change the execution policy?
[Y] Yes [A] Yes to All [N] No [L] No to All [S] Suspend [?] Help (default is "N"): Y

PS> Import-Module ExchangeOnlineManagement

# this will again display a webpage with consent
PS> Connect-ExchangeOnline

# set the execution policy back
PS> Set-ExecutionPolicy -ExecutionPolicy Restricted

Now we grant access to a desired mailbox.

# DisplayName can be whatever
PS> New-ServicePrincipal -AppId <our app id> -ServiceId <our object id> -DisplayName "<Your Application Service principal>"

PS> Add-MailboxPermission -Identity "<mailbox>" -User <our object id> -AccessRights FullAccess

PS> $startDate = Get-Date
PS> $endDate = $startDate.AddMonths(12)
PS> New-AzureADApplicationPasswordCredential -ObjectId <our object id> -CustomKeyIdentifier "IMAP Secret" -StartDate $startDate -EndDate $endDate

CustomKeyIdentifier : {12, ...}
EndDate : 07.10.2025 14:33:27
KeyId :
StartDate : 07.10.2024 14:33:27
Value : 2TV5UG...

Here, the Value is what we need to use in the code as client secret.

C# Code

We need to add the NuGet packages MailKit and Microsoft.Identity.Client to the project.

using MailKit;
using MailKit.Net.Imap;
using MailKit.Search;
using MailKit.Security;
using Microsoft.Identity.Client;

const string tenantId = "881...";
const string clientId = "5bc....";
const string clientSecret = "2TV5UG...";

using (var client = new ImapClient()) {
client.Connect ("outlook.office365.com", 993, SecureSocketOptions.SslOnConnect);

IConfidentialClientApplication confidentialClientApplication = ConfidentialClientApplicationBuilder
.Create(clientId)
.WithClientSecret(clientSecret)
.WithAuthority(new Uri("https://login.microsoftonline.com/" + tenantId + "/v2.0"))
.Build();

AuthenticationResult authToken = await confidentialClientApplication
.AcquireTokenForClient(["https://outlook.office365.com/.default"])
.ExecuteAsync();

SaslMechanismOAuth2 oauth = new SaslMechanismOAuth2("your_mailbox@your_company.com", authToken.AccessToken);
client.Authenticate(oauth);

// example how to read 10 messages from Inbox folder

client.Inbox.Open(FolderAccess.ReadWrite); // or FolderAccess.ReadOnly
var uids = client.Inbox.Search(SearchQuery.All);

foreach (var uid in uids.Take(10)) {
var message = client.Inbox.GetMessage(uid);

Console.WriteLine(message.Subject);
}

client.Disconnect (true);
}

Resources