Workspace Gmail
Connects a Google account per workspace so automated emails (welcome emails, brochures) are sent from the org's own Gmail address.
- API:
apps/api/src/gmail/(prefix/v1/workspace/gmail) - SDK:
@branofy/api/gmail - UI:
apps/premium/app/(dashboard)/settings/integrations/email/page.tsx - Schema:
gmail_connections(one row per workspace)
See README.md for the shared OAuth model, signed state, and env vars.
Endpoints
| Method | Path | Auth | Purpose |
| --- | --- | --- | --- |
| GET | /v1/workspace/gmail/auth | session/api‑key | Returns { url } — the Google consent URL (access_type=offline, prompt=consent). |
| GET | /v1/workspace/gmail/callback | public | Exchanges code → tokens + email → stores connection → 302 to settings. |
| GET | /v1/workspace/gmail/status | session/api‑key | { connected, email }. |
| DELETE | /v1/workspace/gmail/status | session/api‑key | Disconnect (deletes the row). |
Connect flow
sequenceDiagram
actor U as User (browser)
participant A as NestJS API (/v1/workspace/gmail)
participant G as Google OAuth
participant I as googleapis.com
U->>A: GET /v1/workspace/gmail/auth → { url }
U->>G: redirect to consent (access_type=offline, prompt=consent)
G-->>A: GET /v1/workspace/gmail/callback ?code & state
A->>A: verify state
A->>I: POST oauth2/token → access + refresh tokens
A->>I: GET oauth2/v2/userinfo → email
A->>A: upsert gmail_connections (by organization_id)
A-->>U: 302 → /settings/integrations/email?gmail=connected&address=...
- UI fetches
/api/workspace/gmail/auth, redirects to Google's consent screen. - Google redirects to
{API_PUBLIC_URL}/v1/workspace/gmail/callback?code&state. - Callback exchanges the code at
oauth2.googleapis.com/token, reads the address fromoauth2/v2/userinfo, and upsertsgmail_connectionsfor the org. - Redirect →
{APP}/settings/integrations/email?gmail=connected&address=....
OAuth scopes
https://www.googleapis.com/auth/gmail.send,
https://www.googleapis.com/auth/userinfo.email, openid.
Credentials
Google OAuth client is env‑only: GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRET.
Authorized redirect URI: {API_PUBLIC_URL}/v1/workspace/gmail/callback. The Gmail
API must be enabled on the Google Cloud project.
Reconnecting
The connection is keyed by organization_id (one Gmail per workspace).
Reconnecting updates the existing row with a fresh access token, expiry, scope,
and email.
flowchart TD
R[connect runs again] --> C{row exists for org?}
C -- no --> I[INSERT gmail_connections]
C -- yes --> U[UPDATE access token, expiry, scope, email]
U --> Q{response has refresh_token?}
Q -- yes --> N[store new refresh token]
Q -- no --> K[KEEP existing refresh token]
I --> D[done]
N --> D
K --> D
Refresh‑token preservation: Google only returns a
refresh_tokenon the first consent (or withprompt=consent). The callback keeps the previously stored refresh token whenever the new token response omits one, so a reconnect never wipes the ability to refresh access tokens offline.
Access tokens are short‑lived (~1h) and meant to be refreshed using the stored refresh token by the email‑sending worker. Disconnecting deletes the row; a later connect is a clean first‑time consent.
Troubleshooting
| Symptom | Cause / fix |
| --- | --- |
| gmail=error&reason=... | Google denied or misconfigured — check the authorized redirect URI and that the Gmail API is enabled. |
| 400 from /auth | GOOGLE_CLIENT_ID not set. |
| No refresh token after reconnect | Expected — the original is preserved; force a new one by disconnecting first, then reconnecting. |