DLRSDLRS
Integrations

Gmail Integration — Email Intelligence

Connect Gmail to auto-classify emails (task vs invoice vs meeting), extract tasks with deadlines, and keep provenance. Covers OAuth, vault, and ingestion — beginner-friendly.

What Gmail Integration Does

Beyond Inbox Dump

It doesn't copy all emails as tasks. The Email Agent classifies each email:
  • task_request → create task (e.g., “Update pricing by Thursday”)
  • invoice → Finance (subscriptions, receipts)
  • meeting → Calendar event
  • personal → low priority, no auto-task
Only high-confidence task emails become tasks (confidence > 0.8).

OAuth Setup (Step-by-Step)

1. Enable APIs in Google Cloud

Console → APIs & Services → Enable: Gmail API, Google Drive API, Calendar API.

2. Configure Consent Screen

  • User Type: External (for personal use, add test user = your email)
  • App name: DLRS Personal OS
  • Scopes: .../auth/gmail.readonly (read-only, safer), .../auth/drive.readonly, .../auth/calendar.readonly

3. Create OAuth Credentials

Credentials → Create → OAuth 2.0 Client → Web application:

Authorized redirect URIs (exact, no trailing slash):
http://localhost:3000/api/integrations/gmail/callback
http://localhost:3000/api/integrations/drive/callback
http://localhost:3000/api/integrations/calendar/callback

For production:
https://yourdomain.com/api/integrations/gmail/callback

4. Add to .env

GMAIL_CLIENT_ID="123.apps.googleusercontent.com"
GMAIL_CLIENT_SECRET="GOCSPX-..."
# Same credentials can be reused for Drive/Calendar or separate
GOOGLE_DRIVE_CLIENT_ID="..."
GOOGLE_DRIVE_CLIENT_SECRET="..."
GOOGLE_CALENDAR_CLIENT_ID="..."
GOOGLE_CALENDAR_CLIENT_SECRET="..."
SECRET_ENCRYPTION_KEY="already generated via node -e ..." # encrypts tokens

5. Connect in UI

/w/[workspace]/settings/integrations → Gmail → Connect → consent → redirect back with code → exchange for access_token + refresh_token.

Token Storage & Security

Flow:
1. OAuth callback: GET /api/integrations/gmail/callback?code=...
2. Exchange code → tokens via googleapis
3. Encrypt: vault = AES-256-GCM(JSON.stringify({ access_token, refresh_token }), SECRET_ENCRYPTION_KEY)
4. Store: vault_items { workspace_id, kind: "gmail", encrypted_blob }
5. Use: decrypt transiently per request, refresh if expired

Key file: lib/security/vault.ts
Table: vault_items (encrypted at rest, never logged)

🔒 Production Must

Never commit .env. Rotating SECRET_ENCRYPTION_KEY invalidates existing vault rows — re-encrypt via migration script.

Ingestion & Classification

Current: webhook/poll hybrid.

Option A: Gmail push notification (watch)
  POST https://gmail.googleapis.com/users/me/watch
  → Pub/Sub → POST /api/webhooks/inbox (verified via WEBHOOK_SECRET)

Option B: Poll (fallback, current worker every 5m)
  Worker calls Gmail history.list → new messages → queue jobs

Both queue gmail_ingest jobs via Redis.

From Email to Task (Example)

Email from Sarah <[email protected]>:
Subject: Website pricing update
Body: "Please update pricing section on website by Thursday. See pricing.pdf attached. High priority."
Attachment: pricing.pdf (1 page)

Steps:
1. Fetch via Gmail API → store raw in emails { gmail_id, thread_id, subject, body, attachments }
2. Email Agent: classify task_request, confidence 0.94
3. Inbox Agent: extract client=Acme (from email domain), project=Website, task="Update pricing section",
     deadline=Thursday 2025-12-04, attachment=pricing.pdf, priority=High
4. Propose → Review modal → Create task linked to client/project, attach file
5. Provenance: task.source_type=gmail, source_id=email_abc123, ai_confidence=0.94
6. Click Source in task → opens Gmail thread
emails {
  id, workspace_id, gmail_id, thread_id, subject, body_text, snippet,
  from_email, to_email, date, has_attachments, classification, confidence
}
attachments { id, email_id, filename, mime, storage_path }

Search: lib/search includes emails content — query "pricing" finds this email + task + file.

Troubleshooting

On this page