Email Infrastructure
Gmail + Outlook integration with real-time sync
┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐
│ Gmail API │ │ Microsoft Graph │ │ Supabase │
│ (OAuth 2.0) │ │ (OAuth 2.0) │ │ (PostgreSQL) │
└────────┬─────────┘ └────────┬──────────┘ └────────┬─────────┘
│ │ │
▼ ▼ │
┌──────────────────────────────────────────┐ │
│ Provider Abstraction Layer │ │
│ lib/mail/google.ts │ microsoft.ts │ │
│ lib/mail/factory.ts │ │
│ (auto-selects provider per account) │ │
└────────────────────┬─────────────────────┘ │
│ │
▼ │
┌──────────────────────────────────────────┐ │
│ Inngest Job Queue │ │
│ syncInbox (scheduled, every 12h) │──── upsert ───▶│
│ processWebhookEvent (on push) │ │
│ renewWatchesAndSubscriptions │ │
└────────────────────┬─────────────────────┘ │
│ │
▼ ▼
┌──────────────────────────────────────────────────────────────┐
│ Unified Inbox UI │
│ 3-column layout │ Thread view │ Compose │ Reply │ Archive │
│ Supabase Realtime subscriptions for instant UI updates │
└──────────────────────────────────────────────────────────────┘Provider Abstraction
A clean interface-based abstraction allows the platform to support multiple email providers with the same API surface. The factory pattern auto-selects the correct provider based on the connected account.
interface MailProvider {
listThreads(options: ListOptions): Promise<Thread[]>
getThread(threadId: string): Promise<ThreadDetail>
sendMessage(params: SendParams): Promise<Message>
replyToThread(threadId: string, params: ReplyParams): Promise<Message>
archiveThread(threadId: string): Promise<void>
starThread(threadId: string, starred: boolean): Promise<void>
markAsRead(threadId: string): Promise<void>
searchThreads(query: string): Promise<Thread[]>
}google.tsmicrosoft.tsfactory.tsOAuth Flow
createExternalAccount. This avoids Clerk's session re-verification requirement (403 Forbidden) and allows requesting extended mailbox scopes.Required Scopes
Real-time Sync
Email synchronization uses a hybrid approach: scheduled full syncs via Inngest (every 12 hours) combined with real-time delta syncs triggered by provider webhooks.
| Job | Trigger | Behavior |
|---|---|---|
| syncInbox | Scheduled (12h) + on-demand | Fetch 50 threads, batch-process 10 at a time, upsert to Supabase |
| processWebhookEvent | Webhook push | Delta sync using stored cursor (historyId or deltaToken) |
| renewWatchesAndSubscriptions | Scheduled (12h) | Renew Gmail watches (7-day expiry) + Outlook subscriptions (4230 min) |
Capabilities Matrix
| Capability | Gmail | Outlook | Status |
|---|---|---|---|
| OAuth 2.0 Connect | Implemented | ||
| List Threads (paginated) | Implemented | ||
| Full Message Bodies | Implemented | ||
| Send New Email | Implemented | ||
| Reply to Thread | Implemented | ||
| Archive | Implemented | ||
| Star / Flag | Implemented | ||
| Mark Read/Unread | Implemented | ||
| Attachment Tracking | Implemented | ||
| Real-time Push | Scaffolded | ||
| Delta Sync | Implemented | ||
| Token Auto-Refresh | Implemented | ||
| Proposal ↔ Thread Link | Implemented |
Webhook Architecture
• Google Cloud Pub/Sub topic receives Gmail notifications
• Push subscription sends to /api/webhooks/gmail
• Webhook creates mail_sync_events row
• Inngest job performs delta sync via history.list
• Watch must be renewed every 7 days
• Microsoft Graph subscription on /me/messages
• Push to /api/webhooks/microsoft-graph
• Handles validation token handshake
• Inngest job performs delta sync via deltaToken
• Subscription renewed every 4230 minutes