This document describes the architecture of outlook-cli, covering the shared configuration format, the Node.js and .NET implementations, the security model, and AI agent integration.
outlook-cli has two independent implementations that produce identical CLI behavior:
| Node.js | .NET | |
|---|---|---|
| Language | JavaScript (ESM) | C# (.NET 8) |
| CLI framework | Commander.js | System.CommandLine (beta4) |
| Auth library | @azure/msal-node | Microsoft.Identity.Client (MSAL.NET) |
| Graph calls | Direct HTTP (fetch) | Direct HTTP (HttpClient) |
| Test runner | vitest | Integration tests via vitest |
| AOT | N/A (interpreted) | NativeAOT (~10 MB binary) |
Both implementations share all configuration files and the encrypted token cache. You can authenticate with one and use the other.
All configuration is stored in ~/.outlook-cli/.
Defines accounts, their Azure app registrations, and per-account permissions:
{
"defaults": {
"permissions": {
"allowed": ["User.Read", "Mail.Read", "Mail.ReadWrite", "Calendars.Read", "Calendars.ReadWrite", "offline_access"],
"forbidden": ["Mail.Send"]
}
},
"accounts": {
"personal": {
"clientId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"tenant": "common",
"parent": null
},
"work": {
"clientId": "yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy",
"tenant": "contoso.onmicrosoft.com",
"parent": "personal",
"permissions": {
"send_to": ["team@contoso.com", "boss@contoso.com"]
}
}
},
"default": "personal"
}Key concepts:
- defaults block — Shared permission settings inherited by all accounts
- parent chains — An account can inherit settings from a parent account
- per-account permissions — Override allowed/forbidden scopes and send_to whitelists
Maps short names to email addresses:
{
"fred": "freddie@outlook.com",
"wilma": "wilma@outlook.com",
"boss": "jane.smith@contoso.com"
}Aliases can be used anywhere an email address is expected (e.g., --to fred, --as fred).
- Format: AES-256-GCM encrypted binary
- Key derivation: PBKDF2 with SHA-512, 310,000 iterations
- Passphrase source:
OUTLOOK_CLI_PASSPHRASEenvironment variable, or machine-derived default - Files:
token-cache-<account>.bin - Interoperable: Both Node.js and .NET read/write the same format
Watch mode stores delta tokens for change tracking:
- Files:
delta-<alias>.json - Content: Microsoft Graph delta link URLs for resuming change queries
Both implementations call the Microsoft Graph REST API v1.0 directly via HTTP — no SDK wrapper.
| Feature | Method | Endpoint |
|---|---|---|
| Inbox | GET | /me/mailFolders/inbox/messages |
| Read message | GET | /me/messages/{id} |
| Search | GET | /me/messages?$search=... |
| Create draft | POST | /me/messages |
| Reply | POST | /me/messages/{id}/createReply |
| Reply all | POST | /me/messages/{id}/createReplyAll |
| Forward | POST | /me/messages/{id}/createForward |
| Send | POST | /me/messages/{id}/send |
| Move | POST | /me/messages/{id}/move |
| Flag | PATCH | /me/messages/{id} |
| Mark read | PATCH | /me/messages/{id} |
| Mail folders | GET | /me/mailFolders |
| Calendar view | GET | /me/calendarView |
| Event details | GET | /me/events/{id} |
| Create event | POST | /me/events |
| List calendars | GET | /me/calendars |
| Contacts | GET | /me/people?$search=... |
| Delta (mail) | GET | /me/mailFolders/inbox/messages/delta |
| Delta (calendar) | GET | /me/calendarView/delta |
For delegate access (--as), the /me/ prefix is replaced with /users/{userId}/.
Both implementations use a render dispatcher pattern:
Command result → Render dispatcher → Format module → stdout/file
├── text.js / Text.cs
├── json.js / Json.cs
├── markdown.js / Markdown.cs
└── html.js / Html.cs
The --format flag (or --json shorthand) selects the output module. The --output flag redirects to a file.
See SECURITY.md for the full threat model. Key architectural elements:
Each account in accounts.json can define:
- allowed — Scopes the CLI will request during authentication
- forbidden — Scopes that must NOT appear in the token (enforced at runtime)
- send_to — Whitelist of recipient email addresses for send operations
Even if the Azure app registration is misconfigured, the CLI inspects the acquired token and rejects it if:
- It contains a forbidden scope
- A send operation targets a recipient not in the send_to whitelist (when configured)
All write operations (draft, reply, forward, send, move, flag, create event) require interactive confirmation. The --yes flag bypasses prompts for scripting and agent use.
- Module system: ESM (
"type": "module"inpackage.json) - CLI: Commander.js defines commands and options declaratively
- Auth:
@azure/msal-nodehandles OAuth 2.0 authorization code flow, device code flow, and silent token refresh - HTTP: Native
fetch()(Node.js 20+) for Graph API calls - Testing: vitest with mock-based unit tests
See src-node.md for build and development details.
- Framework: .NET 8 with NativeAOT compilation
- CLI: System.CommandLine (2.0.0-beta4) for command parsing
- Auth: MSAL.NET (
Microsoft.Identity.Client) for OAuth 2.0 flows - HTTP:
HttpClientfor Graph API calls - Serialization: All JSON handled via source generators (
OutlookCliJsonContext) — no reflection at runtime - Graph client design:
GraphClientaccepts pre-serializedstring? bodyJsonparameters rather than generic objects, ensuring AOT compatibility - Binary size: ~10 MB self-contained, no .NET runtime required on target
See src-dotnet.md for build and publish details.
Watch mode uses Microsoft Graph delta queries for efficient change tracking:
- First call returns all current items + a delta token
- Subsequent calls send the delta token and receive only changes since the last call
- Delta tokens are persisted in
~/.outlook-cli/delta-<alias>.json
The --config flag accepts a JSON file defining rules for processing incoming changes:
{
"jobs": [
{
"account": "work",
"folder": "Inbox",
"rules": [
{
"match": { "from": "*@contoso.com" },
"action": { "type": "webhook", "url": "https://..." }
}
]
}
]
}See WATCH-MODE.md for full documentation.
outlook-cli integrates with OpenClaw and NanoClaw AI agent frameworks in two modes:
The agent invokes CLI commands and parses structured JSON output:
Agent → outlook-cli mail inbox --json → JSON response → Agent processes
The skill/ directory contains tool definitions that describe available commands, parameters, and response formats for the agent framework.
Watch mode acts as a channel factory — it normalizes incoming emails into channel messages and delivers them to the agent:
Graph delta → Watch rules engine → Normalize to channel message
├── stdout (JSONL)
├── webhook (HTTP POST)
├── exec (spawn process)
└── file (append to file)
This allows the agent to receive a stream of normalized events without polling.
For enterprise environments where Microsoft Graph API is blocked or unavailable (e.g., Exchange on-premises with no Graph API access, or corporate policies that prevent Azure App Registrations), outlook-cli can route requests through a COM Bridge server running on a Windows machine with Outlook Desktop installed.
The .NET implementation supports two providers — the Graph API (default) and the COM Bridge:
┌───────────────────────────────────────────────────────────────┐
│ outlook-cli │
│ │
│ ProviderFactory → provider = "graph"? │
│ ├── Yes → GraphMailProvider → Graph API │
│ └── No → BridgeMailProvider → HTTP+HMAC │
│ │ │
│ ┌─────────────────────▼────────┐ │
│ │ COM Bridge Server │ │
│ │ (separate Windows host) │ │
│ │ ASP.NET Core + COM interop│ │
│ │ → Outlook Desktop (COM) │ │
│ └──────────────────────────────┘ │
└───────────────────────────────────────────────────────────────┘
| Provider | Use Case | Auth | Transport |
|---|---|---|---|
| Graph API (default) | Cloud-connected, standard M365 environments | OAuth 2.0 via MSAL | HTTPS to graph.microsoft.com |
| COM Bridge | Enterprise with Graph API blocked, on-premises Exchange | HMAC-SHA256 shared secret | HTTP to bridge server |
Set provider: "bridge" in accounts.json to route an account through the COM Bridge:
{
"accounts": {
"work-bridge": {
"clientId": "not-used-for-bridge",
"provider": "bridge",
"bridgeUrl": "http://bridge-machine:9080",
"bridgeSecret": "your-shared-hmac-secret"
},
"shared-mailbox": {
"clientId": "not-used-for-bridge",
"provider": "bridge",
"bridgeUrl": "http://bridge-machine:9080",
"bridgeSecret": "your-shared-hmac-secret",
"bridgeAccount": "shared"
}
}
}The bridgeAccount field maps to an account entry in the bridge server's bridge.json, routing requests to a specific Outlook store (mailbox). Omit it to use the default store.
- Bridge server:
src/bridge/— C# ASP.NET Core, Windows-only. Wraps Outlook Desktop via COM automation (OutlookWrapper.cs). Not NativeAOT (COM requires reflection). - CLI providers:
src/dotnet/Providers/BridgeMailProvider.cs,BridgeCalendarProvider.cs,BridgeContactsProvider.cs— HTTP clients that call the bridge server with HMAC-signed requests. - Provider routing:
src/dotnet/Providers/ProviderFactory.cs— readsproviderfield from account config and creates the appropriate provider. - Security:
src/bridge/Security/BridgeSecurity.cs— HMAC-SHA256 request signing, nonce-based replay protection (60s window), read-only mode support.
| Feature | Graph API | COM Bridge | Notes |
|---|---|---|---|
| Mail list/read/search | ✅ | ✅ | Bridge uses Outlook's MAPI search |
| Mail draft/send/reply/forward | ✅ | ✅ | Bridge validates send controls |
| Mail move/flag/mark-read/delete | ✅ | ✅ | |
| Mail folders | ✅ | ✅ | |
| Calendar events/create/delete | ✅ | ✅ | |
| Calendar list calendars | ✅ | Bridge returns hardcoded "default" — COM has no equivalent | |
| Contacts search | ✅ | ✅ | Bridge searches GAL via CreateRecipient+Resolve and AddressEntries iteration, plus personal contacts folder. Graph uses People API. |
Delegate access (--as) |
✅ | ❌ | Not supported via COM — use multi-account instead |
| Multi-account (store routing) | ✅ | ✅ | Bridge routes via ?account= param to specific Outlook stores |
| Watch mode (delta queries) | ✅ | ❌ | Bridge uses poll-based checking |
| Attachments | ✅ | ✅ |
See self-hosting.md for deployment instructions.
- src-node.md — Node.js build and development
- src-dotnet.md — .NET build and publish
- src-tests.md — Test architecture
- SECURITY.md — Full security model
- WATCH-MODE.md — Watch mode and rules engine
- MULTI-ACCOUNT.md — Multi-account configuration
- DIRECTORY-STRUCTURE.md — Project directory layout