Skip to main content
A minimal guide to turning your Gmail inbox into Cognee memory. 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.
This reads the content of your email. Nothing is fetched until you run the script. Scope what you ingest with label_ids, keep token.json private, and use a dedicated dataset so you can wipe it with a single cognee.forget.

Before You Start

  • Complete Quickstart to understand basic operations
  • Ensure you have LLM Providers configured (LLM_API_KEY in .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.json next to the script, or point GMAIL_CREDENTIALS_PATH at it (GMAIL_TOKEN_PATH sets where the token is cached, default token.json). The first run opens a browser to consent

Code in Action

What Just Happened

Step 1: Configure the Sync

These are the routing options for 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

The sync pulls the messages into the 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 same gmail_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_results and finished, it saves Gmail’s historyId as 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.
Cognee then builds the graph only for the messages that are new or changed. Messages it already processed are skipped, because 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 cursor only carries over when:
  • the previous sync ran without max_results and finished. An interrupted sync saves nothing, so the next one starts over.
  • later syncs use the same dataset_name and label_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

Drop max_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.
Once it finishes, every later sync fetches only what changed, as described in Syncing Again.

Sync Behavior

  • Read-only access: the connector requests only the gmail.readonly scope and never modifies your mailbox. The OAuth token is cached at token.json with owner-only (0600) permissions.
  • Changing labels: passing a different label_ids selection 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). A run_in_background=True run 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 options

Forget

Wipe the Gmail dataset when you are done