Using Graph API to access Office 365 mailbox in non-interactive case

Office 365 email service will be gradually revoking IMAP access with login/password authentication. You’ll need to use IMAP via OAuth 2.0 instead.

MailBee.NET Objects, email library for .NET platform, allows for accessing Office 365 accounts via IMAP with OAuth 2.0 used. However, it will only work in an interactive scenario when the user has to explicitly grant consent for your app to access their mailbox. The user will see Office 365 popup in the browser asking for consent and must click Agree.

In a non-interactive case, you would need to access mailbox using credentials of an application in Azure portal rather than user’s credentials – and currently, Microsoft allows that via their proprietary Graph API only, not IMAP.

To create an app registration in Azure, see OAuth 2.0 for Office 365 Accounts (installed applications) article or OAuth 2.0 with IMAP/SMTP for Office 365 in ASP.NET Core 3.1 MVC applications version for web apps. The only difference would be that these articles describe how to set permissions for IMAP (IMAP.AccessAsUser.All permission). But we’ll set permissions for Graph instead. Your app in Azure portal must have Mail.Read and User.Read.All enabled in Application Permissions (NOT Delegated Permissions). Click the picture to enlarge:

Let’s suppose we need to download all the email messages from the particular user account. The idea of the following sample is to use Graph to download email source from the server, feed it to MailBee.NET library and then use its MailMessage object the same way as you do when getting email from IMAP – for instance, to save a message as .EML file, or to display message headers or attachments when looping through all the messages found in the account.

To build this sample, you’ll need to add Microsoft.Graph, Microsoft.Graph.Auth and MailBee.NET packages in Nuget.

using System;
using System.Threading.Tasks;
using Microsoft.Identity.Client;
using System.Collections.Generic;
using Microsoft.Graph;
using Microsoft.Graph.Auth;
using MailBee.Mime;

namespace MsGraphWithMailBeeConsoleApp
{
	class Program
	{
		private static string Tenant = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"; // Directory (tenant) ID, from Azure app.
		private static string Instance = "https://login.microsoftonline.com/";
		private static string ClientId = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx"; // Application (client) ID, from Azure app.
		private static string ClientSecret = "secret"; // You create one in "Certificates & secrets" of Azure app registration.
		private static string email = "user@some.office365.domain";

		static async Task Main(string[] args)
		{
			// Confidential app does not require consent from the user. With public apps (which require consent) we can use IMAP but
			// with confidential apps Graph is the only option.

			// Your app in Azure portal must have Mail.Read and User.Read.All enabled in Application Permissions (not Delegated Permissions).
			IConfidentialClientApplication app = ConfidentialClientApplicationBuilder.Create(ClientId)
		   .WithTenantId(Tenant)
		   .WithClientSecret(ClientSecret)
		   .Build();

			List scopes = new List();
			scopes.Add(".default");

			AuthenticationResult result = null;

			result = await app.AcquireTokenForClient(scopes).ExecuteAsync();

			ClientCredentialProvider authProvider = new ClientCredentialProvider(app);
			GraphServiceClient graphClient = new GraphServiceClient(authProvider);

			// Profile is not needed for this sample but can be useful for your app.
			// Note that we cannot use Me here because there is no signed-in user ("Me" user).
			var profile = await graphClient.Users[email].Request().GetAsync();

			// We get IDs of messages here.
			var messages = await graphClient.Users[email].Messages
				.Request()
				.Select("sender,subject") // You can remove this line, the sample does not need it but it shows how to request extra info with messages.
				.GetAsync();

			if (messages.Count == 0)
			{
				Console.WriteLine("Mailbox is empty");
			}
			else
			{
				// Get raw data of the most recent message in the email account and save it to disk
				// (note that in Graph messages are sorted from newer to older by default, contrary to IMAP).
				MailMessage msg = new MailMessage();
				using (var msgStream = await graphClient.Users[email].Messages[messages[0].Id].Content.Request().GetAsync())
				{
					msg.LoadMessage(msgStream);
					msg.SaveMessage(@"C:\Temp\msg.eml");
				}

				// Print some info on the message.
				Console.WriteLine(msg.From.ToString());
				Console.WriteLine(msg.Subject);
				Console.WriteLine(msg.Date);
				foreach (MailBee.Mime.Attachment att in msg.Attachments)
				{
					Console.WriteLine(att.Filename);
				}
			}
		}
	}
}

Using Graph API to access Office 365 mailbox in non-interactive case

One thought on “Using Graph API to access Office 365 mailbox in non-interactive case

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s