Judge / Evaluator? → Start with DEMO.md for a 3-minute walkthrough, 5 live curl examples, the full Corsair integration map, and the scoring checklist.
A full-stack productivity application built entirely on the Corsair SDK that brings Gmail and Google Calendar into a focused, AI-driven, human-in-the-loop workflow. Built for the Corsair Hackathon.
Pitch deck (full walkthrough): Watch on X
Short demo clips — click to watch on X:
| # | Link |
|---|---|
| Pitch | Pitch deck |
| YC style | It is what it is ep-2 |
| 1 | Demo clip 1 |
| 2 | Demo clip 2 |
| 3 | Demo clip 3 |
| 4 | Demo clip 4 |
| 5 | Demo clip 5 |
| 6 | Demo clip 6 |
Live app: thread-web.vercel.app · API docs: thread-api.vercel.app/docs
| Feature | Description | Corsair APIs Used |
|---|---|---|
| AI Daily Brief | Personalised daily summary: urgent emails, meetings, follow-ups, free windows | gmail.api.threads.*, googlecalendar.api.events.*, OpenAI |
| AI Agent (57 tools) | Plain-language assistant: send emails, manage calendar, summarize threads, get contact intelligence | All Corsair Gmail + Calendar APIs + DB search |
| Human-in-the-Loop Queue | Every AI-composed email/invite requires approval before sending | gmail.api.messages.send, googlecalendar.api.events.create |
| MCP Server (57 tools) | Full MCP 2024-11-05 server: tools, resources, prompts — connect Claude/Cursor/any AI | All Corsair APIs + DB search |
| Inbox | Cache-first Gmail inbox, search, labels, thread reader, keyboard nav | gmail.api.threads.*, gmail.api.labels.* |
| Smart Reply | 3 AI suggestions per Gmail thread | gmail.api.threads.get + OpenAI |
| Meeting Prep | Agenda, risks, talking points, related emails per calendar event | googlecalendar.api.events.get + gmail.api.threads.list + OpenAI |
| Thread Summarization | Key decisions, action items, next steps | gmail.api.threads.get + OpenAI |
| Contact Intelligence | Relationship summary, response rate, recommended next action | gmail.api.threads.list + OpenAI |
| Missed Follow-ups | Meetings from last 2 weeks with no follow-up email | googlecalendar.api.events.* + gmail.api.threads.list |
| Calendar | View, create, reschedule, cancel events; accept/decline invites | googlecalendar.api.events.* |
| CC/BCC Support | All outbound emails support CC and BCC fields | gmail.api.messages.send (raw MIME) |
| Gmail Push Webhooks | Real-time inbox updates via Gmail Pub/Sub | gmail.api.users.watch |
| Calendar Push Webhooks | Real-time calendar updates | googlecalendar.api.channels.* |
Total Corsair API call sites: 55+ · Agent tools: 57 · MCP tools: 57 (verified by CI parity test)
- Inbox — Cache-first Gmail inbox with stale-while-revalidate, search, thread reader, CC/BCC compose, and keyboard navigation (
j/k/Enter) - AI Priority — Rank inbox threads by urgency using OpenAI + Corsair Gmail data
- Queue — Every outbound action (email send, draft save, calendar invite) is staged here for your approval before it executes — nothing sends without your OK
- Agent — Plain-language AI assistant with streaming responses and 57 Corsair-backed tools. Full parity with the MCP server (CI-verified).
- Calendar — View and manage events; create/reschedule/cancel through the approval queue
- MCP Server — Full MCP 2024-11-05 server at
/mcp:tools,resources,prompts— so Claude, Cursor, and any AI tool can use your inbox and queue directly
┌─────────────────────────────────────────────────────────────┐
│ Browser │
│ Next.js 15 (apps/web) │
│ ├── /inbox — Gmail inbox (cache-first, SWR) │
│ ├── /queue — Human approval queue │
│ ├── /agent — AI chat (SSE streaming) │
│ ├── /calendar — Event management │
│ └── /settings — Auto-approve preferences │
└────────────────────┬────────────────────────────────────────┘
│ tRPC + REST (OpenAPI)
┌────────────────────▼────────────────────────────────────────┐
│ Express API (apps/api) │
│ ├── /trpc — Type-safe tRPC procedures │
│ ├── /mcp — MCP 2024-11 / JSON-RPC 2.0 server │
│ ├── /agent/stream — SSE streaming agent responses │
│ ├── /sync/events — SSE webhook → client cache invalidation │
│ ├── /auth/corsair — Gmail + Calendar OAuth callbacks │
│ ├── /webhooks — Gmail push notification handler │
│ ├── /metrics — Prometheus-compatible metrics │
│ ├── /health — Database health check │
│ └── /docs — Scalar OpenAPI reference │
└──────────┬──────────────────────┬───────────────────────────┘
│ │
┌──────────▼──────────┐ ┌────────▼────────────────────────────┐
│ PostgreSQL │ │ Corsair SDK │
│ (Neon / local) │ │ ├── Gmail API (threads, messages) │
│ Drizzle ORM │ │ ├── Google Calendar API │
│ ├── users │ │ ├── OAuth management │
│ ├── queue_items │ │ └── Push webhook delivery │
│ ├── mail_cache │ └─────────────────────────────────────┘
│ └── contacts │
└─────────────────────┘
| Package | Purpose |
|---|---|
apps/web |
Next.js frontend |
apps/api |
Express API server |
packages/trpc |
Shared tRPC router + procedures |
packages/services |
Domain services (inbox, queue, calendar, AI agent) |
packages/database |
Drizzle schema + migrations |
- Node.js 20+
- pnpm 9+
- PostgreSQL (local Docker or Neon free tier)
- Corsair account — corsair.dev (free tier works)
- Google Cloud project with Gmail API + Google Calendar API enabled
- OpenAI API key (for AI features; optional but required for agent/ranking)
git clone <repo-url>
cd "Corsair Hackathon"
pnpm installCopy .env.example to .env and fill in the values:
cp .env.example .envRequired variables:
# Database (Neon free tier or local Docker)
DATABASE_URL=postgresql://... # Pooled connection (app runtime)
DATABASE_URL_UNPOOLED=postgresql://... # Direct connection (migrations)
# Auth — generate with: openssl rand -base64 32
JWT_SECRET=<random-32-char-string>
JWT_REFRESH_SECRET=<random-32-char-string>
# Google OAuth (user sign-in with Google)
# Create at: https://console.cloud.google.com → APIs & Services → Credentials
# Authorized redirect URI: http://localhost:3000/api-auth/google/callback
GOOGLE_OAUTH_CLIENT_ID=your-client-id.apps.googleusercontent.com
GOOGLE_OAUTH_CLIENT_SECRET=GOCSPX-...
GOOGLE_OAUTH_REDIRECT_URI=http://localhost:3000/api-auth/google/callback
# Corsair — Gmail + Calendar integration (https://corsair.dev → Dashboard → API Keys)
# CORSAIR_KEK: 32-byte secret for encrypting OAuth tokens at rest
# generate: node -e "console.log(require('crypto').randomBytes(32).toString('base64'))"
CORSAIR_DEV_KEY=ch_your-corsair-dev-key
CORSAIR_KEK=<base64-encoded-32-byte-key>
CORSAIR_GMAIL_REDIRECT_URI=http://localhost:3000/api-connect/gmail/callback
APP_URL=http://localhost:3000
# OpenAI (optional — enables Agent + Priority inbox ranking)
# OPENAI_API_KEY=sk-proj-...
# OPENAI_MODEL=gpt-4o-mini
# App URLs
CLIENT_URL=http://localhost:3000
BASE_URL=http://localhost:8000
API_INTERNAL_URL=http://localhost:8000
NEXT_PUBLIC_API_URL=/trpcNote on CORSAIR_KEK: this is a symmetric encryption key, not an API key. Use the one-liner above to generate it — any 32 random bytes encoded as base64 works. Keep it stable across deployments; rotating it invalidates stored tokens.
In your Google Cloud project (same OAuth client as above, or a separate one):
- Enable Gmail API and Google Calendar API in APIs & Services → Library.
- Add the following OAuth scopes:
https://www.googleapis.com/auth/gmail.modifyhttps://www.googleapis.com/auth/calendar
- Add authorized redirect URIs:
http://localhost:3000/api-connect/gmail/callbackhttp://localhost:3000/api-connect/calendar/callback
- Copy the Client ID and Secret — these are the same
GOOGLE_OAUTH_CLIENT_ID/GOOGLE_OAUTH_CLIENT_SECRETused for user sign-in.
pnpm db:migrateNote: If you want Gmail push notifications in production (real-time inbox updates), see the Gmail Pub/Sub webhook setup section below.
pnpm devThis starts both the Next.js frontend (:3000) and Express API (:8000) in watch mode.
pnpm dev # Start all services
pnpm build # Production build
pnpm check-types # TypeScript type-check (all packages)
pnpm lint # ESLint (all packages)
pnpm test # Vitest unit tests (API package)
pnpm db:migrate # Run Drizzle migrations
pnpm db:studio # Open Drizzle Studio (DB GUI)cd apps/web
pnpm exec playwright testCI runs with THREAD_E2E_MOCK_GMAIL=true so compose → queue → approve works without live Gmail OAuth (e2e/queue-workflow.spec.ts).
For live Gmail integration (optional — requires connected account):
E2E_GMAIL_AVAILABLE=true E2E_SESSION_COOKIE="jwt=...; jwt_refresh=..." \
pnpm exec playwright test e2e/gmail-authenticated.spec.ts e2e/gmail-flows.spec.tsThread exposes two MCP endpoints:
| Endpoint | Purpose |
|---|---|
POST /mcp |
Thread domain tools (57) — inbox, queue, calendar, AI |
POST /mcp/corsair |
Official @corsair-dev/mcp adapter — corsair_setup, list_operations, get_schema, run_script |
Per Corsair MCP docs: agents discover and call any Gmail/Calendar endpoint dynamically.
{
"mcpServers": {
"corsair": {
"url": "http://localhost:8000/mcp/corsair",
"type": "http"
}
}
}Destructive Corsair actions (threads.delete, messages.delete, events.delete) use Corsair Permissions (cautious mode) — approve at /corsair/approve/:token or via POST /corsair/permissions/:token/approve.
| Tool | Description |
|---|---|
list_inbox |
List recent Gmail threads |
search_inbox |
Gmail query syntax search |
get_thread |
Full thread content |
list_queue |
Pending approval items |
approve_queue_item |
Approve → sends email / creates event |
dismiss_queue_item |
Reject without sending |
get_gmail_connection_status |
Check Gmail connection |
rank_inbox |
AI urgency ranking |
list_calendar_events |
Calendar events in a date range |
queue_email |
Queue email for human approval (3 sends/min cap) |
queue_calendar_invite |
Queue calendar invite (supports recurrence RRULE) |
list_labels |
List Gmail labels |
archive_thread |
Archive a thread |
apply_label |
Apply label to a thread |
remove_label |
Remove label from a thread |
Headless MCP auth: set THREAD_MCP_API_KEY + THREAD_MCP_USER_ID (bound pair — no arbitrary user impersonation). See mcp-server.json (v2.5.0, 57 tools).
Point your AI tool at the MCP server using mcp-server.json in the project root, or configure manually:
{
"mcpServers": {
"thread": {
"url": "http://localhost:8000/mcp",
"type": "http"
}
}
}Auth uses the same JWT cookies as the web app. Sign in at http://localhost:3000/sign-in first.
# Discover tools
curl -X POST http://localhost:8000/mcp \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'# Prometheus-compatible metrics (requires OPENAPI_DOCS_SECRET in production)
curl http://localhost:8000/metrics -H "Authorization: Bearer $OPENAPI_DOCS_SECRET"
# JSON metrics
curl http://localhost:8000/metrics/json -H "Authorization: Bearer $OPENAPI_DOCS_SECRET"
# Health
curl http://localhost:8000/health
# Readiness
curl http://localhost:8000/readyMetrics tracked:
- Per-route p50 / p95 / p99 latency
- Request counts + error rates
inbox.cache_hit— cache-warm inbox loadsqueue.approved.total/queue.dismissed.totalmcp.tool.<name>— per-tool MCP call counts
When REDIS_URL is set, counters persist across API restarts and /metrics/json merges Redis totals. Webhook-driven inbox/calendar updates also fan out via Redis pub/sub to /sync/events SSE clients (multi-instance safe).
Available at http://localhost:8000/docs — Scalar UI with full Corsair integration guide (~116 REST endpoints), tag groups, request examples, curl code samples, MCP tool appendix, and webhook/reference paths.
| Doc | URL |
|---|---|
| Scalar UI | {BASE_URL}/docs |
| OpenAPI JSON | {BASE_URL}/openapi.json |
| Full technical guide | DOCS.md |
| Judge walkthrough | JUDGE_WALKTHROUGH.md |
Production: set PUBLIC_OPENAPI_DOCS=false and OPENAPI_DOCS_SECRET, or pass ?key=<secret> / Authorization: Bearer <secret>.
In production you can receive real-time Gmail push notifications instead of relying on manual refreshes.
1. Create a Pub/Sub topic and grant Gmail publish rights:
gcloud pubsub topics create gmail-push
gcloud pubsub topics add-iam-policy-binding gmail-push \
--member=serviceAccount:gmail-api-push@system.gserviceaccount.com \
--role=roles/pubsub.publisher2. Create a push subscription pointing at your API:
gcloud pubsub subscriptions create gmail-push-sub \
--topic=gmail-push \
--push-endpoint=https://api.yourdomain.com/webhooks/gmail \
--ack-deadline=103. Add to .env:
WEBHOOKS_BASE_URL=https://api.yourdomain.com
CORSAIR_WEBHOOK_SECRET=your-random-secret
CORSAIR_GMAIL_TOPIC_ID=projects/YOUR_GCP_PROJECT/topics/gmail-pushThread automatically:
- Registers a Gmail watch on first user connection (or re-registers on OAuth refresh).
- Uses the Gmail History API for incremental sync — fetches only changed threads, not the full inbox.
- Persists the latest
historyIdto the database so restarts don't re-fetch everything. - Falls back to full-list refresh if history is too stale.
- Pushes
inbox_updated/calendar_updatedhints to connected browsers via SSE (GET /sync/events).
When rescheduling or deleting a recurring event, choose This event only, All events in the series, or This and following events — all changes go through the approval queue first.
The app ships with a one-click demo login that bypasses the sign-up + email verification flow.
Step 1 — seed the demo user (once, after migrations):
pnpm db:seedThis creates (or refreshes) the demo user — password is re-synced from SEED_DEMO_PASSWORD on every seed:
| Field | Value |
|---|---|
demo@thread.dev (or SEED_USER_EMAIL) |
|
| Password | DemoPass123! (or SEED_DEMO_PASSWORD) |
Step 2 — enable demo login in .env (recommended for production demo/judging):
DEMO_LOGIN_ENABLED=true
DEMO_USER_EMAIL=demo@thread.dev
DEMO_USER_PASSWORD=DemoPass123!Step 3 — open the demo URL:
http://localhost:3000/api-auth/demo?next=/inbox
This signs in automatically and redirects to the inbox. No email verification required.
Alternatively, sign up normally at http://localhost:3000/sign-up with any email, verify it, then sign in.
After signing in, go to /inbox → Connect Gmail (top right) and follow the OAuth flow. Repeat for Calendar at /calendar.
Once connected, the agent and inbox are fully live against your real Gmail account.
See apps/api/VERCEL_DEPLOY.md for the full checklist.
- Root Directory:
apps/api - Required env:
DATABASE_URL,JWT_SECRET,CORSAIR_KEK,BASE_URL,CLIENT_URL - Web project must set
API_INTERNAL_URL=https://thread-api.vercel.app
Deploy apps/api using railway.toml. Required env:
| Variable | Example | Notes |
|---|---|---|
DATABASE_URL |
postgresql://… |
Neon pooled URL |
CORSAIR_KEK |
base64:… |
From Corsair dashboard |
JWT_SECRET / JWT_REFRESH_SECRET |
long random strings | ≥32 chars |
CLIENT_URL |
https://app.yourdomain.com |
Web origin (CORS) |
BASE_URL |
https://api.yourdomain.com |
Public API URL |
WEBHOOKS_BASE_URL |
https://api.yourdomain.com |
Gmail/Calendar push targets |
CORSAIR_WEBHOOK_SECRET |
random ≥16 chars | Must match Pub/Sub push header |
CORSAIR_GMAIL_TOPIC_ID |
projects/…/topics/gmail-push |
Enables live inbox sync |
GOOGLE_OAUTH_CLIENT_ID/SECRET |
GCP console | Same app as Corsair plugins |
OPENAI_API_KEY |
sk-… |
Agent + priority ranking |
DEMO_LOGIN_ENABLED |
false |
Disable in public prod unless judging |
After deploy: GET https://api.yourdomain.com/ready should return ready: true.
Deploy apps/web with vercel.json. Set:
API_INTERNAL_URL=https://api.yourdomain.comNEXT_PUBLIC_*vars if used by client
Verify rewrites: /api-auth/*, /agent/stream, /inbox/attachments/* proxy to API.
Follow Gmail Pub/Sub webhooks. Push endpoint:
https://api.yourdomain.com/webhooks/gmail
Header: x-corsair-webhook-secret: <CORSAIR_WEBHOOK_SECRET>
Set THREAD_MCP_API_KEY + THREAD_MCP_USER_ID (bound pair) and call MCP with:
Authorization: Bearer <THREAD_MCP_API_KEY>
The API key is scoped to the user id in THREAD_MCP_USER_ID — arbitrary impersonation is not supported.
See mcp-server.json for all 57 tools.
curl https://api.yourdomain.com/health
curl https://api.yourdomain.com/ready
node scripts/load-test/health-smoke.mjs https://api.yourdomain.comSign in → Connect Gmail → send test queue item → approve → verify in Gmail.
- All outbound actions go through the human-in-the-loop queue — nothing sends without explicit approval
- Agent has 5 layers of guardrails: injection detection, email validation, per-session send cap (3), data fencing, token limit
- Rate limiting: auth (40/15min), agent (20/min/user), MCP (60/min/user)
- JWT with refresh token revocation on logout and password reset (tokenVersion bump), account lockout after 5 failures
- CSRF protection via
requireTrustedOriginon all state-changing requests