Everything Email Triage does — from LLM classification to calendar-aware meeting scheduling to the agent API.
Section 1
Each incoming message is classified by a local LLM into one of your categories. The classifier sees the sender, subject, and body; you write the category descriptions and the system uses them as the classifier's instruction.
12 system categories ship out of the box:
| Category | Description |
|---|---|
to-respond | Emails that need a reply from you |
action-required | Tasks, requests, deadlines |
fyi | Informational. No action needed. |
newsletters | Subscriptions and recurring content |
meetings | Meeting invites, agenda, scheduling |
meeting-request | Prose-only requests to schedule a meeting (no .ics) |
grant-related | Grant applications, reviews, funding communications |
self-event | Note-to-self about a personal event. Auto-creates calendar entry. |
| … plus invoices, comments, notifications, sponsor | |
Add as many as you want. Each category is defined by a short slug and a description; the description is what the LLM uses to decide whether a message belongs. Write it well, the classifier follows.
/categories — system defaults + user-added categories with operator-editable descriptions
Two types of fast-path rules bypass the LLM entirely:
List-Id header to a category. Sub-millisecond routing.
/rules — mailing-list, sender, and regex rules with 30-day hit counts
Two-level Redis cache keyed by (sender, normalized_subject, body_hash). Repeat emails skip the LLM call. Cache is disabled by default for HIPAA-flagged accounts — re-classification cost buys audit posture.
Section 2
After classification, messages route through configurable action chains. Actions are independent — any number can fire on a single message.
| Action | What It Does |
|---|---|
move | Move to a different folder. Per-category folder mapping. |
label | Apply the category as a label / Gmail label. |
notify | Slack, SMS-via-gateway, custom webhook. |
draft_reply | Generate a contextual draft reply, save to Drafts folder. |
suggest_meeting_times | Calendar-aware draft listing free slots. Auto-fires on meeting-request. |
accept_invite / decline / tentative | Calendar invite handling with iMIP-threaded replies. |
escalate | Forward to a configurable recipient. SMS-gateway pattern for phone delivery. |
mute | Suppress all classification + action firing. |
/accounts/<id>/routes — per-category action table. Different accounts can route the same category differently.
Independent of category. Match a sender, subject, body pattern → fire escalate / webhook / mute. Useful for "alert me immediately when X arrives" patterns that don't fit a category.
Section 3
Two digest features that solve the two recurring inbox problems every professional has: "I subscribe to too many newsletters to read" and "I've handed my inbox to an AI and now have no idea what it's doing."
Target user: the knowledge-work professional whose job requires staying current on a high-volume subscription feed. The research clinician subscribed to journal alerts + NIH funding announcements + IRB digests + society newsletters + sponsor communications. The financial analyst tracking 12 industry briefings. The consultant signed up for every category-defining substack in their vertical.
For someone like this, newsletter overload is a 60-to-90-minute-per-day problem. Each newsletter has 2–4 articles worth a 30-second scan; only 5–10% of those articles deserve a real read. The current state-of-the-art is Cmd+A → Archive — losing the signal entirely.
The newsletter digest replaces that with one inbox entry at a scheduled time (typically 7 AM local, operator-configurable):
Concrete outcome. A research clinician with 30 newsletter subscriptions averaging 3 articles each goes from "120 inbox entries the system can't help me with" to "one digest with 90 article cards I can scan in 5 minutes." The 5–10 articles worth a full read get a click-through; the other 80 are dismissed by closing the digest. Daily time saved: 60–90 minutes.
newsletters but can bundle any category (e.g. team-internal, comments for GitHub-style notifications)Each schedule slot has an independent enable/disable toggle and a "Run now" button for previewing without waiting 24 hours.
/accounts/<id>/edit on the Digests tab — Daily Activity, custom digests (Morning Newsletter, Weekly Wrap), per-slot Preview and Send-test-now buttons
The HTML body of a generated newsletter digest as it arrives in your inbox — section headings per source, article cards with headlines, 1–2 sentence summaries, and "Read more" links back to the original article URLs. The visual "30 emails → 1 digest" outcome:
A daily 7 AM newsletter digest — 12 articles across 5 sources (NIH funding, NEJM, AHA, STAT News, JAMA), each with a headline + summary + "Read more" link
The "set it and forget it" anti-pattern is real: an AI quietly classifies + drafts + moves mail for weeks, the operator's confidence drifts toward "I have no idea what it's doing," trust erodes, eventually the operator pulls the plug. The triage-activity digest is the antidote — every day, the operator gets a complete record of what email-triage did with their mail.
Daily summary delivered to the account's own mailbox at the scheduled time (typically end-of-day, 6 PM local — operator-configurable):
The system never feels like it's acting on your behalf without your knowledge. Every classification, every action, every draft is in the digest. For HIPAA-regulated environments this doubles as a lightweight audit surface — the compliance officer reviews the operator's last 5 daily digests instead of querying the access log.
Section 4
The draft_reply action generates a contextual response using your local language model. The draft lands in your Drafts folder. Nothing is ever sent automatically.
Sender, subject, body — the obvious context.
Distilled from your sent mail. Tone, greeting style, signoff conventions, common phrases, average reply length. Default window: last 50 messages, operator-adjustable up to 250+.
Per-account vector index of past sent mail. Few-shot examples shown to the LLM. Edits you make to drafts are weighted 1.3× over ordinary sent mail — the system learns your corrections.
A drafted reply in the email client's Drafts folder — threaded into the original conversation, ready for review
The system extracts measurable signals from your sent mail and shows you what it learned. Greeting patterns, signoff conventions, average length, common phrases — all visible and verifiable, not magic.
/profile/style-data — the distilled writing-style profile. Edits you make to drafts feed back into this index.
If your account sends from multiple addresses (work, personal, project-specific), the system maintains a separate writing-style profile per address. A draft to a message addressed to alias-a@ reflects how you write as alias-a.
When you edit a draft before sending, the final version is captured as a "high-signal example." Future drafts learn the way you actually want it to write — not the way it guessed.
Privacy posture. All drafting runs on the local LLM. The incoming message, the style examples, and the generated draft never leave your network. HIPAA-flagged accounts apply the same scrubbing rules to drafted body as to classification reasoning.
Section 5
The highest-leverage feature for anyone whose calendar is the bottleneck. When mail is classified as meeting-request, the system reads your calendar, picks slots, and drafts a calendar-aware reply.
/profile?tab=meeting — slot configuration, working-hours grid, OOO override
A generated meeting-suggestion draft — N slots across M days with proper EST/EDT labels
When a .ics-attached invite arrives, the accept_invite / decline_invite / tentative_invite actions respond on your calendar and draft an iMIP-formatted reply, properly threaded back to the organizer.
Pure-IMAP servers don't expose a calendar API. The system supports routing calendar operations for an IMAP account through a sibling Gmail / O365 account. Operator sets the surrogate via dropdown.
Section 6
| Provider | Push | Labels/Folders | Calendar API |
|---|---|---|---|
| Gmail | Cloud Pub/Sub (seconds) | Native labels | Google Calendar API |
| Office 365 | Graph subscription (seconds) | Native folders | Microsoft Graph |
| IMAP | IDLE (<2 sec, multi-folder) | SPECIAL-USE detected | via surrogate Gmail/O365 |
Supports Dovecot, Cyrus, Exchange-via-IMAP, Fastmail, Proton Bridge. TLS by default (port 993). Multi-folder watch per account with per-folder route overrides.
/accounts — multi-account view with real-time push status and per-account health indicators
Each account has its own tabbed configuration page covering provider credentials, push/poll cadence, watched folders, calendar role assignment, delegates, and integration endpoints. Every knob is in the UI — no config-file editing required.
/accounts/<id>/edit — provider credentials, aliases, time zone, push/poll, watch folders
Section 7
One install supports any number of users, each with their own accounts, categories, style profile, and meeting preferences. Three role tiers: Admin (install-wide config), User (owns own accounts), Delegate (granted view/triage/draft on another user's account).
Per-user isolation is enforced at the data layer. Delegate actions are audited with both actor and account-owner stamped. The HIPAA §164.312(b) audit gate distinguishes owner self-access from delegate access — the former is a §164.502(a) self-disclosure carve-out and isn't audited as PHI access; the latter writes an audit row every time.
/users — user management, role assignment, and delegate grants visible on the same surface
Section 8
Every operational signal is exposed through the admin UI — classifier latency, cache hit ratio, embedding metrics, supervised-task state, push watcher health, ingestion rollups. JSON for machine consumption (Nagios, Datadog, Prometheus); HTML for the admin stats page.
/admin/stats — operational signals dashboard showing the system is healthy at a glance
Tabbed configuration page covers ingestion, classifier model, integrations (Redis cache, webhooks), AI backends, TLS / security, and BAA acknowledgments. Operator-controlled, audit-logged, no config-file editing.
/config — install-wide configuration tabs
The runtime container image is ~250 MB compressed, Debian-13-slim with Python 3.12, non-root user, signed with cosign and SLSA-3 provenance. CPU-only PyTorch wheels (the GPU is reserved for the Ollama chat-model host). Fast pulls, fast CI, fits under GitHub Releases' 2 GiB asset cap for air-gap tarballs.
The embedding backend (PyTorch CPU runtime, sentence-transformers, all-MiniLM-L6-v2 model — ~600 MB total) is not baked into the image. On first admin setup, the operator clicks [Install now] on the AI Backends config page; the installer fetches each manifest-listed file over HTTPS, verifies SHA-256 against a pinned manifest baked into the image, and pip installs into the persistent volume at /app/data/runtime-deps/.
Operators who choose Ollama for embedding (rather than the in-process CPU backend) skip the local install entirely; their embed calls go to the same Ollama host as the chat model.
For organizations on disconnected networks — classified environments, OT segments, regulated research enclaves — air-gap installs use scripts/download-embedding-bits.sh on a connected machine to produce a hash-pinned tarball + SHA-256 sidecar. Transfer the tarball to the air-gap host and sideload through the admin UI. Sideload runs the same hash verification as the auto-download path — operator-staged bytes are not trusted; a bit-corrupted tarball or a tampered air-gap drop trips the same refusal as a poisoned PyPI mirror.
Image and source. Container: ghcr.io/unlimited-data-works-llc/email-triage. Source: github.com/Unlimited-Data-Works-LLC/Email-Triage (Apache 2.0). Verification recipe in the repo's docs/install.md.
A 30-minute call to scope deployment, integration, and tier fit.
Schedule a Demo