This integration is for self-hosted Cognee deployments — you create and own the Google Cloud OAuth client. It is the server-side, multi-user counterpart to calling
gmail_source() from Python yourself: the same connector runs underneath, but Cognee holds the OAuth credential instead of a local token.json.v1.6.0 and is not tagged yet, so it needs a dev checkout until the next release.
What It Does
- One Google account, one Cognee user. Whoever completes the consent owns the credential, and the mailbox is indexed into that user’s memory. Personal and Workspace accounts take the same path and are told apart by the hosted-domain claim (
account_type: workspaceorpersonal). - Read-only. The requested scopes are
openid email https://www.googleapis.com/auth/gmail.readonly— Cognee never modifies the mailbox. - Its own grant. Like Google Drive, Gmail is a separate provider with its own settings and its own consent. The authorize URL does not fold its grant together with Drive’s, so disconnecting one never revokes the other.
- Opt-in by label. A new connection selects nothing. No mail is read until you choose labels and start a sync.
- Incremental and deletion-aware. The first sync backfills the selected scope and records Gmail’s
historyId; later syncs fetch only what changed, and messages that are deleted, trashed or move out of scope are forgotten.
Prerequisites
-
A Cognee backend on
dev— see the version note above. -
The
gmailextra installed on that backend:It pulls ingoogle-api-python-client,google-authandgoogle-auth-oauthlib, which the sync builds its Gmail client with. OAuth and label listing work without them, so a backend missing the extra connects fine and then fails its first sync with anImportError. - A Google Cloud project with the Gmail API enabled.
Setup
1. Create the Google Cloud OAuth client
In the Google Cloud Console, enable the Gmail API, then:2. Configure Cognee
Add the client’s values to your backend’s.env:
Stored tokens are encrypted with the integrations framework’s credential key, the same
INTEGRATION_CREDENTIALS_KEY (or INTEGRATION_CREDENTIALS_KEYS keyring) described on the Google Drive page.
The settings are checked when they are used, not at startup, so a backend without them still boots. POST /authorize answers 503 (gmail integration is not configured on this server.) until they are set. Restart the backend after changing .env.
3. Connect
/authorize, within 10 minutes, and read the result from the redirect to <GOOGLE_GMAIL_FRONTEND_BASE_URL>/integrations?gmail=<outcome>. The outcomes (connected, cancelled, error_invalid_state, error_already_connected, error_exchange_failed) and the split-domain cookie caveat are described under Google Drive → Connect.
In the local Cognee UI, a Gmail card sits under Data sources on the Integrations page, alongside Google Drive. It drives this same API: connect, choose labels, Save selection, then Refresh to sync.
Choosing Labels
List the account’s labels:id, its name, and attributes with the label type (system or user) and messages_total (usually null, because Gmail’s label listing omits message counts). Persist a selection with PUT:
/labels is an alias for the generic /resources path on both verbs. The selection has three states:
Changing the label set makes the next sync start with a full backfill of the new scope, so older messages that now match are picked up. Reordering the same labels keeps the incremental cursor.
Narrowing the selection deletes data. The backfill after a label change forgets every previously synced message outside the new scope, from the graph, vector and relational stores alike. A sync that fails midway forgets nothing, so a transient error never takes mail with it. Setting the selection to
[] and syncing clears everything the account has indexed; so does disconnecting with delete_data=true.
Syncing and Status
Connecting does not start a sync. Start one after choosing labels:gmail_source() to remember() with write_disposition="merge", primary_key="id" and no row cap, so a re-sync updates messages instead of duplicating them. It runs with self_improvement=False, so no whole-graph improve() pass fires on each refresh.
- Only the first sync reads the whole scope. Later syncs ask Gmail’s History API for changes since the stored
historyId. If that cursor has expired (Gmail keeps about a week of history), the sync falls back to a full backfill. - Transient Gmail API errors are retried — each Gmail API call retries up to 6 times before a failure counts against the sync.
- One sync per account at a time. While a sync is running, another
/synccall returns the same{"accepted": true}and does nothing. The guard lives in the API process, so a deployment with several workers needs its own queue or lock. - Nothing refreshes on its own. There are no Gmail webhooks and no built-in scheduler. Call
/syncagain, or from your own scheduler, to pick up new mail.
gmail_<email-slug>_<hash>. The slug is built the same way as for Google Drive datasets; the hash is the first 10 hex characters of the SHA-256 of the Google account id.
GET /api/v1/integrations/gmail/connection returns the same fields as the Drive connection state: connected, accountLabel, datasetId, storedItems (items actually stored in the dataset), syncStatus (ok, degraded, syncing), lastSyncedAt and syncCounts. Gmail’s syncCounts carry scanned, skipped, failed and deleted, plus skipped_unavailable (a message that vanished before it could be fetched), failed_message_fetch, failed_ingestion and failed_rate_limit.
Reconnecting an account keeps its stored label selection. Only a first-time connection starts with [].
Token Lifecycle and Disconnect
Tokens are handled exactly as for Google Drive: the access token is refreshed 5 minutes before it expires, and aninvalid_grant on refresh (access revoked at myaccount.google.com/permissions) revokes the connection locally so it stops reading as connected.
gmail disconnected, but its data could not be deleted). The connection is already gone at that point.
Limitations
- Labels intersect. There is no way to select “INBOX or Label_123” in one connection.
- No automatic refresh.
/syncis the only thing that updates memory. - One Cognee user per Google account. Connecting an account that another user already holds is refused with
error_already_connected. gmail.readonlyis a restricted scope, so public distribution needs Google’s OAuth verification.
Related
Google Drive
The sibling Google OAuth integration, with the same connect flow, status fields and token handling.
Gmail from Python
Call
gmail_source() directly with a local OAuth token, without the integrations API.