# Gmail API

The Gmail API is an HTTP interface to a Google mailbox, used instead of IMAP and SMTP. Two things differ from the classic protocols and both change how a client is written: synchronisation is driven by a **history ID** rather than a folder cursor, and a message has **labels** rather than living in one folder.

- **Status:** Reference
- **Audience:** both, developer
- **Last verified:** 2026-09-10
- **Canonical:** https://connectbyjbrh.com/docs/technology/gmail-api/

## History IDs, and the window that expires

Each mailbox carries a monotonically increasing history ID. A client stores the last one it processed and asks for the changes since — messages added, deleted, and labels applied or removed. That is a change feed rather than a state comparison, which is why incremental synchronisation over this API is so much cheaper than a folder scan.

1. Perform a full synchronisation once and record the mailbox's current history ID.
   - Result: You have a baseline and a cursor, in one call rather than two.
2. Ask for history since that ID, and process what comes back in order.
   - Result: Additions, deletions and label changes arrive as a list. Store the new ID only after the batch has been handled.
3. Handle the expiry case explicitly: a history ID that is too old is rejected.
   - Result: The correct recovery is a full resynchronisation, not a retry. A client that retries loops until somebody notices.

> **Careful** History is retained for a limited period, so a client that has been offline long enough loses its window. This is the Gmail equivalent of a UIDVALIDITY change, and like that case the danger is treating it as a transient error rather than as an instruction to start again.

## Labels are not folders

| Concept | IMAP | Gmail API |
|---|---|---|
| Location | One folder per message | Many labels per message; `INBOX` is a label |
| Read state | The `\Seen` flag | The `UNREAD` label, removed rather than set |
| Threading | Client-side, from `References` and `In-Reply-To` | Server-side `threadId`, computed by Gmail |
| Archive | Move to another folder | Remove the `INBOX` label; the message stays where it was |
| Delete | Flag and expunge | `TRASH` label, or a permanent delete that is genuinely permanent |
| Search | Server-side `SEARCH` | Gmail query syntax, which is considerably more expressive |

The consequence for a client is that "which folder is this in" has no answer, and code written around that question needs rewriting rather than adapting. Threading is the pleasant surprise: `threadId` is supplied by the server, so a client does not have to reconstruct conversations from reference headers and get it subtly wrong.

## Does Connect use the Gmail API?

**Used, and it is the route a Gmail mailbox takes.** Connecting a Google account is a Google OAuth flow, after which mail is fetched through the Gmail API rather than IMAP. History-based synchronisation is what `tenant_inbox.py` performs; the mailbox row carries the transport and the credentials, and `providers.build()` reads nothing else.

Two implementation details are worth stating because they are the ones that break quietly if handled casually:

**Token storage must be writable** — `oauth.store_tokens` rebuilds the credential dictionary and assigns it back to the mailbox's `config`. A read-only property there would silently drop every refreshed access token, and the mailbox would work until the first refresh and then stop.
**Write-back never blocks a reply** — Marking a message read and applying a label are Gmail write-back operations. If one fails, the reply that was already sent is unaffected. Coupling them would let a labelling error prevent a customer being answered, which inverts their relative importance.

Google OAuth is also the only way to sign in to Connect itself — there is no password login — but that is a separate flow from mailbox access and grants different scopes. Signing in does not connect a mailbox, and connecting a mailbox is not a sign-in.

## Quotas, failure and the shape of a good client

- **Quota is per-user and per-method, not per-request.** A cheap-looking loop that fetches messages one at a time can exhaust a unit budget while barely moving data.
- **A 429 or a 403 with a rate-limit reason is a back-off instruction**, and re-issuing immediately makes the window longer.
- **Batch and partial responses matter.** Ask for the metadata you need rather than the full raw message when you are building a list.
- **Revocation looks like an authorisation failure with no warning.** A person removing access in their Google account produces an error on the next call, not a notification.
- **Deleted-forever is forever.** The permanent delete has no trash stage, which is a good reason for an automated client never to call it.

The general shape that survives contact with production: treat the API as a change feed, store the cursor only after the work behind it is done, and keep the operations that modify the mailbox separate from the operations that answer a customer.

## Questions

### Is the Gmail API a replacement for IMAP and SMTP?

For a Google mailbox, it does the same jobs with a better change feed and richer search. It is not a general mail protocol — it works only against Gmail — so a product that supports arbitrary mailboxes still needs IMAP and SMTP alongside it. Connect carries all three transports for exactly that reason.

### What happens if a history ID is too old to use?

The request is rejected and the recovery is a full resynchronisation followed by recording the fresh history ID. Treating it as a retryable error produces a loop that never succeeds; treating it as a fatal error stops a mailbox that is perfectly healthy.

### Why keep write-back separate from sending?

Because they have very different consequences. A failed send means a customer was not answered. A failed label or read-marking means the mailbox looks slightly untidy. Making the second able to block the first would let the trivial failure cause the serious one.

## Related

- [IMAP](https://connectbyjbrh.com/docs/technology/imap/)
- [Microsoft Graph mail](https://connectbyjbrh.com/docs/technology/microsoft-graph/)
- [OAuth 2.0](https://connectbyjbrh.com/docs/technology/oauth/)
- [SMTP](https://connectbyjbrh.com/docs/technology/smtp/)
- [Why 'connected' is not enough to prove mailbox health](https://connectbyjbrh.com/research/mailbox-health-beyond-connected/)
- [Email in Connect](https://connectbyjbrh.com/docs/email/)

## What this page is based on

- Gmail API — users.history and synchronisation guide, https://developers.google.com/gmail/api/guides/sync
- Gmail API — labels, https://developers.google.com/gmail/api/guides/labels
- Connect channel source pack — docs-source/sources/CHANNELS.md §1, mailbox transports, the `config` setter and Gmail write-back
- Connect capability registry (docs-source/facts.py) — gmail_oauth, gmail_history_sync, gmail_writeback
