Skip to content

jiteshm17/email-triaging

Repository files navigation

Email Triaging

Fetches recent emails from Gmail and tags each with a category using a local LLM (Ollama). Applies the category as a Gmail label

What it does

  1. Fetch – Connects to Gmail (OAuth), lists recent messages in the inbox, and downloads each message’s headers and body.
  2. Tag – Sends subject + body to Ollama (OpenAI-compatible API) and gets a single category and short reason per email.
  3. Save – Writes a raw CSV of fetched emails and a tagged CSV with category and reason.
  4. Apply – For each tagged email, adds the category as a Gmail label

Setup

1. Python environment

python -m venv .venv
.venv\Scripts\activate   # Windows
pip install -r requirements.txt

2. Gmail OAuth

  1. In Google Cloud Console, create a project and enable the Gmail API.
  2. Create OAuth 2.0 credentials (Desktop app), download as credentials.json, and place it in the project root.
  3. On the first run, a browser window will open for you to sign in. After authorising, a token.json is created automatically — this stores your access token so you don't need to sign in again.

credentials.json is the app identity (safe to keep private, never changes).
token.json is your personal access token (auto-refreshed when it expires).
Both are listed in .gitignore and will never be committed.

3. Ollama

Install Ollama, then pull the default model:

ollama pull qwen2.5:14b

4. Gmail labels

In Gmail Settings → Labels, create labels whose names exactly match the category names defined in utils/prompts.py (e.g. TRANSACTION_ALERT, OTP_AND_VERIFICATION, ADS). The script prints which labels are missing on startup — any missing categories are still classified but the label won't be applied in Gmail.


Usage

Daily run — tag untagged Primary inbox mail

python run.py

Fetches Primary messages that still lack any of your triage category labels (read or unread), classifies with Ollama, applies labels. By default only the last 90 days are searched (newer_than:90d) so untagged mode does not pull your entire archive; use --since-days 0 for no limit. Pass --unread-only for the old behavior (unread Primary only). Pass --mark-read to clear UNREAD after labeling (the Airflow DAG uses this).

Flag Default Description
--since-days N 90 Only messages newer than N days (0 = no date limit)
--max N None (all) Cap how many messages Gmail returns for this run
--unread-only off Only unread messages (legacy Primary, or all tabs with --all-tabs)
--dry-run off Classify only, skip Gmail updates
--mark-read off After labeling, mark each message as read
--all-tabs off Untagged (or unread with --unread-only) across the whole inbox, not only Primary
--cache off Reuse today’s CSV instead of re-fetching
--model MODEL qwen2.5:14b Ollama model to use

Airflow on WSL2 — do I need to restart after every code change?

Usually no.

  • Changes to run.py, utils/, etc.: each task runs a new python … run.py process, so the next DAG run always uses the latest files on disk. Keep scheduler + webserver running; no restart.
  • Changes to dags/*.py: the scheduler re-parses DAG files periodically (often about every 30 seconds). Wait one cycle or trigger a new run. Still no need to kill both processes unless something is stuck.
  • Restart when: the scheduler/webserver crashed, you changed airflow.cfg, or the UI shows the scheduler heartbeat warning and logs confirm it died.

Optional helper (from WSL, project root):

chmod +x scripts/airflow_wsl.sh
./scripts/airflow_wsl.sh restart   # stop stale PIDs, start scheduler + webserver in background
./scripts/airflow_wsl.sh stop

About

Fetch Gmail inbox, tag each email with a category using a local LLM (Ollama), and optionally apply labels.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors