Everything needed to take the OAuth consent screen from Testing to
Published, and to answer the questions Google's reviewers ask. Keep this
document in sync with src/server/integrations.ts, the single source of
truth for scopes in code.
| Scope | Classification | Why we need it | Features it powers |
|---|---|---|---|
https://www.googleapis.com/auth/gmail.modify |
Restricted | Read messages and change mailbox state. Strictly narrower than mail.google.com; supersedes gmail.readonly, which is therefore not requested. |
Inbox list, message view, search, AI priority triage, archive / star / mark read / trash |
https://www.googleapis.com/auth/gmail.send |
Restricted | Send mail the user composes. Grants no read access. | Compose, reply, forward |
https://www.googleapis.com/auth/calendar.events |
Sensitive | View and manage events only — deliberately not the full auth/calendar scope (no calendar list / ACL / settings access). |
Calendar view, event create/update/delete, invitations, focus timeline, event push notifications |
Identity scopes (openid, email, profile) are requested at sign-in only.
Where scopes are requested in code (the complete list):
src/app/_components/auth/integration-connect.tsx—authClient.linkSocial({ scopes }), usingGOOGLE_SCOPESfromsrc/server/integrations.ts. Gmail and Calendar are requested separately, only when the user clicks the respective Connect button (incremental authorization).src/app/_components/auth/social-buttons.tsx— Google sign-in, identity only.
Legacy grants (gmail.readonly, full auth/calendar) from earlier builds
are still recognized by getIntegrationStatus so existing users keep
working, but are never requested again.
(Reviewers ask for this; it is also stated on /privacy and the homepage.)
- Google user data is used only to provide user-facing features: inbox display, triage, search, composing/sending, and calendar scheduling.
- No advertising use, no sale of data, no human access except with explicit user consent, for security, or as required by law.
- The mailbox is not synced or copied to our servers. Messages are
fetched from the Gmail API per request. The only derived data stored is a
per-message priority/category label (no message content) in
email_classification. - AI features send relevant excerpts to the Google Gemini API at the user's direction, solely to produce the requested output; not used to train generalized models. Disclosed in the privacy policy.
- OAuth tokens are stored encrypted (Better Auth
accounttable plus Corsair's KEK-encrypted credential store) and deleted on disconnect.
/settings— shows exactly which permissions are granted; Disconnect Google & revoke access callshttps://oauth2.googleapis.com/revokeand deletes tokens, push channels, and triage labels (account.disconnectGoogleinsrc/server/api/routers/account.ts).- Delete account — same revocation plus full deletion of the user row
and all cascaded data, and explicit purge of Corsair tenant rows
(
account.deleteAccount). - The privacy policy also links Google's own permissions page.
Console: APIs & Services → OAuth consent screen in the project that owns the OAuth client.
- App domain ownership verified in Google Search Console for the
production domain (the one in
BETTER_AUTH_URL). - Homepage — production
/(public, describes the app, links the privacy policy in the footer). - Privacy policy URL — production
/privacy. - Terms of service URL — production
/terms. - Authorized redirect URI —
<BETTER_AUTH_URL>/api/auth/callback/google. - Authorized JavaScript origin —
<BETTER_AUTH_URL>. - App name, logo (120×120), and support email set; logo triggers brand verification.
- Scope justifications — paste the table above, one justification per scope, naming the in-product feature.
- Demo video (YouTube, unlisted is fine) showing: the OAuth consent
screen with your production client, connecting Gmail and Calendar
separately, each scope being exercised in the UI (inbox + archive for
gmail.modify, sending forgmail.send, event creation forcalendar.events), and the disconnect flow in/settings. - Because
gmail.modifyandgmail.sendare restricted scopes, expect the security assessment (CASA Tier 2) for public launch. Budget for an annual third-party assessment once over 100 users. - Enable the Gmail API and Google Calendar API in the project.
- Move publishing status from Testing to In production and submit for verification.
- The consent screen domain,
BETTER_AUTH_URL, homepage, privacy, and terms must all live on the same verified domain. - The privacy policy must mention Google user data specifically (ours
does: see "How we use Google user data" on
/privacy) and include the Limited Use disclosure. - Don't request scopes the demo video doesn't show being used — reviewers reject on unused scopes. Our request set is exactly what the UI uses.
- Keep using incremental auth (separate Gmail / Calendar connects); Google explicitly recommends it and it reduces review friction.