Skip to content

@memberjunction/communication-ms-graph

Microsoft Graph (Office 365 / Exchange Online) provider for the MemberJunction Communication Framework. This provider enables full mailbox operations — sending, receiving, searching, managing folders, attachments, drafts, and more — through the Microsoft Graph API with Azure AD application authentication.

graph TD
    subgraph msgraph["@memberjunction/communication-ms-graph"]
        MSP["MSGraphProvider"]
        AUTH["Auth Module\n(ClientSecretCredential)"]
        CFG["Config Module\n(Environment Variables)"]
        CRED["MSGraphCredentials"]
    end

    subgraph azure["Azure / Microsoft"]
        AAD["Azure AD\n(OAuth2 Client Credentials)"]
        GRAPH["Microsoft Graph API\n(/users/email/...)"]
        MAIL["Exchange Online\nMailbox"]
    end

    subgraph base["@memberjunction/communication-types"]
        BCP["BaseCommunicationProvider"]
    end

    BCP --> MSP
    MSP --> AUTH
    MSP --> CFG
    MSP --> CRED
    AUTH --> AAD
    MSP --> GRAPH
    GRAPH --> MAIL

    style msgraph fill:#2d6a9f,stroke:#1a4971,color:#fff
    style azure fill:#7c5295,stroke:#563a6b,color:#fff
    style base fill:#2d8659,stroke:#1a5c3a,color:#fff
Terminal window
npm install @memberjunction/communication-ms-graph

Set the following environment variables:

AZURE_TENANT_ID=your-azure-tenant-id
AZURE_CLIENT_ID=your-azure-app-client-id
AZURE_CLIENT_SECRET=your-azure-app-client-secret
AZURE_ACCOUNT_EMAIL=mailbox@yourdomain.com

Required Azure AD App Permissions (Application type)

Section titled “Required Azure AD App Permissions (Application type)”
PermissionOperations
Mail.SendSendSingleMessage, ForwardMessage, ReplyToMessage
Mail.ReadGetMessages, GetSingleMessage, SearchMessages, ListFolders, ListAttachments, DownloadAttachment
Mail.ReadWriteCreateDraft, DeleteMessage, MoveMessage, MarkAsRead, ArchiveMessage
Calendars.ReadGetEvents
User.Read.AllGetServiceAccount (user lookup, optional)

Calendars.Read is sufficient for GetEvents; Calendars.ReadWrite is not required, since nothing here creates or modifies an event. As Application permissions these are granted against the tenant, not against one mailbox — narrowing an app to specific mailboxes is done in Exchange with RBAC for Applications, not by these grants.

This provider supports all 15 operations defined in BaseCommunicationProvider:

OperationDescription
SendSingleMessageSend email via Graph API
GetMessagesRetrieve messages with filtering and header extraction
GetSingleMessageRetrieve a single message by ID
ForwardMessageForward email to new recipients
ReplyToMessageReply to an existing email thread
CreateDraftCreate a draft message in the mailbox
DeleteMessageMove to Deleted Items or permanently delete
MoveMessageMove message to a different mail folder
ListFoldersList mail folders with optional message counts
MarkAsReadMark messages as read or unread (batch)
ArchiveMessageMove message to Archive folder
SearchMessagesFull-text search with KQL syntax and date filtering
ListAttachmentsList attachments on a message
DownloadAttachmentDownload attachment content as base64/Buffer
GetEventsRead calendar events for one mailbox
import { CommunicationEngine } from '@memberjunction/communication-engine';
import { Message } from '@memberjunction/communication-types';
const engine = CommunicationEngine.Instance;
await engine.Config(false, contextUser);
const message = new Message();
message.From = 'user@yourdomain.com';
message.To = 'recipient@example.com';
message.Subject = 'Hello from MS Graph';
message.HTMLBody = '<h1>Hello</h1><p>Sent via Microsoft Graph.</p>';
message.ContextData = { saveToSentItems: true };
const result = await engine.SendSingleMessage(
'Microsoft Graph',
'Standard Email',
message
);

Override credentials on a per-request basis for multi-tenant scenarios:

