gmail_source() returns a dlt resource you pass straight to remember(): the first sync loads the messages in a label, later syncs fetch only what changed, and messages you delete in Gmail are forgotten on the next sync.
Before You Start
- Complete Quickstart to understand basic operations
- Ensure you have LLM Providers configured (
LLM_API_KEYin.env) - Read dlt Integration for how structured sources flow through
remember() - Install the Gmail extra:
pip install "cognee[gmail]" - In the Google Cloud Console, enable the Gmail API, configure an OAuth consent screen (add yourself as a test user), and create an OAuth 2.0 Client ID of type Desktop app
- Save the downloaded client-secret JSON as
credentials.jsonnext to the script, or pointGMAIL_CREDENTIALS_PATHat it (GMAIL_TOKEN_PATHsets where the token is cached, defaulttoken.json). The first run opens a browser to consent
Code in Action
What Just Happened
Step 1: Configure the Sync
remember(); pass the same ones on every later sync. write_disposition="merge" upserts messages by their Gmail id instead of replacing the whole dataset on each sync. max_rows_per_table=0 only makes the intent explicit: Gmail is a document source, and Cognee always reads a document source’s whole staging table, so forget-on-delete compares against the entire synced inbox whatever this or DLT_MAX_ROWS_PER_TABLE says. incremental_loading stays at its default (True), so a later sync only builds the graph for new or changed messages, and self_improvement=False skips the automatic Improve step after each sync.
Step 2: Build the Gmail Source
gmail_source() authenticates with your OAuth client secrets and returns a dlt resource scoped to the INBOX label. If credentials.json is missing, the script prints a message and exits; the setup steps it points to are the ones in Before You Start. max_results=25 loads only the 25 newest messages so the demo runs quickly. To load everything, see Loading Your Whole Inbox.
Step 3: Sync and Ask Your Inbox
gmail_inbox dataset and builds the graph, which you then query with recall() using GRAPH_COMPLETION, restricted to that dataset. answer[0].text is the generated answer. source.cognee_sync_stats counts what this sync did: scanned messages fetched from Gmail, deleted messages forgotten, and skipped / failed fetches.
Syncing Again
To pick up new mail, run the samegmail_source() and remember() again later, for example once a day. You don’t choose how to sync; the connector decides from what it saved last time:
- No saved cursor (the first sync): it lists the label from the newest message down and fetches each one. If the run wasn’t capped with
max_resultsand finished, it saves Gmail’shistoryIdas a cursor. - Saved cursor: it asks Gmail’s History API what changed since the cursor. Only new or changed messages are fetched, and messages you deleted or trashed are forgotten. If nothing changed, no messages are downloaded.
incremental_loading is on by default. If you pass incremental_loading=False, every sync re-runs graph extraction on every message in the dataset.
- the previous sync ran without
max_resultsand finished. An interrupted sync saves nothing, so the next one starts over. - later syncs use the same
dataset_nameandlabel_ids. Changing the labels loads the new selection from scratch. - you don’t delete the connector’s saved state. The cursor lives in dlt’s pipeline state under
~/.dlt/pipelines/. - you sync at least about once a week. Gmail expires history after roughly a week; with an expired cursor the connector loads every message in the label again instead of stalling.
prune doesn’t reset a sync. cognee.prune wipes Cognee’s memory, but not the saved cursor or dlt’s staged copy of your mail (the dlt_database_<dataset> staging database). The next sync reads that copy back, so every previously synced message returns to memory and goes through graph extraction again. Leave the script’s prune calls out of real syncs.
Capped syncs never switch to incremental. A run with max_results always starts from the newest message and stops after that many. It doesn’t remember where it stopped, so the next capped run starts from the top again instead of continuing to older mail. Messages you already have are requested from Gmail again and count against your quota, but they aren’t processed into the graph again. A capped run also saves no cursor and never forgets deleted mail.
Loading Your Whole Inbox
Dropmax_results and let one sync run to completion:
- It takes a while. Gmail allows 6,000 quota units per user per minute, and fetching one message costs 20. The connector paces itself to 5,000 units a minute by default, about 250 messages, so 10,000 messages take around 40 minutes to download. Lower the budget with
gmail_source(quota_units_per_minute=...)if other apps use the same account. - Every message goes through graph extraction, so expect at least one LLM call per message. Try a smaller label first to estimate time and cost.
- Let it finish. An interrupted sync saves no cursor, so the next run starts from the beginning.
Sync Behavior
- Read-only access: the connector requests only the
gmail.readonlyscope and never modifies your mailbox. The OAuth token is cached attoken.jsonwith owner-only (0600) permissions. - Changing labels: passing a different
label_idsselection loads the new scope from scratch, including messages older than the last sync, and forgets mail that no longer matches. Reordering the same labels keeps the cursor. A capped or interrupted sync forgets nothing. - Foreground syncs only: deletions propagate only when
remember()runs in the foreground (the default). Arun_in_background=Truerun skips orphan cleanup.
Gmail Integration
Let users connect their mailboxes through Cognee’s API instead of a local token
dlt (Data Load Tool)
How dlt sources, merge syncs, and orphan cleanup work
Remember
All
remember() parameters, including the dlt optionsForget
Wipe the Gmail dataset when you are done