Gmail API + OAuth2: shared mailbox with no real password
How to connect a shared Gmail mailbox to an internal panel — the same experience as tools like Tidio, Front or Help Scout — without anyone, not even whoever writes the code, ever seeing the mailbox's real password.
Two ways to connect to Gmail, and which to pick
Google offers two paths for an app that needs to read/write Gmail on someone else's behalf:
| Aspetto / Aspect | Domain-Wide Delegation | OAuth2 app installata / installed app |
|---|---|---|
| Needed for | Impersonating multiple mailboxes across a whole Workspace domain | A single specific mailbox |
| Requires | Service account + accesso admin a Workspace | One-time consent from whoever has real access to the mailbox |
| Drawback | Complex setup, elevated privileges | Has to be redone if the token is revoked |
For a single shared mailbox, installed-app OAuth2 is almost always the right call: no domain-admin privileges, simpler setup.
Setup on Google Cloud Console
- Enable the Gmail API on the Google Cloud project
- Configure the OAuth consent screen with the
gmail.readonlyandgmail.sendscopes (minimum necessary — avoid broader scopes likegmail.modifyunless needed) - Add the address that will give consent as a "test user" — the app stays in Testing mode, no publishing or Google verification needed for internal use
- Create a "desktop app" type OAuth client
- Run the one-time consent: the app receives a refresh token, not a password
Regola ferrea: nessuna password reale deve mai passare per chat, ticket o codice. È proprio il senso di OAuth: nemmeno chi implementa l'integrazione ha bisogno di vedere la password vera della casella, in nessun momento del processo — solo il refresh token, salvato esclusivamente come variabile d'ambiente lato server.
The three typical Netlify Functions
A complete integration splits into three server-side functions, never in the client bundle:
- list/read — periodic poll (e.g. every minute) on the mailbox for new messages, using
gmail.readonly - send — sending replies with
gmail.send, never bundled with the read function - attachments/inline images — resolving
cid:references in embedded images and downloading attachments on demand
The 403 caused by one extra character
A 403 error with a seemingly correct refresh token and client ID/secret is almost always a copy-paste issue: a trailing space, a leftover newline, or an invisible character in the environment variable. Always double-check the exact length and content of the variable on Netlify — don't trust the input field, log the string length temporarily if in doubt.
Limite della modalità Testing: un refresh token per un'app "installata" non ha scadenza fissa, ma può essere invalidato se l'utente cambia password, revoca manualmente l'accesso da Google, o se il client OAuth in modalità Testing supera 100 refresh token attivi. In quel caso basta ripetere il consenso una tantum per generarne uno nuovo.
Making an email look like an email, not a panel table row
The HTML body returned by the Gmail API should render with the message's original styling, not be reformatted with the panel's CSS — otherwise the result looks more like a table row than an email. Images embedded via cid: need resolving by fetching the matching attachment and swapping the reference for a URL or data URI before rendering.
Quick checklist
- Minimum scopes (
gmail.readonly+gmail.send), nogmail.modifyunless needed - Refresh token only as a server-side environment variable, never in the repo or client
- Separate read and send functions, to limit each key's blast radius
- Byte-for-byte check of environment variables if an apparently unjustified 403 shows up
- Awareness of the 100 active refresh token limit in Testing mode
Frequently asked questions
Can you read and reply to a Gmail mailbox without ever knowing the real password?
Yes, with OAuth2: whoever has real access gives one-time consent on Google, and the app receives a refresh token — not a password — that only lives as a server-side environment variable.
Do you need Domain-Wide Delegation to connect a single Gmail mailbox?
No. It's only needed to impersonate multiple mailboxes across a Workspace domain via a service account, with admin access. For a single mailbox, installed-app OAuth2 with one-time consent is enough.
Can a Gmail refresh token expire or stop working?
It has no fixed expiry, but can be invalidated if the user changes password, revokes access, or if the OAuth client in Testing mode exceeds 100 active refresh tokens. Repeating consent generates a new one.