import { MSGraphCredentials } from '@memberjunction/communication-ms-graph';
const result = await engine.SendSingleMessage(
'Microsoft Graph',
'Standard Email',
message,
undefined,
false,
{
tenantId: 'customer-tenant-id',
clientId: 'customer-app-id',
clientSecret: 'customer-secret',
accountEmail: 'user@customer.com'
} as MSGraphCredentials
);
const provider = engine.GetProvider('Microsoft Graph');
const result = await provider.GetMessages({
NumMessages: 10,
UnreadOnly: true,
IncludeHeaders: true,
ContextData: {
ReturnAsPlainText: true,
MarkAsRead: true
}
});
result.Messages.forEach(msg => {
console.log(`${msg.From}: ${msg.Subject}`);
console.log(`Thread: ${msg.ThreadID}`);
});

Requires Calendars.Read. Whether you pass a window changes what comes back, so GetEvents reports which it did via RecurrenceExpanded rather than leaving you to guess:

const provider = engine.GetProvider('Microsoft Graph');
// With a window -> /calendarView: a recurring series is EXPANDED into one entry per occurrence.
const occurrences = await provider.GetEvents({
Identifier: 'rep@example.com',
NumEvents: 50,
StartDateTime: new Date('2026-09-01T00:00:00Z'),
EndDateTime: new Date('2026-09-08T00:00:00Z')
});
console.log(occurrences.RecurrenceExpanded); // true
// Without one -> /events: a weekly stand-up is ONE row, the series master, whose start time is
// whenever the series began — possibly years ago.
const masters = await provider.GetEvents({ Identifier: 'rep@example.com', NumEvents: 50 });
console.log(masters.RecurrenceExpanded); // false

Three things worth knowing before syncing on this:

  • The window selects overlap, not start times. An event that began before StartDateTime and is still running when the window opens is returned. An event straddling a boundary therefore appears in both adjacent windows — dedupe on the event id.
  • NumEvents is one page. It becomes $top; there is no @odata.nextLink following. To cover a period completely, narrow the window rather than raise the number.
  • Cancelled events are excluded by default and cannot be recovered after the fact — Graph does not return them once filtered. Pass IncludeCancelled: true if you are logging history.

MS Graph supports KQL (Keyword Query Language) for search:

const result = await provider.SearchMessages({
Query: 'invoice',
FromDate: new Date('2025-01-01'),
MaxResults: 25,
FolderID: 'inbox-folder-id'
});
const result = await engine.CreateDraft(message, 'Microsoft Graph', contextUser);
if (result.Success) {
console.log(`Draft ID: ${result.DraftID}`);
}
// List top-level folders
const folders = await provider.ListFolders({ IncludeCounts: true });
folders.Folders.forEach(f => {
console.log(`${f.Name}: ${f.MessageCount} total, ${f.UnreadCount} unread`);
});
// List subfolders
const subfolders = await provider.ListFolders({
ParentFolderID: 'parent-folder-id',
IncludeCounts: true
});
// Move a message
await provider.MoveMessage({
MessageID: 'msg-id',
DestinationFolderID: 'folder-id'
});
// Archive a message
await provider.ArchiveMessage({ MessageID: 'msg-id' });
const attachments = await provider.ListAttachments({ MessageID: 'msg-id' });
for (const att of attachments.Attachments) {
const download = await provider.DownloadAttachment({
MessageID: 'msg-id',
AttachmentID: att.ID
});
// download.Content is a Buffer
// download.ContentBase64 is the raw base64 string
}

The provider caches Microsoft Graph Client instances per credential set for performance. Environment credential clients are shared across all calls; per-request credential clients are cached by tenantId:clientId.

Exchange Display NameSystemFolderType
Inboxinbox
Sent Itemssent
Draftsdrafts
Deleted Itemstrash
Junk Emailspam
Archivearchive
Other foldersother

When ReturnAsPlainText is set in ContextData, the provider uses the html-to-text library to convert HTML email bodies to plain text with 130-character word wrap.

PackagePurpose
@memberjunction/communication-typesBase provider class and type definitions
@memberjunction/coreLogging utilities
@memberjunction/globalRegisterClass decorator
@microsoft/microsoft-graph-clientMicrosoft Graph SDK
@azure/identityAzure AD ClientSecretCredential
html-to-textHTML to plain text conversion
Terminal window
npm run build # Compile TypeScript
npm run clean # Remove dist directory