The outlook-cli watch command monitors your mailbox and calendar for changes using Microsoft Graph. Three notification modes are available:
| Mode | Latency | Setup | Best For |
|---|---|---|---|
--mode poll (default) |
30–60s | None | General monitoring |
--mode fast-poll |
5–60s adaptive | None | Quick response without external deps |
--mode webhook |
~1–5s push | Tunnel tool (cloudflared) | Gateway/agent integration |
# Default: poll mode (delta queries every 60 seconds)
outlook-cli watch --account my-account
# Fast-poll: adaptive 5–60 second polling (speeds up on activity)
outlook-cli watch --mode fast-poll --interval 10
# Webhook: near-instant push notifications via Microsoft Graph subscriptions
outlook-cli watch --mode webhook
# JSONL mode for piping to an AI agent
outlook-cli watch --mode webhook --jsonl | my-agent-process
# Watch mail only, specific folder
outlook-cli watch --no-calendar --folder "Important"
# Include heartbeat signals every 5 minutes
outlook-cli watch --heartbeat --heartbeat-interval 300| Question | Recommendation |
|---|---|
| Just monitoring for notifications? | poll (default) — 30-60s is fine for human attention |
| AI agent needs quick response? | fast-poll — no external dependencies, 5-10s response |
| Building a real-time gateway? | webhook — ~1-5 second push, but needs a tunnel |
| Running on a server with a public IP? | webhook — skip the tunnel, use --webhook-port |
| Multiple accounts simultaneously? | poll or fast-poll with staggered intervals to avoid rate limits |
| Limited API quota / shared tenant? | poll at 60s — fewest API calls |
Uses Microsoft Graph delta queries — an efficient pull-based approach that minimizes API calls. Delta queries only return items that changed since your last call, so even if you have 10,000 messages, each poll transfers only the new/changed ones.
- Initial sync: Fetches all existing items and establishes a baseline (no events emitted). This can take several seconds for large mailboxes but only happens once.
- Incremental sync: Each subsequent call returns only what changed since the last check. Microsoft Graph tracks the changes server-side using a delta token.
- Delta tokens: Sync state is persisted to
~/.outlook-cli/delta-{alias}.json, surviving restarts. When you restart the watcher, it picks up where it left off — no duplicate events.
Minimum interval: 30 seconds. Default: 60 seconds. Microsoft Graph rate limits apply — for personal accounts, you get ~10,000 API calls per 10 minutes across all applications.
Same delta query mechanism but with adaptive polling that automatically adjusts its frequency based on activity. This is the best mode for AI agent integration when you don't want to set up a tunnel:
- Starts at the configured interval (minimum 5 seconds)
- When changes are detected, drops to 5-second polling — catching follow-up messages almost immediately
- When no changes are found, backs off by 1.5× each cycle — e.g., 5s → 7.5s → 11s → 17s → 25s → 38s → max
- Caps at the configured
--intervalvalue (default 60s) - Resets to 5s immediately when any change is detected
This means: during an active email conversation, polling happens every 5 seconds. After the conversation stops, it gradually relaxes back to the configured maximum. This balances responsiveness with API efficiency.
# Adaptive polling: 5–30 second range
outlook-cli watch --mode fast-poll --interval 30
# Aggressive: 5–10 second range (for gateway use)
outlook-cli watch --mode fast-poll --interval 10Uses Microsoft Graph change notifications (push-based). Microsoft Graph POSTs to a webhook URL when changes occur, delivering notifications within ~1–5 seconds.
Since the CLI runs on your local machine (not a public server), a tunnel exposes the local webhook server to the internet:
Microsoft Graph ──POST──→ Tunnel (cloudflared) ──→ localhost:PORT ──→ outlook-cli
Supported tunnels:
| Tunnel | Install | Notes |
|---|---|---|
cloudflared (default) |
winget install Cloudflare.cloudflared |
Free, no signup for quick tunnels |
localtunnel |
npx localtunnel (auto-installed) |
NPM package, zero config |
ngrok |
winget install Ngrok.Ngrok |
Popular, optional free signup |
# Webhook with default tunnel (cloudflared)
outlook-cli watch --mode webhook
# Webhook with specific tunnel
outlook-cli watch --mode webhook --tunnel ngrok
# Webhook with custom port
outlook-cli watch --mode webhook --webhook-port 3000Reliability features:
- Auto-renews Graph subscriptions before expiry (~3 days for personal accounts)
- Runs delta query reconciliation every 5 minutes as safety net for missed notifications
- Graceful shutdown: deletes subscriptions, stops tunnel, closes server
| Event Type | Description |
|---|---|
mail.changed |
New or modified email message |
mail.removed |
Deleted email message |
calendar.changed |
New or modified calendar event |
calendar.removed |
Deleted calendar event |
sync.status |
Informational (initial sync, errors) |
heartbeat |
Periodic liveness signal |
Human-readable notifications with timestamps and icons:
[14:32:05] ℹ️ Watch started
[14:32:06] ℹ️ Mail baseline: 47 messages in Inbox
[14:32:07] ℹ️ Calendar baseline: 12 events in next 30 days
[14:32:07] ℹ️ Initial sync complete. Watching for changes...
[14:33:07] 📧 Mail from alice@example.com: "Meeting notes" (unread)
Hi everyone, here are the notes from today's...
[14:34:07] 📅 Calendar: "Team Standup" at 1/15/2024, 10:00:00 AM
📍 Conference Room B
One JSON object per line — designed for piping to AI agents:
outlook-cli watch --jsonl{"type":"sync.status","timestamp":"2024-01-15T14:32:05.123Z","data":{"message":"Watch started","account":"my-account","watching":{"mail":"Inbox","calendar":true},"interval":"60s"}}
{"type":"mail.changed","timestamp":"2024-01-15T14:33:07.456Z","data":{"id":"AAMk...","subject":"Meeting notes","from":"alice@example.com","receivedDateTime":"2024-01-15T14:33:00Z","isRead":false,"bodyPreview":"Hi everyone...","importance":"normal","hasAttachments":false}}
{"type":"calendar.changed","timestamp":"2024-01-15T14:34:07.789Z","data":{"id":"AAMk...","subject":"Team Standup","start":{"dateTime":"2024-01-15T10:00:00","timeZone":"Pacific Standard Time"},"end":{"dateTime":"2024-01-15T10:30:00","timeZone":"Pacific Standard Time"},"location":"Conference Room B","organizer":"bob@example.com"}}An AI agent can consume the JSONL stream to react to real-time changes:
# Start watch in background, pipe to agent
outlook-cli watch --jsonl --account work --heartbeat | process-events.shThe agent reads lines from stdin, parses each as JSON, and acts accordingly:
// Example agent event processing
process.stdin.on('data', (chunk) => {
for (const line of chunk.toString().split('\n').filter(Boolean)) {
const event = JSON.parse(line);
if (event.type === 'mail.changed' && !event.data.isRead) {
console.log(`New email from ${event.data.from}: ${event.data.subject}`);
// Trigger agent action...
}
}
});| Option | Default | Description |
|---|---|---|
--mode <mode> |
poll | Watch mode: poll, fast-poll, or webhook |
--interval <seconds> |
60 | Poll/fast-poll interval (min: 30 for poll, 5 for fast-poll) |
--tunnel <type> |
cloudflared | Tunnel for webhook mode: cloudflared, ngrok, localtunnel |
--webhook-port <port> |
random | Local port for webhook server |
--no-mail |
watch mail | Disable mail watching |
--no-calendar |
watch calendar | Disable calendar watching |
--folder <name> |
Inbox | Mail folder to watch |
--jsonl |
text | Output as JSON Lines |
--heartbeat |
off | Emit periodic heartbeat events |
--heartbeat-interval <s> |
300 | Heartbeat interval in seconds |
--account <alias> |
default | Account to watch |
--as <email> |
own mailbox | Watch a delegate mailbox |
Delta tokens are the backbone of the watch system. They allow the CLI to ask Microsoft Graph "what changed since I last checked?" instead of re-fetching everything. Tokens are stored per-account in ~/.outlook-cli/delta-{alias}.json:
{
"mail:Inbox": "https://graph.microsoft.com/v1.0/me/mailFolders/Inbox/messages/delta?$deltatoken=...",
"calendar:rolling30d": "https://graph.microsoft.com/v1.0/me/calendarView/delta?$deltatoken=..."
}- Persistence: Tokens survive process restarts — only new changes are reported. You can stop the watcher, restart your machine, and when you start watching again, it only reports changes that happened while you were away.
- Expiry: Tokens expire after ~30 days of inactivity. When a token expires, the Graph API returns a
410 Goneresponse, and the watcher automatically falls back to a full re-sync (establishing a new baseline and getting a fresh token). The initial sync is suppressed so you don't get a flood of old messages. - Reset: Delete the delta file (
~/.outlook-cli/delta-{alias}.json) to force a full re-sync. This is useful if you suspect the delta state is corrupted or out of sync. - Multiple watchers: Each account has its own delta file. You can watch multiple accounts simultaneously without conflicts. However, running two watchers for the same account simultaneously will cause delta token conflicts — the second watcher may miss events or report duplicates.
Press Ctrl+C to stop the watcher. It will:
- Complete any in-progress delta query
- Save delta tokens to disk
- Delete Graph webhook subscriptions (webhook mode)
- Stop tunnel process (webhook mode)
- Exit cleanly
For OpenClaw and NanoClaw integration, use the provided scripts in skill/openclaw/ and skill/nanoclaw/. These scripts automatically:
- Detect the best watch mode (webhook if tunnel available, fast-poll otherwise)
- Configure JSONL output for agent consumption
- Handle binary detection and account selection
See skill/openclaw/SKILL.md and skill/nanoclaw/SKILL.md for full setup instructions.
For advanced scenarios, you can define multiple watch jobs in a JSON configuration file. Each job has its own account, folder, interval, and rules:
{
"jobs": [
{
"name": "personal-inbox",
"account": "personal",
"folder": "Inbox",
"interval": 60,
"mode": "poll",
"rules": [
{
"condition": { "from": "*@company.com" },
"actions": ["mark-read", { "move": "Work" }]
}
]
},
{
"name": "work-urgent",
"account": "work",
"folder": "Inbox",
"interval": 15,
"mode": "fast-poll",
"rules": [
{
"condition": { "importance": "high", "isRead": false },
"actions": ["flag", { "run": "notify-send 'Urgent: ${subject}'" }]
}
]
}
]
}# Run multi-job watch
outlook-cli watch --config watches.json
# Validate config without running
outlook-cli watch --config watches.json --validateEach job runs independently with its own delta token and polling interval. Rules are evaluated on each change event, and matching actions are executed in order. The run action can execute arbitrary commands with variable substitution (${subject}, ${from}, ${id}).
The C# implementation supports poll and fast-poll modes. Webhook mode is not available in C# — when --mode webhook is specified, the C# binary displays a message directing the user to the Node.js implementation:
⚠️ Webhook mode is only available in the Node.js implementation.
Run: outlook-cli watch --mode webhook
(Requires Node.js runtime)
Falling back to fast-poll mode...
This is because the webhook infrastructure (tunnel management, HTTP server, subscription lifecycle) is complex and Node.js-specific. For gateway scenarios requiring webhook mode, use the Node.js runtime.