Skip to content

feat: send-only Gmail integration with draft_email chat tool - #1819

Open
gabrielste1n wants to merge 1 commit into
mainfrom
feat/gmail-integration
Open

feat: send-only Gmail integration with draft_email chat tool#1819
gabrielste1n wants to merge 1 commit into
mainfrom
feat/gmail-integration

Conversation

@gabrielste1n

Copy link
Copy Markdown
Collaborator

What

Adds Gmail as an integration (Settings → Integrations → Email) and a draft_email chat tool. In a meeting note's chat, "draft me a follow-up email" produces an editable email card — To/Cc pre-filled from the note's calendar participants, subject and body composed from the note and transcript. Pressing Send on the card sends via the Gmail API from the user's account.

Design decisions

  • Send-only scope. The OAuth grant is gmail.send openid email only — Google's sensitive tier, which requires only brand verification (no CASA security audit). No read/compose scopes anywhere.
  • The model can never send. draft_email is a pure compose step that returns the draft as tool metadata; the only send path is the user's click on the card, which goes through a dedicated gmail-send-email IPC handler.
  • Tokens encrypted at rest. gmail_tokens columns are encrypted with the existing secretCrypto helper (OS-keychain AES-256-GCM) — deliberately stronger than the plaintext-SQLite calendar-token precedent, since a send-capable refresh token is higher risk. Plaintext fallback only where the app already falls back (Linux without a keyring). Tokens are revoked on disconnect and on account deletion.
  • Reuses the calendar OAuth plumbing. Same loopback 127.0.0.1 + PKCE flow (oauthLoopbackFlow.js), same net.fetch conventions, no new dependencies. Client credentials read GMAIL_CLIENT_ID/_SECRET with fallback to GOOGLE_CALENDAR_CLIENT_* (same GCP client), so CI needs no changes.
  • Single account for v1; reconnecting with a different account replaces the stored grant.
  • Hidden when unconfigured. The Integrations tile only renders when an OAuth client id is configured, so OSS/dev builds don't show a tile that can only fail.

Notable side fix

The BYOK/local streaming path dropped tool-result metadata (only the cloud path forwarded it), so interactive tool cards silently never rendered for BYOK users. processTextStreamingAI now forwards successful object outputs as metadata — this also un-breaks the existing note cards on BYOK.

Changes

  • Main process: gmailOAuth.js, gmailManager.js (RFC 2822 MIME builder + messages/send POST), encrypted gmail_tokens table + CRUD, gmail-* IPC handlers, lifecycle wiring, revoke on account deletion
  • Renderer: Integrations Email section with connect/disconnect, gmailConnected/gmailEmail in the settings store, draft_email tool gated on connection, participants added to note-chat context, EmailDraftCard with draft/sending/sent/failed states, sent state persisted into the message's tool-call metadata via a new db-update-agent-tool-call IPC
  • i18n: all 10 locales (npm run i18n:check passes)
  • Tests: 17 new tests across gmailManager (MIME framing, UTF-8 headers, send auth/error paths), gmailDatabase (encryption at rest, single-account replacement, metadata patching), and draftEmailTool (validation, no-send contract)

Testing

  • npm test: 2777 pass; the only 2 failures (textEditMonitorWindowBounds) fail identically on pristine main (environment-dependent)
  • npm run typecheck, npm run lint, npm run i18n:check: clean
  • Branch rebased onto latest main

Companion PR

  • OpenWhispr/openwhispr-website#113 — teaches /auth/desktop-callback the gmail_connected/gmail_error params. Until it deploys, test locally with VITE_OPENWHISPR_OAUTH_CALLBACK_URL.

Out of scope (follow-ups)

  • GCP console: add gmail.send to the consent screen and submit sensitive-scope verification (this flow doubles as the demo video)
  • Reading email / Gmail drafts (restricted scopes → CASA audit) intentionally deferred

Connect Gmail from Settings -> Integrations (loopback PKCE OAuth, gmail.send
scope only) and ask the note chat to draft a follow-up email: a new
draft_email tool composes from the note, transcript, and participants, and
renders an editable email card in chat. Sending only happens when the user
presses Send on the card.

- Gmail OAuth + manager in the main process (net.fetch, no new deps),
  reusing the shared loopback flow; falls back to the calendar OAuth client
- Gmail tokens encrypted at rest via secretCrypto (keychain AES-256-GCM),
  single-account, revoked on account deletion
- draft_email tool gated on a connected Gmail account; participants from
  calendar events now included in the note chat context
- EmailDraftCard with editable To/Cc/Subject/body, send states, and sent
  status persisted into the message's tool-call metadata
- Forward AI-SDK tool outputs as metadata on the BYOK/local path so
  tool-result cards (including existing note cards) render there too
- Integrations tile hidden when no OAuth client is configured; i18n for all
  10 locales
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant