# 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.

- **Status:** Available
- **Audience:** both
- **Channels:** email
- **In the app:** #/mailboxes, #/inbox
- **Last verified:** 2026-09-10
- **Canonical:** https://connectbyjbrh.com/docs/workflows/connect-gmail-workflow/

## The chain from consent to first thread

1. Trigger — Add a mailbox on the Mailboxes screen, in either audience: the Owner's flow asks the same question a customer's wizard asks.
2. User event — you choose Google and are sent to Google's own consent screen; Connect never sees the password behind it.
3. 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.
4. Ingest — Google returns a grant, which `oauth.store_tokens` turns into the mailbox's `config`.
5. Canonical record — a mailbox row exists with a transport, sealed credentials, a role, a signature, its own autonomy and its own health verdicts.
6. Classification — the uniqueness check asks whether this Google identity is already connected, using the one query that can see rows an ordinary list cannot.
7. Knowledge, memory and rules — nothing yet; a new mailbox inherits the workspace and channel tiers until somebody sets an endpoint rule on it.
8. Autonomy and approval — the new endpoint gets no autonomy of its own, so the channel rule applies until you narrow it.
9. Action through a provider — the first sync calls the Gmail API with the stored grant and fetches from an empty cursor.
10. Result — threads on Conversations, a last-sync time on the row, and a health verdict that is more than 'connected'.
11. Relationship, timeline and memory — the people behind those messages resolve to persons and companies, so history exists before the first reply is written.
12. 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](/docs/troubleshooting/reconnect-required/) covers the way back.

> **Note** Both audiences use one implementation here. The Owner's Add-a-mailbox asks the same question the customer wizard asks, and the Owner's Google flow falls back to the platform's own client. A capability that exists for one audience and not the other is a defect in this codebase, not a plan.

## Proving the connection rather than assuming it

1. 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.
2. 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.
3. 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.
4. 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.

> **Careful** A green row is not a working mailbox. 'Connected' means the credential authenticated; it says nothing about whether mail is arriving, whether the bridge ran, or whether anything can be sent.

## 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](/docs/workflows/gmail-history-catchup/) 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.

## Related

- [Connecting Gmail](https://connectbyjbrh.com/docs/email/connect-gmail/)
- [Gmail OAuth scopes and why each is asked for](https://connectbyjbrh.com/docs/email/gmail-scopes/)
- [Mailboxes](https://connectbyjbrh.com/docs/email/mailboxes/)
- [Connecting an IMAP mailbox, end to end](https://connectbyjbrh.com/docs/workflows/connect-imap-workflow/)
- [Assigning a mailbox role, end to end](https://connectbyjbrh.com/docs/workflows/mailbox-role-assignment/)
- [Reconnect required](https://connectbyjbrh.com/docs/troubleshooting/reconnect-required/)
- [One implementation, two audiences](https://connectbyjbrh.com/research/two-audiences-one-implementation/)

## What this page is based on

- Connect source pack — channels, §1: mailboxes, providers, the `config` setter and the three mailbox defects (`docs-source/sources/CHANNELS.md`)
- Connect source pack — security: Google OAuth as the only sign-in, sealed provider credentials (`docs-source/sources/GENERAL.md` §10)
- Connect capability registry (`docs-source/facts.py`) — `gmail_oauth`, `gmail_history_sync`, `mailbox_health`
