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.
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.
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.
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.
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.
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_tokensrebuilds the credential dictionary and assigns it back to the mailbox'sconfig. 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.