Connecting Gmail, end to end
You start the connection from the Mailboxes screen, consent at Google, and come back to a mailbox row that holds sealed tokens rather than a password. The first sync then proves the connection is real. Connecting a mailbox is a separate consent from signing in to Connect, and revoking one does not revoke the other.
The chain from consent to first thread#
- Trigger — Add a mailbox on the Mailboxes screen, in either audience: the Owner's flow asks the same question a customer's wizard asks.
- User event — you choose Google and are sent to Google's own consent screen; Connect never sees the password behind it.
- Authentication and workspace resolution — the callback re-enters your session, and the mailbox is created inside the workspace that started the flow, not the one a browser claims.
- Ingest — Google returns a grant, which
oauth.store_tokensturns into the mailbox'sconfig. - Canonical record — a mailbox row exists with a transport, sealed credentials, a role, a signature, its own autonomy and its own health verdicts.
- Classification — the uniqueness check asks whether this Google identity is already connected, using the one query that can see rows an ordinary list cannot.
- Knowledge, memory and rules — nothing yet; a new mailbox inherits the workspace and channel tiers until somebody sets an endpoint rule on it.
- Autonomy and approval — the new endpoint gets no autonomy of its own, so the channel rule applies until you narrow it.
- Action through a provider — the first sync calls the Gmail API with the stored grant and fetches from an empty cursor.
- Result — threads on Conversations, a last-sync time on the row, and a health verdict that is more than 'connected'.
- Relationship, timeline and memory — the people behind those messages resolve to persons and companies, so history exists before the first reply is written.
- Audit, usage and Needs You — the connection is recorded, the fetch is metered, and anything that needs your attention is queued rather than left in a log.
What is stored, and what is not#
Connect stores a grant from Google, not your password. The grant lives in the mailbox's config, which settings_store seals on every save and never echoes back to a screen — reading the connection form back shows you that a credential is present, not what it is.
config is a real setter rather than a read-only view, and that detail is load-bearing: oauth.store_tokens rebuilds the whole dictionary and assigns it back each time Google issues a fresh access token. A read-only property would have quietly dropped every refresh, and the mailbox would have worked until the first token expired and then stopped for no visible reason.
| Stage | What you see | What changes | What can fail |
|---|---|---|---|
| Start | Google's consent screen | Nothing on the Connect side yet | A blocked pop-up, or an account chooser landing on the wrong Google identity |
| Consent | The scopes Google is asking you to grant | Nothing yet | Declining one scope, which leaves a grant that cannot do the work |
| Callback | The Mailboxes screen with a new row | A mailbox row with sealed credentials in config | A session that expired while you were at Google |
| Uniqueness check | One row, not two, for one inbox | Nothing, when the identity is already connected | A row with an empty workspace stamp — invisible to an ordinary list, so a reconnection would have written a second row for one real inbox |
| First sync | Threads on Conversations | tenant_* rows, then canonical threads, then the cursor | A scope that was declined, or a grant revoked at Google between consent and sync |
| Health | A verdict beyond 'connected' | Health verdicts on the row | A mailbox that authenticates and returns nothing — a quiet mailbox, which is a signal rather than an error |
Two consents that are easy to confuse#
Google OAuth is the only way to sign in to Connect — there is no password login. That makes it easy to assume signing in also connected your mail. It did not. Signing in proves who you are; connecting a mailbox grants Connect the ability to read and send as that mailbox, and the two grants are separate, revocable separately, and visible separately in your Google account.
The practical consequence is worth remembering during an incident: revoking Connect's access at Google stops mail flowing while you can still sign in perfectly well, which reads on screen as a broken mailbox rather than a deliberate revocation. Reconnect required covers the way back.
Proving the connection rather than assuming it#
Press Sync on the new row and wait for the last-sync time to move.
Result The read path is proved: a grant that cannot read never updates that time.
Open Conversations and find a thread you recognise from the last day.
Result The bridge is proved too — a fetch that filled
tenant_*and never reached the canonical tables looks identical on the mailbox row.Give the mailbox a role and a signature before the first reply goes out.
Result The role decides which mailbox answers which thread, so setting it later means some replies already went from a mailbox you did not intend.
Send one reply, or approve one held reply, from this mailbox.
Result The send path is proved, with the provider's acknowledgement recorded against the message.
Questions#
Can one workspace connect several Google mailboxes?
Yes — several mailboxes per workspace is the normal case, each with its own role, signature, autonomy and health. The uniqueness check exists to stop the *same* identity being connected twice, which used to be possible when a row carried an empty workspace stamp.
What happens to mail that arrived before I connected?
The first sync starts from an empty cursor and fetches history from the provider; how far back that reaches is the provider's decision rather than Connect's. Catching up after a Gmail history gap covers the case where the provider cannot serve the range asked for.
Does Connect need to write to my Gmail?
Only to mark read state and apply labels, and that write-back is deliberately allowed to fail without blocking a reply. A missing label means the write-back failed, not that the reply did.