Command Inbox
Overview

Architecture

System design, data flows, and component boundaries

High-level diagram

Design principles

Corsair cache first

Thread list reads and most inbox actions go through Corsair's Postgres cache, not live Gmail API calls on every keystroke. This keeps navigation snappy and reduces Google API quota usage.

Webhooks keep the cache fresh. When a new message arrives, Gmail push notifies Pub/Sub, Corsair verifies and processes the event, and the app re-classifies the affected thread.

Server-only secrets

Environment validation lives in src/lib/env.ts with the server-only package. Client components must never import server env modules — use public env vars (NEXT_PUBLIC_*) or API routes instead.

In-process MCP, not a separate server

The agent embeds Corsair MCP tools in-process via @corsair-dev/mcp. There is no standalone MCP HTTP server. Tools are built per-request for the authenticated user's tenant.

OpenAI-first AI with fallback

Chat, classification, drafts, and embeddings use a provider chain: OpenAI first, Gemini on rate-limit or quota errors. Embeddings track embedding_provider per classification row so semantic search stays consistent when you switch providers.

Primary data flows

1. Inbound email (webhook path)

Gmail INBOX change
  → Google Pub/Sub push
  → POST /api/webhooks?tenantId={userId}
  → Corsair processWebhook (signature verify)
  → handleGmailMessageChanged (async)
  → Fetch thread from Corsair cache
  → AI classify + embed
  → Upsert classifications row
  → Pusher event → browser refreshes lane

2. Inbox read (user opens app)

GET /inbox (RSC)
  → Session check
  → Query Corsair cache for thread list
  → Join classifications + snoozes + meetings
  → Render lanes with cached metadata

If indexing is incomplete, an activity banner shows progress: first ~50 threads for lanes, then background full INBOX indexing for semantic search.

3. Hero workflow (M)

User presses M
  → POST /api/inbox/meeting
  → Read scheduling intent from classification
  → Fetch calendar free/busy via Corsair GCal plugin
  → User picks slot in availability UI
  → Create calendar event + Meet link
  → Generate confirmation draft (AI or template fallback)
  → Store thread_meetings link

4. Semantic search (/)

User types query
  → POST /api/search { query, provider }
  → Embed query with selected provider
  → pgvector cosine search on classifications.embedding
  → Filter by userId + embedding_provider
  → Return ranked SearchHit[]

5. Agent chat

User sends message
  → POST /api/agent/chat (streaming)
  → createInboxAgentStream() with buildAgentMcpTools(tenant)
  → Vercel AI SDK streamText with typed + discovery tools
  → send_email / create_calendar_invite pause for approval (needsApproval: true)
  → User approves → tool executes → stream continues

Security boundaries

LayerMechanism
User API routesBetter Auth session cookie
Inbox mutationsSession + Gmail/Calendar fully connected
WebhooksCorsair x-corsair-signature verification
CronAuthorization: Bearer $CRON_SECRET
OAuth tokensEncrypted at rest by Corsair with CORSAIR_KEK

Deployment topology

ComponentHost
Next.js appVercel (command-inbox.sayantanbal.in)
DocumentationVercel separate project (docs.command-inbox.sayantanbal.in)
Postgres + pgvectorNeon
RealtimePusher Channels
Scheduled jobscron-job.org → /api/cron/process-due
Gmail pushGCP Pub/Sub → production webhook URL

See Deploy to production for step-by-step setup.