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.POST messages/batch— fetch up to 100 messages in one request. Critical for sync performance.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).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.
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:
InstalledFlowAuthenticatorwith browser redirect to localhost. Tokens stored at~/.local/share/mxr/tokens/{token_ref}.json. - Bundled OAuth credentials — mxr ships with its own client ID/secret (compile-time env vars
GMAIL_CLIENT_ID,GMAIL_CLIENT_SECRET). Users can override in config. - Sync via
messages.list(paginated) +messages.batchGet(format=full)(batched). - Delta via
history.list?startHistoryId=N. - Threading: uses Gmail's
threadIddirectly. No JWZ needed.
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 (long process). mxr is in this position; users with their own credentials in config bypass.
- 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