Skip to content

Quickstart

Phase 1 needs only .NET 10 - no tenant, no Azure. Phase 2 connects to a real tenant. Phase 3 (Authentication) adds caller authentication.

  • .NET SDK 10 (download)
  • PowerShell 7+ (pwsh on PATH) - hosts Exchange Online in-process
  • ExchangeOnlineManagement 3.9.x (the documented baseline; 3.5.0 has a manifest bug):
    Terminal window
    pwsh -Command "Install-Module ExchangeOnlineManagement -MinimumVersion 3.9.0 -Scope CurrentUser"
  • git (for cloning + the docs submodule)
  • (Phase 2+) an Exchange Online tenant where you’re an admin

Phase 1: clone and run offline (about 2 minutes)

Section titled “Phase 1: clone and run offline (about 2 minutes)”

Boots the API in metadata-only mode - the service document, the OData CSDL, and the Explorer UI - without any tenant credentials.

Terminal window
git clone <repo-url> && cd exonaut
git submodule update --init --depth 1 # the cmdlet docs live in a submodule
dotnet build ExchangeApi.slnx
dotnet run --project src/ExchangeApi.Web
# → listening on http://localhost:5043

In Phase 1 the Explorer browses $metadata, but data endpoints error until a tenant is connected - that’s expected. On a dev laptop (the Development environment) callers don’t need a token (a permissive dev handler auto-grants scopes and logs a loud per-request warning). The host refuses to start in Production without real auth config, so that shortcut can’t leak into production.

Create an Entra app registration so the API can sign into EXO unattended, grant it Exchange.ManageAsApp (+ a directory role: Global Reader for read, Exchange Administrator for write), then store the settings in .NET user-secrets (never in source control):

Terminal window
dotnet user-secrets --project src/ExchangeApi.Web init
dotnet user-secrets --project src/ExchangeApi.Web set "ExchangeOnline:ConnectionMode" "ClientSecret"
dotnet user-secrets --project src/ExchangeApi.Web set "ExchangeOnline:Organization" "<tenant>.onmicrosoft.com"
dotnet user-secrets --project src/ExchangeApi.Web set "ExchangeOnline:TenantId" "<tenant-guid>"
dotnet user-secrets --project src/ExchangeApi.Web set "ExchangeOnline:AppId" "<app-id-guid>"
dotnet user-secrets --project src/ExchangeApi.Web set "ExchangeOnline:ClientSecret" "<secret-value>"

Restart, wait for Runspace pool warmed (first sign-in ~15s), then:

Terminal window
curl -s "http://localhost:5043/readyz"
curl -s "http://localhost:5043/exchange/v1.0/mailboxes?\$top=2&\$select=Id,RecipientTypeDetails" | jq