Docs / Guides · updated

How do I triage my inbox with a self-hosted LLM?

Run simple, explainable rules on every message first, send only the ambiguous ones to a language model, and never let anything leave your machine as an outgoing reply until a person has read it. Keep a log of every decision so you can see why a message landed where it did. That design works with a small local model, stays predictable when the model is missing, and keeps your mail data on hardware you control.

The problem with "just let the AI read my email"

An inbox mixes very different things: invoices, newsletters, support questions, meeting requests, password alerts, angry customers. Sending all of it to a model and acting on the answer creates three problems:

  • Unpredictable labels. The same email can get a different category on two runs, which makes filters and reports unreliable.
  • Invented context. A model asked to "handle" an email may cite an invoice number, an order or a recipient that is not in the message.
  • Privacy. Hosted AI services see the full text of your mail, including customer data, bank notices and personal messages.

Triage needs to be boring and repeatable for most mail, with the model reserved for the cases where a rule cannot decide.

A practical approach you can apply today

You do not need to buy anything to use this pattern. It works with a short Python script, a cron job and a SQLite file.

1. Deterministic rules first

Write keyword and sender rules for the categories you see every day, applied in a fixed order so the first match wins:

  • Payment words (invoice, payment, overdue, due date) go to an invoice bucket.
  • unsubscribe in the body or no-reply in the sender goes to newsletter.
  • support or help in the subject goes to support.
  • Everything else goes to general.

Keep the category list closed. "Urgent" is a feeling, not a category: a payment reminder with a deadline is still an invoice. Put urgency in a separate priority field instead.

2. An LLM only for ambiguous mail

When no rule matches with confidence, or the message is long and mixed, pass it to a model with a strict prompt: the allowed categories, the exact JSON shape you expect, and a rule that it must not mention any ID, name or reference that is not in the input. If the model output does not parse, or the model is unavailable, fall back to the rule result and mark the message for review. The pipeline should never stall because the model is down.

3. Drafts, not sends

Let the system write a reply draft, but store it with an explicit "not sent" flag and put it in a review queue. The drafting prompt should forbid promises of payment, attendance or deadlines that the sender did not authorize. A human reads, edits and sends.

4. What to log

For each message, keep one JSON line with a timestamp, message or task ID, the step, the status (ok or error) and the duration. Store the final decision next to the evidence that produced it: which rule matched, or which model answered. Failed tasks should go to a dead-letter list you can replay after fixing the cause.

5. Privacy

Run the queue and the rules on your own machine. Mask emails, phone numbers and account numbers before writing any log that keeps text. If you use a model, know where it runs: a model on your own hardware keeps the text local, a hosted assistant does not.

Agent Inbox Triage Pack

Agent Inbox Triage Pack is a runnable email-triage agent with a SQLite queue, retry with backoff (10 s, 60 s, 600 s), a dead-letter queue and replay. Its built-in handlers are classify_email, draft_reply, archive_email, forward_email and extract_task.

  • classify_email returns exactly one of invoice | support | newsletter | general and never emits urgent. Its default action is review.
  • The contract forbids inventing an invoice ID, client, message ID, attachment or forward recipient. Missing or ambiguous context means action=review.
  • Without an LLM CLI, the deterministic handlers run. With one connected, the agent runs the Executor prompt from PROMPTS.md and falls back to the deterministic handler if the CLI is installed but not logged in.
  • Commands from the README include python agent.py status, python agent.py run --deterministic and python agent.py replay --task t-ab12cd34.

Mail ingestion is cron, webhook or CLI; the README notes that an IMAP adapter is something you add.

Agent Personnel Pack

Agent Personnel Pack targets a personal inbox. Its email triage returns a priority, a category (FINANCES, AGENDA, SECURITE, NEWSLETTER, PERSONNEL), an action (read, reply, archive) and a short summary. Its reply drafting returns envoi_automatique: false with suggested actions such as reviewing before sending. It also handles document filing suggestions, calendar extraction that returns no event when a date is missing, and text anonymization into tokens like [EMAIL_1]. Its delegate_map.yaml template sets auto_execute: false on every route.

Agent Support Pack

Agent Support Pack triages a batch of support tickets into three buckets: auto_resolve, escalate and manual_review. Outages escalate first. A ticket from an unverified customer, or citing an order not in known_orders, goes to a human with MISSING_DATA in the reason. Only a verified customer whose problem is covered by the knowledge base is auto-resolved, and the triage never approves a refund. The no-LLM batch triage is the triage subcommand of agent.py, which takes a JSON payload of verified customers, known orders, knowledge base entries and tickets.

Agent Moderation v1

Agent Moderation v1 covers community messages, comments and review queues. It reads JSONL, scores each message against a JSON policy (spam, abuse, privacy, self-harm, threat, escalation) and records allow, review, hide or escalate in SQLite with the matched evidence. hide is a recommendation unless you wire enforcement, it does not delete source data, and human review remains required for escalations. Commands include review --action ACTION to list decisions for a human and python3 agent.py export --output moderation_report.json.

Shared across all four

Each pack runs offline in deterministic mode and ships llm_adapter.py, which calls a coding-agent CLI (Claude Code, Kimi Code or Codex) or a local Ollama server (PACK_LLM=ollama), with no API key. Each also keeps a local learning memory in ./.pack_memory/ that masks emails, phone numbers and card numbers before writing, and lets you mark results good or bad with python3 pack_memory.py feedback last ok.

Honest limits

  • Pick where the model runs. llm_adapter.py can call a coding-agent CLI (Claude Code, Kimi Code or Codex), which sends message text to that provider, or a local Ollama server, which keeps it on your machine. For mail that must stay local, set PACK_LLM=ollama (and PACK_OLLAMA_MODEL if you want a specific model), or run deterministic mode.
  • No mailbox connector is included in the inbox pack. You feed it messages.
  • The deterministic draft is a generic acknowledgement. Useful replies need a model and your review.
  • Keyword rules miss things. Expect to tune them on your own mail.
  • Nothing sends or deletes for you. That is deliberate, and it means a human step stays in the loop.

Where to start

  1. Export a week of mail and write the four-bucket rules above. Count how much they settle without a model.
  2. Add a model only for the leftovers, with a strict JSON prompt and a review queue.
  3. If you want working code instead of starting from scratch, begin with Agent Inbox Triage Pack for email, add Agent Support Pack for tickets or Agent Moderation v1 for community messages. Packs are $3 one-time. Browse them all at /packs and follow the quickstart to install one.

Packs used in this guide

Browse all packs by category →