Gmail API
Gmail API
Google's REST API for Gmail. Replaces IMAP for clients that want native access to Gmail-specific concepts: labels, threads, history-based delta sync, snippet, snooze. The standard for serious Gmail integrations.
Why not just use IMAP?
Gmail-over-IMAP is a leaky abstraction:
- Gmail uses labels; IMAP only knows folders. A message with three labels appears in three IMAP folders with the same Message-ID.
- "Move to label X" via IMAP doesn't add a label — it removes the source label. Wrong semantics.
- Gmail's
threadId(server-side threading) isn't visible. - Gmail's snippet (the preview text) isn't visible.
- History-based delta sync (
history.list) is much faster than IMAP's modseq scanning.
The REST API exposes the real semantics. mxr uses it for Gmail accounts; falls back to IMAP for everything else.
Core endpoints
GET messages/— list message IDs (paginated, with optionalq=query in Gmail-search syntax:from:alice is:unread).GET messages/{id}— fetch one message.format=fullfor headers + body;format=metadatafor just headers;format=rawfor the original RFC 5322 bytes.- Concurrent
GET messages/{id}chunks — mxr'sbatch_get_messageshelper fetchesformat=fullmessages in bounded concurrent chunks. It is an mxr helper, not a Gmail batch-get endpoint. GET labels/— list all labels (system + user).POST messages/{id}/modify— add or remove labels. The mutation primitive (no separate "move" or "archive" — those are label changes).POST messages/send— send an RFC 5322 message through the authorized Gmail API client. mxr uses this under the currentgmail.modifygrant rather than requestinggmail.sendseparately.GET history/— delta sync. Given astartHistoryId, returns all changes since (additions, deletions, label modifications).
Authentication
OAuth2 only. See OAuth for Email. Gmail's required scopes:
gmail.readonly— read messages and labelsgmail.modify— modify labels (no delete)gmail.compose— create draftsgmail.send— send messagesgmail.labels— manage labels
Permissions are coarse — there's no "read only this label" scope.
Current mxr uses gmail.readonly, gmail.modify, and gmail.labels. It relies on gmail.modify for Gmail API send calls rather than requesting gmail.send separately.
Delta sync via historyId
Each Gmail account has a monotonic historyId. Every change (new message, label edit, deletion) bumps it. The client stores the last seen historyId, then queries history.list?startHistoryId=N to get everything that happened since.
Output: a list of historyRecord entries, each describing message additions, deletions, label additions, label deletions. The client applies these deltas to its local store.
The catch: historyIds expire. Google keeps history for ~7 days (sometimes longer). If your client has been offline longer, the history endpoint returns 404, and you must reset to a full sync.
Mxr handles this in crates/sync/engine.rs: catches NotFound on Gmail cursor, resets to SyncCursor::Initial, retries.
Labels vs folders
Gmail's label model:
- A message has 0+ labels.
- System labels:
INBOX,SENT,DRAFT,TRASH,SPAM,STARRED,IMPORTANT,UNREAD,CHAT. - User labels: anything the user creates. Hierarchical via
/(Work/Reports/Q3). - Removing
INBOX= "archived" (still inAll Mail). - Adding
TRASH= "deleted" (gone in 30 days).
mxr's internal model captures this with LabelKind: System | Folder | User plus provider_id for the original Gmail label ID.
Watch / Pub-Sub for push notifications
Polling Gmail for changes is wasteful. Two push mechanisms:
POST watch— sets up a watch on a Pub/Sub topic. Google publishes to your topic when changes happen; your server receives them. Requires a Google Cloud Pub/Sub setup.- Push notifications via FCM — for mobile clients.
Mxr doesn't use these — it's local-only and a Pub/Sub setup would defeat that. mxr polls on an interval (configurable, default 60s).
What mxr does
Per crates/provider-gmail/src/:
- Library:
yup_oauth2for OAuth, custom REST client wrapper. - Auth:
yup_oauth2installed-app flow and device-code flow. Gmail tokens are stored keychain-first through mxr'sKeychainTokenStorage, with a private token-dir fallback for migration and noninteractive recovery. - OAuth client source: mxr v1 docs recommend BYOC, meaning a user-created Google OAuth Desktop app. Release builds may include bundled credentials from compile-time env vars
GMAIL_CLIENT_IDandGMAIL_CLIENT_SECRET, but that shared client is an unverified fallback. - Sync via
messages.list(paginated) + bounded concurrentmessages.get(format=full)through mxr'sbatch_get_messageshelper. - Delta via
history.list?startHistoryId=N. - Threading: uses Gmail's
threadIddirectly. No JWZ needed. - Search: mxr does not delegate normal search to Gmail's remote
q=. It parses Gmail-style syntax withmail-query, then executes over the local SQLite/Tantivy/semantic runtime. That gives useful muscle-memory parity without making Gmail the search source of truth. See Parser Parity Is Not Execution Parity.
Common pitfalls
- historyId expiration — handle the 404 by falling back to full sync.
- Rate limits — Gmail is generous (1B units per day for most apps) but bursting can throttle. Use
batchGetover per-message fetches. - Quota for OAuth verification — unverified Google OAuth apps are capped at ~100 users. To go beyond, submit for verification. mxr's bundled fallback is in this position; the official v1 docs point users to their own OAuth client so one shared unverified app is not the production bottleneck.
- Test accounts — Google Workspace accounts may have admin-imposed restrictions on which OAuth apps can connect.
- Two-factor auth — Gmail enforces app-specific passwords for IMAP if 2FA is on. Doesn't apply to OAuth, which is why mxr prefers OAuth.
Comparable APIs
- Microsoft Graph API — same shape for Outlook/Microsoft 365. Different scopes, different endpoints, similar concepts.
- Yahoo Mail API — exists but minimal.
- Fastmail JMAP — open standard (JMAP overview). Not Gmail's, but worth knowing.
See also
- IMAP — the alternative, with its impedance mismatch
- OAuth for Email — the auth flow Gmail API requires
- Email Internal Model — how mxr reconciles Gmail labels with IMAP folders
- How Email Actually Works — synthesis
- Mxr — concrete implementation
- Gmail API docs: https://developers.google.com/gmail/api