Fixing Email for Guest Users in D365 Business Central

If you manage any Dynamics 365 Business Central clients where users sign in as Entra B2B guests (aka M365 Guest Users) you've probably hit this issue: the built-in email connectors don't work properly for guest users.

The use of guest accounts in BC is becoming more common due to organisations with a multi-tenancy strategy or where companies are looking for very clear separation of data access & governance. The expectation is simply that users email will just work in BC guest or not!  

The Problem

Business Central (2026 wave 1) ships with three email connector options:

  • Current User (built-in) - works great, but only for accounts native to the BC host tenant. If you're a guest user with a cross-tenant identity, BC can't send mail for you.
  • M365 / Shared Mailbox - This is also dead-end for guest users, as they simply can't use or access other users or shared mailboxes in the tenancy where they are a guest.
  • SMTP - sends messages using good old SMTP - which is as old as the internet itself! This is one way to deal with guest users, but can't meet a modern per-user approach. 

The result: the scope for guest users to send email is quite narrow, only SMTP. A key requirement of modern connected system is clarity of identity when communicating with colleagues, customers or vendors. Being able to send as yourself is an important capability!  

The Solution - Current User Email API

The extension implements the same "Current User" pattern that BC uses natively - but via the Microsoft Graph API, which works regardless of whether you're a guest or a member.

Here's how it works:

  1. Admin sets up once - create an Entra app registration with Mail.Send delegated permission, enter the details in BC. That's it for admin.
  2. Each user consents once - open the "Connect Current User Email API" page, click a button, sign in with your work account in the popup, approve access. Done.
  3. Every email send works automatically - compose dialog, customer statements, scheduled reports, background jobs, ISV extensions. The connector resolves the correct Graph token for whoever is sending at that moment.
Email Setup Card with app registration details filled in...

There is no per-user admin action required. No routing rules. No account-per-user management. One email account is registered with BC's email framework and set as the default. At send time, the connector looks up the current user's stored OAuth token and calls Graph as that user.

The User Experience

At present the users must access a consent page to confirm their connectin, In the next iteration I will aim to move closer to the 'current user' type experience so the users do not need to carry out this step and the necessary token for access to send email is managed from memory only and not stored at all. Currently the consent page shows a simple "Connect my Email" button. A popup opens, the user signs in with their normal work account, approves access, and the popup closes automatically. The page updates to show they're connected.

1 click to connect to the email API 

How It Works Under the Hood

For those who want to know what's going on technically:

  • OAuth 2.0 Authorization Code + PKCE - the standard delegated flow. Each user authenticates as themselves and grants Mail.Send permission to the app.
  • Token storage in IsolatedStorage - per-user (DataScope::User), private to each user. No other user or background task running as a different identity can access it.
  • Silent token refresh - access tokens expire after about 60 minutes. The extension automatically refreshes them using the stored refresh token. Users don't need to re-consent unless they explicitly disconnect.
  • Graph API POST /v1.0/me/sendMail - the actual send. /me resolves to the authenticated user's mailbox, whether they're a guest or a member. The email arrives from their real work address.

The architecture is simple: one fixed-GUID email account in BC's email framework, one row per user in a token tracking table, and the Graph call resolves the right identity at runtime via UserSecurityId().

In the next version we will move to

Built with AI, Reviewed by a Human

The code was written by GitHub Copilot (Claude), with me prompting and testing. It's important to review AI generated code, especially for security. Arend-Jan Kauffmann reviewed the app and identified several important improvements around token security, storage encryption, and BC platform best practices. That feedback is driving the next phase of development.

What's Next

The current release is a working proof of concept - Phase 1 and 2 are complete. Phase 3 is scoped and includes:

  • Security hardening - SecretText parameters, [NonDebuggable] attributes, encrypted storage for the client secret, and removing unnecessary token persistence
  • System Application adoption - replacing raw HttpClient with BC's Rest Client module and evaluating the built-in OAuth2 codeunit
  • Multi-tenancy support - row-based setup for environments where guests come from multiple home tenancies that each need a separate Entra app registration
  • Email attachment support - critical for BC where almost every email includes a file. The extension will handle inline attachments for small files and Graph upload sessions for files up to 30MB+

Try It / Contribute

The extension is open source and available on GitHub:

github.com/andywingate/D365BC-guest-email-api

It's a per-tenant extension (PTE) - you can deploy it to any BC SaaS environment. The QUICKSTART guide walks through the full setup: Entra app registration, BC configuration, and user consent.

If you're hitting the guest email problem in your BC environment, give it a try. If you have feedback, ideas, or want to contribute - open an issue or a PR on GitHub. 

Resources

What do you think?

Connect or follow me on LinkedIn to get all my updates Andrew Wingate | LinkedIn