An AI-powered Gmail and Google Calendar client. Thread gives you a priority inbox, plain-language scheduling, and a conversational assistant that reads, drafts, and acts on your behalf — all without storing your email on our servers.
- Priority inbox — AI triage classifies each message and surfaces what needs attention
- Calendar view — unified schedule with conflict detection and availability queries
- Focus mode — stripped-down view for heads-down work
- AI assistant (
/chat) — plain-language interface to Gmail and Calendar; reads threads, drafts replies, creates events, checks availability - Google-native — connects via OAuth; your mailbox is never synced to our database. The assistant reads directly from Gmail at query time through Corsair
- Account controls — disconnect Google, revoke tokens, or delete your account from
/settings
| Layer | Technology |
|---|---|
| Framework | Next.js 15 (App Router) + React 19 |
| Styling | Tailwind CSS v4 |
| Auth | Better Auth — email/password + Google OAuth |
| Database | PostgreSQL + Drizzle ORM |
| API | tRPC v11 + React Query |
| Gmail / Calendar | Corsair — per-user encrypted credential store, MCP tool adapter |
| AI | Vercel AI SDK + Google Gemini (flash for triage, pro for assistant) |
| Deployment | Vercel |
src/
├── app/
│ ├── (marketing)/ # Public pages: /, /privacy, /terms
│ ├── (auth)/ # /signin, /signup
│ ├── inbox/ # Priority inbox + thread view
│ ├── calendar/ # Calendar workspace
│ ├── focus/ # Focus mode
│ ├── chat/ # AI assistant
│ ├── settings/ # Account & integrations management
│ └── api/ # Route handlers: auth, trpc, chat, health, webhooks
└── server/
├── better-auth/ # Auth config + session helpers
├── db/ # Drizzle schema + client
├── api/ # tRPC routers (account, inbox, calendar, …)
├── ai/ # Gemini provider, system prompt, chat store
├── mail/ # Gmail service + triage classifier
├── calendar/ # Calendar service + event watcher
├── corsair/ # Per-tenant credential management
├── integrations.ts # Google OAuth scope definitions (single source of truth)
├── config.ts # App URL, name, environment helpers
└── logger.ts # Structured JSON logger (JSON in prod, pretty in dev)
Prerequisites: Node 20+, pnpm, Docker (for the local Postgres instance)
# 1. Install dependencies
pnpm install
# 2. Create your local env file and fill in the values
cp .env.example .env
# 3. Start a local Postgres container
./start-database.sh
# 4. Push the schema (no migration files needed in dev)
pnpm db:push
# 5. Start the dev server with Turbopack
pnpm devOpen http://localhost:3000.
| Command | What it does |
|---|---|
pnpm dev |
Dev server with Turbopack |
pnpm build |
Production build |
pnpm check |
Lint + typecheck (run before pushing) |
pnpm db:push |
Push schema changes without generating migration files |
pnpm db:generate |
Generate a Drizzle migration file |
pnpm db:migrate |
Run pending migrations |
pnpm db:studio |
Open Drizzle Studio (visual DB browser) |
pnpm format:write |
Format all source files with Prettier |
Copy .env.example to .env and fill in every value. The app validates all variables at startup via src/env.js and will refuse to start if required values are missing.
Required groups:
- Database —
DATABASE_URL(Postgres connection string) - Auth —
BETTER_AUTH_SECRET(≥32 chars in production),BETTER_AUTH_URL(canonical origin, required in production) - Google OAuth —
GOOGLE_CLIENT_ID,GOOGLE_CLIENT_SECRET - Corsair —
CORSAIR_API_KEY,CORSAIR_ENCRYPTION_KEY - Gemini —
GEMINI_API_KEY
See docs/deployment.md for the full variable reference and Google Cloud project setup.
| Route | Auth | Purpose |
|---|---|---|
/ |
Public | Homepage + Google data transparency disclosure |
/privacy |
Public | Privacy Policy (required for Google OAuth verification) |
/terms |
Public | Terms of Service |
/signin, /signup |
Public | Authentication |
/inbox |
Required | Priority inbox |
/calendar |
Required | Calendar workspace |
/focus |
Required | Focus mode |
/chat |
Required | AI assistant (landing page after login) |
/settings |
Required | Google integration status + disconnect/delete |
/api/health |
Public | Liveness probe — DB ping, returns 200 ok or 503 degraded |
Scopes are defined in one place — src/server/integrations.ts — and must not be changed without updating the OAuth verification submission.
| Scope | Why |
|---|---|
gmail.modify |
Read messages, apply labels (triage), mark read/unread |
gmail.send |
Send replies and new messages from the assistant |
calendar.events |
Read and write calendar events; check availability |
All three are restricted scopes. They require Google's OAuth verification process, including a CASA Tier 2 security assessment. See docs/google-oauth-verification.md.
See docs/deployment.md for the full guide. The short version:
# Vercel CLI
vercel --prodRequired before going live:
- Set all environment variables in the Vercel dashboard
- Add
BETTER_AUTH_URLpointing to your production domain - Add
<your-domain>/api/auth/callback/googleas an authorised redirect URI in the Google Cloud Console - Run
pnpm db:migrateagainst the production database - Complete Google OAuth verification (see docs/google-oauth-verification.md)