❄️
❄️
❄️
❄️
❄️
❄️
❄️
❄️
❄️
❄️
❄️
❄️
❄️
❄️
❄️
❄️
❄️
❄️
❄️
❄️
❄️
❄️
❄️
❄️
❄️
❄️
❄️
❄️
❄️
❄️
❄️
❄️
❄️
❄️
❄️
❄️
❄️
❄️
❄️
❄️
❄️
❄️
❄️
❄️
❄️
❄️
❄️
❄️
❄️
❄️

Microsoft Graph Webhook (Shared Mailbox)

Microsoft Graph Webhook (Shared Mailbox)

Microsoft Graph Webhook for a Shared Mailbox

Subscribe to message events in a shared mailbox so your service is notified when mail arrives, then process it with Semantic Kernel (SK).

When to use

  • Ingest emails for classification, extraction, triage, or automation via SK.
  • Need near‑real‑time notifications without polling.

Prerequisites

  • App registration with Application permissions:
    • Mail.ReadBasic.All (subjects/senders) or Mail.Read (full content)
    • Admin consent granted
  • Public HTTPS endpoint to receive notifications and handle validation
  • Access to the shared mailbox with app permissions
    • Scope access using Exchange Online Application Access Policies (recommended for least privilege)

For highly regulated data, consider includeResourceData to receive encrypted payloads and verify signatures. Otherwise, fetch the message after you get a notification.


Create the subscription (C# Graph SDK)

using Azure.Identity;
using Microsoft.Graph;
using Microsoft.Graph.Models;

var credential = new ClientSecretCredential(tenantId, clientId, clientSecret);
var graph = new GraphServiceClient(credential, new[] { "https://graph.microsoft.com/.default" });

var sharedMailbox = "shared-mailbox@contoso.com"; // UPN or objectId
var sub = new Subscription
{
    ChangeType = "created",
    Resource = $"/users/{sharedMailbox}/mailFolders('inbox')/messages",
    NotificationUrl = "https://yourapp.example.com/graph/webhook",
    ClientState = Guid.NewGuid().ToString("N"),
    ExpirationDateTime = DateTimeOffset.UtcNow.AddHours(12) // renew before expiry
};

var created = await graph.Subscriptions.PostAsync(sub);
Console.WriteLine($"Subscription: {created?.Id}, Expires: {created?.ExpirationDateTime}");
  • Resource options: /users/{id}/messages or a folder path, e.g., Inbox.
  • Typical renewal window for Outlook resources is limited; renew well before expiry.

Webhook validation and notifications (ASP.NET Core minimal API)

using Microsoft.AspNetCore.Mvc;
using Microsoft.Graph.Models;

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

// 1) Validation (Graph sends GET with validationToken)
app.MapGet("/graph/webhook", ([FromQuery] string validationToken) =>
{
    return Results.Content(validationToken, "text/plain");
});

// 2) Notifications (POST)
app.MapPost("/graph/webhook", async ([FromBody] ChangeNotificationCollection body, ILogger<Program> logger) =>
{
    foreach (var n in body.Value)
    {
        // Verify clientState
        if (n.ClientState != YourStore.LoadClientStateFor(n.SubscriptionId))
            return Results.Unauthorized();

        // Fetch the message (resourceData contains IDs)
        var (userId, messageId) = (n.ResourceData?.AdditionalData?["userId"]?.ToString(),
                                   n.ResourceData?.Id);

        await YourQueue.EnqueueAsync(new FetchRequest(userId!, messageId!));
    }
    return Results.Ok();
});

app.Run();
  • Respond to validation within 10s, echoing validationToken as text/plain.
  • For non-includeResourceData subscriptions, Graph may not populate IDs in resourceData; if so, parse from n.Resource or switch to resource data mode. Prefer queueing and processing asynchronously.

Renewals

var updated = await graph.Subscriptions[created!.Id].PatchAsync(new Subscription
{
    ExpirationDateTime = DateTimeOffset.UtcNow.AddHours(12)
});
  • Run a timer/background job (see Timer Trigger) to renew before expiry.
  • Store subscription IDs securely; handle re-creation on errors.

Security and resilience

  • Least privilege: use Mail.ReadBasic.All if sufficient; otherwise Mail.Read.
  • Scope mailbox access with Exchange Application Access Policies.
  • Validate clientState and use HTTPS; avoid relying solely on IP allow-lists.
  • Idempotency: dedupe notifications by subscriptionId + resource + changeType + time.
  • Backpressure: immediately ack POST and process via a queue with limited concurrency.

Integrating with SK

  • After fetching the message with Graph, call an SK function/plugin to classify, route, or extract.
  • Combine with the Webhook Trigger pattern and Timer Trigger for renewals.

Pros / Cons

  • Pros: Near real-time, efficient, integrates cleanly with SK pipelines.
  • Cons: Subscription lifecycle/renewals; validation and security hardening required.