# inbox-to-action [![CI](https://github.com/tarunlnmiit/inbox-to-action/actions/workflows/ci.yml/badge.svg)](https://github.com/tarunlnmiit/inbox-to-action/actions/workflows/ci.yml) [![codecov](https://codecov.io/gh/tarunlnmiit/inbox-to-action/graph/badge.svg)](https://codecov.io/gh/tarunlnmiit/inbox-to-action) [![PyPI version](https://img.shields.io/pypi/v/inbox-to-action)](https://pypi.org/project/inbox-to-action/) [![Python 3.11+](https://img.shields.io/badge/python-3.11+-blue)](https://www.python.org/) [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE) [![GitHub stars](https://img.shields.io/github/stars/tarunlnmiit/inbox-to-action?style=social)](https://github.com/tarunlnmiit/inbox-to-action) Published on [PyPI](https://pypi.org/project/inbox-to-action/) and listed on the [Official MCP Registry](https://registry.modelcontextprotocol.io) (`io.github.tarunlnmiit/inbox-to-action`), [Glama](https://glama.ai/mcp/servers/tarunlnmiit/inbox-to-action) (deployable release, Quality A), and [Smithery](https://smithery.ai/servers/tarungupta-y12/inbox-to-action) (MCPB bundle). Registry manifests (`server.json`, `glama.json`) ship in the repo. [![inbox-to-action MCP server](https://glama.ai/mcp/servers/tarunlnmiit/inbox-to-action/badges/card.svg)](https://glama.ai/mcp/servers/tarunlnmiit/inbox-to-action) > One command. Your inbox triaged, summarized, drafted, and turned into tasks โ€” in a single agentic pass. ## Install ```bash pip install inbox-to-action # or: pipx install inbox-to-action uvx inbox-to-action run --mock # zero-install trial (uv) pip install 'inbox-to-action[mcp]' # + MCP server for Claude Code docker run --rm ghcr.io/tarunlnmiit/inbox-to-action # MCP server (stdio) ``` Try it with zero setup: `inbox-to-action run --mock` (bundled sample inbox). ๐Ÿ“– **Full documentation โ†’ [docs/](docs/README.md)** โ€” [install](docs/01-install.md) ยท [providers](docs/02-providers.md) ยท [Gmail OAuth](docs/03-gmail-oauth.md) ยท [multi-account](docs/04-multi-account.md) ยท [integrations](docs/05-integrations.md) ยท [MCP & Skill](docs/06-mcp-and-skill.md) ยท [config](docs/07-config-and-triage.md) ยท [troubleshooting](docs/08-troubleshooting.md) ยท [testing checklist](docs/09-testing-checklist.md). Quick version: [SETUP.md](SETUP.md).

inbox-to-action โ€” one command triages the inbox into a report, drafts, and tasks

