Due modi per collegarsi a Gmail, e quale scegliere
Google offre due strade per un'app che deve leggere/scrivere su Gmail per conto di qualcun altro:
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 |
Serve per Needed for |
Impersonare più caselle di un intero dominio Workspace Impersonating multiple mailboxes across a whole Workspace domain |
Una singola casella specifica A single specific mailbox |
Richiede Requires |
Service account + accesso admin a Workspace |
Consenso una tantum di chi ha accesso reale alla casella One-time consent from whoever has real access to the mailbox |
Svantaggio Drawback |
Setup complesso, privilegi elevati Complex setup, elevated privileges |
Va rifatto se il token viene revocato Has to be redone if the token is revoked |
Per una singola casella condivisa, OAuth2 app installata è quasi sempre la scelta giusta: nessun privilegio da amministratore di dominio, setup più semplice.
For a single shared mailbox, installed-app OAuth2 is almost always the right call: no domain-admin privileges, simpler setup.
Setup su Google Cloud Console
- Abilita la Gmail API sul progetto Google Cloud
- Configura la schermata di consenso OAuth con gli scope
gmail.readonly e gmail.send (minimi necessari — evita scope più ampi come gmail.modify se non servono)
- Aggiungi come "utente di test" l'indirizzo che darà il consenso — l'app resta in modalità Testing, niente pubblicazione o verifica Google necessaria per uso interno
- Crea un client OAuth di tipo "app desktop"
- Esegui il consenso una tantum: l'app riceve un refresh token, non una password
Setup on Google Cloud Console
- Enable the Gmail API on the Google Cloud project
- Configure the OAuth consent screen with the
gmail.readonly and gmail.send scopes (minimum necessary — avoid broader scopes like gmail.modify unless 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.
Le tre Netlify Function tipiche
Un'integrazione completa si divide in tre funzioni server-side, mai nel bundle client:
- list/read — poll periodico (es. ogni minuto) sulla casella per nuovi messaggi, con
gmail.readonly
- send — invio risposte con
gmail.send, mai con la stessa function di lettura
- attachments/inline images — risoluzione dei riferimenti
cid: nelle immagini incorporate e download allegati su richiesta
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
Il 403 causato da un carattere in più
Un errore 403 con refresh token e client ID/secret apparentemente corretti è quasi sempre un problema di copia-incolla: uno spazio finale, un a-capo residuo, o un carattere invisibile nella variabile d'ambiente. Verifica sempre lunghezza e contenuto esatto della variabile su Netlify — non fidarti del campo di input, stampa la lunghezza della stringa in un log temporaneo se il dubbio persiste.
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.
Far sembrare un'email un'email, non una tabella del pannello
Il corpo HTML restituito da Gmail API va renderizzato con lo stile originale del messaggio, non riformattato con il CSS del pannello — altrimenti il risultato somiglia più a una riga di tabella che a una mail. Le immagini incorporate via cid: vanno risolte recuperando l'allegato corrispondente e sostituendo il riferimento con un URL o un data URI prima del render.
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.
Checklist rapida
- Scope minimi (
gmail.readonly + gmail.send), niente gmail.modify se non serve
- Refresh token solo come variabile d'ambiente server-side, mai nel repository né nel client
- Funzioni di lettura e invio separate, per limitare il raggio d'azione di ciascuna chiave
- Verifica byte-per-byte delle variabili d'ambiente se compare un 403 apparentemente ingiustificato
- Consapevolezza del limite di 100 refresh token attivi in modalità Testing
Quick checklist
- Minimum scopes (
gmail.readonly + gmail.send), no gmail.modify unless 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
Domande frequenti
Si può leggere e rispondere a una casella Gmail senza mai conoscere la password reale?
Sì, con OAuth2: chi ha accesso reale alla casella fa un consenso una tantum su Google, e l'app riceve un refresh token — non una password — che vive solo come variabile d'ambiente lato server.
Serve la Domain-Wide Delegation per collegare una sola casella Gmail?
No. Serve solo per impersonare più caselle di un intero dominio Workspace tramite service account, con accesso admin. Per una singola casella basta un OAuth2 "app installata" con consenso una tantum.
Un refresh token Gmail può scadere o smettere di funzionare?
Non ha scadenza fissa, ma può essere invalidato se l'utente cambia password, revoca l'accesso, o se il client OAuth in modalità Testing supera i 100 refresh token attivi. Basta ripetere il consenso per generarne uno nuovo.
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.
Articolo correlato
Come ho ricostruito l'esperienza Tidio — con tutti i bug reali incontrati
Related article
How I rebuilt the Tidio experience — with all the real bugs along the way
→