--- ## Why this exists Most people process their inbox with **four** separate tools: an email client to read, a task manager to capture to-dos, a calendar to block time, and (increasingly) an AI summarizer to make sense of long threads. Every message gets handled four times. `inbox-to-action` collapses all four into **one agentic pass**. Run one command and get a unified triage report, drafted replies saved to Gmail, and extracted tasks โ€” without ever leaving the terminal, and **without ever sending an email automatically**. ## ๐Ÿ”’ Drafts only โ€” never sends This tool **cannot send email**. It requests only the Gmail `readonly` + `compose` scopes; there is no `send` scope and no send API call anywhere in the codebase (enforced by a test). Replies are saved as **Gmail drafts** for you to review and send. Email bodies flow into the LLM prompt, so a hostile email could try to steer its own classification or a drafted reply (prompt injection). Because every draft is saved for **human review** and nothing is ever sent automatically, the worst case is a draft you choose not to send. See [SECURITY.md](SECURITY.md). ## ๐ŸŒ What leaves your machine `inbox-to-action` reads your email. **Where your email content goes for classification depends on the LLM provider you pick** โ€” and the **default (`openrouter`) is a cloud provider**, so an out-of-the-box run sends your subjects + bodies to a third party. | `PROVIDER=` | Email content goes to | Key | |---|---|---| | `ollama` | **Nowhere โ€” fully local** ๐Ÿ”’ | none | | `claude` / `host` | Your existing Claude Code / Anthropic session (keyless) | none | | `openrouter` **(default)** ยท `openai` ยท `nim` ยท `anthropic` | **Third-party cloud** โ˜๏ธ | API key | **Want privacy?** Use `ollama` (local) or `claude` (keyless) so nothing is transmitted to a third party. `--telegram` / `--todoist` also push subjects/tasks off-box (opt-in). Full breakdown โ†’ **[PRIVACY.md](PRIVACY.md)**. > Note: `triage-report.md` and `tasks.md` are written to your working directory and > contain private email content. If you run inside a git repo, add them to `.gitignore`. ## What it does 1. **Fetches** unread email from Gmail (last 24h by default). 2. **Classifies** each into `action_needed` ยท `fyi` ยท `newsletter` ยท `noise`. 3. **Summarizes** long threads (>500 words) into two lines. 4. **Extracts** tasks with deadlines โ†’ local `tasks.md` (optional Todoist via `--todoist`). 5. **Drafts** replies for `action_needed` mail โ†’ saved as Gmail **drafts**. 6. **Flags** emails that need a calendar block. Final output: a single **`triage-report.md`** with a section per category, drafted-reply previews, a tasks summary, and a calendar list. ## Architecture โ€” the agent loop The model's own classification of each email drives which tools fire next โ€” the pipeline is **not** hardcoded. The same tool functions back the CLI agent and the MCP server. ```mermaid flowchart TD CLI[main.py ยท typer] --> AGENT[agent.py ยท agentic loop] AGENT -->|model picks tools per email| T1[classify_email] AGENT --> T2[summarize_thread] AGENT --> T3[extract_tasks] AGENT --> T4[draft_reply โ†’ Gmail draft] AGENT --> T5[flag_for_calendar] T1 & T2 & T3 & T4 & T5 --> LLM[llm_client.py ยท pluggable providers] LLM --> P1[OpenRouter / Ollama / NIM / OpenAI
OpenAI-compatible HTTP] LLM --> P2[Anthropic ยท official SDK ยท keyless via ant auth login] AGENT --> REPORT[report.py โ†’ triage-report.md] ``` ``` fetch โ†’ for each email: classify โ”€โ”ฌโ”€ action_needed โ†’ extract_tasks + draft_reply + flag_calendar โ”œโ”€ fyi / newsletter / noise โ†’ record only โ””โ”€ (long thread) โ†’ summarize โ†’ render triage-report.md ``` ## Quick start (2 minutes) ```bash git clone https://github.com/tarunlnmiit/inbox-to-action.git && cd inbox-to-action python3 -m venv .venv && source .venv/bin/activate pip install -e '.[mcp]' # installs the `inbox-to-action` command cp .env.example .env ``` This installs an `inbox-to-action` console command (and the `python -m inbox_to_action.mcp_server` entry point used by Claude Code / Glama). ### Free-first: run end-to-end on zero spend Pick whichever keyless/free path you like โ€” all run the full pipeline at no cost: **Option A โ€” `claude` CLI (keyless, fastest; uses your Claude Code login):** ```bash PROVIDER=claude inbox-to-action run --mock # no API key; needs `claude` on PATH ``` **Option B โ€” Ollama (truly keyless, fully local):** ```bash ollama serve # in another terminal ollama pull llama3.1 PROVIDER=ollama inbox-to-action run --mock # uses bundled sample inbox ``` **Option C โ€” OpenRouter free model (free signup key):** ```bash # put OPENROUTER_API_KEY in .env (free models, $0 spend) inbox-to-action run --mock # default PROVIDER=openrouter ``` **Option D โ€” inside Claude Code (keyless, Claude Code is the LLM):** see below. `--mock` uses the bundled sample inbox so you can see a full report with **zero Gmail setup**. Drop `--mock` once you've authorized Gmail. Free OpenRouter models are often rate-limited; the client auto-rotates a fallback list and retries with backoff. ### Real inbox ```bash # 1. Create OAuth credentials in Google Cloud Console (Desktop app), # download client_secret.json into the project, then: inbox-to-action auth # one-time consent (read + compose only) inbox-to-action run --since 24h --no-drafts # safe first pass: report only, no writes inbox-to-action run --since 24h # triage the last day (creates Gmail drafts) inbox-to-action run --since 3d --max 40 --todoist ``` - `--no-drafts` โ€” classify, summarize, extract tasks, write the report, but create **no** Gmail drafts. Recommended for a first run. - `--max N` โ€” cap emails per account (default 25) to bound cost/volume. - Automated **no-reply** senders (security alerts, notifications) never get a drafted reply โ€” the report notes them instead. ### Telegram summary (`--telegram`) Push a concise summary to your phone after each run โ€” counts, action-needed subjects (with draft-ready status), extracted tasks, and a link to your Gmail Drafts. ```bash # 1. In Telegram, message @BotFather โ†’ /newbot โ†’ copy the bot token. # 2. Message your new bot once (say "hi"), then open: # https://api.telegram.org/bot/getUpdates โ†’ copy "chat":{"id": ...}. # 3. Put both in .env: # TELEGRAM_BOT_TOKEN=... TELEGRAM_CHAT_ID=... inbox-to-action run --since 24h --telegram ``` Off by default (opt-in flag). A send failure never breaks the run. **Privacy:** this sends email subjects + extracted tasks to Telegram's servers (into your own chat). It's notification only โ€” it never sends email. ### Multiple accounts (Gmail + Google Workspace) Declare accounts in `config.json` โ€” one merged report, each email tagged with its account. Personal Gmail and Workspace both use the Gmail path (Workspace may need your admin to allow the OAuth app). ```json { "accounts": [ { "id": "personal", "kind": "gmail", "label": "Personal Gmail" }, { "id": "work", "kind": "gmail", "label": "Workspace" } ] } ``` ```bash inbox-to-action auth --account personal # authorize each account once inbox-to-action auth --account work inbox-to-action run --since 24h # fetches + triages across all accounts ``` Multiple personal Gmail accounts can reuse one `client_secret.json` โ€” each gets its own cached token (`~/.config/inbox-to-action/tokens/.json`). With no `accounts` block, the tool uses a single default Gmail account (backwards compatible). ## Use inside Claude Code (keyless) When run inside Claude Code, **Claude Code is the LLM** โ€” no provider key needed. Two integration paths ship in this repo: ### MCP server Exposes IO-only tools (`fetch_emails`, `save_gmail_draft`, `append_tasks`, `write_report`). Claude Code does the classify/summarize/extract/draft reasoning itself and calls these. ```bash # after `pip install -e '.[mcp]'` claude mcp add inbox-to-action -- python -m inbox_to_action.mcp_server ``` This is the same stdio server that MCP registries (e.g. [Glama](https://glama.ai)) build from the bundled `Dockerfile` (`CMD python -m inbox_to_action.mcp_server`). ### Skill Copy `skills/inbox-to-action/` into your Claude Code skills directory, then type `/inbox-to-action`. The skill instructs Claude Code to fetch, reason, draft, and write the report โ€” keyless. ## Anthropic (keyless via `ant auth login`) The Anthropic provider uses the official SDK with a zero-arg client, so it picks up your `ant auth login` OAuth profile โ€” **no `ANTHROPIC_API_KEY` required**: ```bash ant auth login PROVIDER=anthropic inbox-to-action run --mock # default model: claude-opus-4-8 ``` ## Configuration All keys live in `.env` (`.env.example` is committed). Switch providers with `PROVIDER`: `openrouter` (default) ยท `ollama` ยท `nim` ยท `openai` ยท `anthropic` ยท `claude` ยท `host`. ### Configure triage (make it *yours*) The default buckets are generic โ€” newsletters and job alerts are treated as no-action. Override that with `config.json` (copy `config.example.json`). Two layers: - **`rules`** โ€” deterministic `field โ†’ category` overrides applied **before** the LLM (fast, free, exact). First match wins. `field` โˆˆ `sender | subject | body | any`. - **`triage_instructions`** โ€” freeform guidance injected into the classifier prompt for nuance the model interprets. ```json { "triage_instructions": "I'm job hunting in ML/AI โ€” treat relevant job alerts as action_needed.", "rules": [ { "field": "sender", "contains": "hirist.tech", "category": "action_needed" }, { "field": "subject", "contains": "invoice", "category": "noise" } ] } ``` ```bash cp config.example.json config.json # edit to taste (config.json is gitignored) inbox-to-action run --since 24h # auto-loads ./config.json inbox-to-action run --config /path/to/other.json ``` Quick override without a file: `TRIAGE_INSTRUCTIONS="treat job alerts as action_needed"`. ## Tests ```bash pytest --cov=. # 100+ tests, ~89% coverage, incl. the never-send security test ``` ## Docs - **[docs/](docs/README.md)** โ€” full guides with screenshots: install, every LLM provider, Gmail OAuth, multi-account, integrations, MCP & Skill, config, troubleshooting, testing checklist. - [SETUP.md](SETUP.md) โ€” 5-minute quickstart. - [PRIVACY.md](PRIVACY.md) โ€” what leaves your machine, per provider. - [SECURITY.md](SECURITY.md) โ€” never-send invariant + vulnerability disclosure. - [CHANGELOG.md](CHANGELOG.md) โ€” version history. - [CONTRIBUTING.md](CONTRIBUTING.md) โ€” dev setup + the never-send rule. - [CLAUDE.md](CLAUDE.md) โ€” project map for Claude Code. ## Built with This project demonstrates the contract skills: - **Agentic orchestration** โ€” model-driven, per-email tool selection (no hardcoded pipeline). - **Function calling** โ€” typed tool schemas (`agent.TOOL_SCHEMAS`) shared by the CLI agent and MCP server. - **Multi-API integration** โ€” Gmail + LLM + Todoist in one flow. - **Pluggable LLM providers** โ€” one `llm_client` swaps OpenRouter / Ollama / NIM / OpenAI / Anthropic. - **Claude Code integration** โ€” first-class MCP server **and** Skill, both keyless. ## License MIT โ€” see [LICENSE](LICENSE).