Features

Everything Email Triage does — from LLM classification to calendar-aware meeting scheduling to the agent API.

The whole picture

One pass in. Routes, watches, topics, and digests out.

Every message flows through the same real-time path — ingest, classify, then routes, watch rules, and topic matching all act on that single classification pass (no second scan, no extra egress). Three kinds of digest run on their own schedule on top. The sections below walk each stage in detail; this is the map.

Detailed Email Triage pipeline: real-time ingest then classify, feeding routes, watch rules, and topic matching; plus scheduled newsletter, triage-activity, and topic/study digests. Local LLM by default, with an HMAC-chained audit row per action.

Section 1

Intelligent Classification

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.

Default Categories

12 system categories ship out of the box:

CategoryDescription
to-respondEmails that need a reply from you
action-requiredTasks, requests, deadlines
fyiInformational. No action needed.
newslettersSubscriptions and recurring content
meetingsMeeting invites, agenda, scheduling
meeting-requestProse-only requests to schedule a meeting (no .ics)
grant-relatedGrant applications, reviews, funding communications
self-eventNote-to-self about a personal event. Auto-creates calendar entry.
… plus invoices, comments, notifications, sponsor

Custom Categories

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 admin page

/categories — system defaults + user-added categories with operator-editable descriptions

Classification Hints

Two types of fast-path rules bypass the LLM entirely:

Rules editor

/rules — mailing-list, sender, and regex rules with 30-day hit counts

Caching

An email the system has effectively seen before doesn’t pay for a second AI look — it reuses the earlier decision, so repeat and near-duplicate mail is fast and cheap. On HIPAA-flagged accounts the cache stores no free text: the same reuse still applies, but the classifier’s free-text reason is stripped at write time and the cache key is hashed — so no PHI sits in the side-cache. (For your IT reviewer: a two-level Redis cache keyed on a hash of sender + normalized subject + body; on HIPAA accounts the free-text reason field is dropped at store time.)

Section 2

Automated Routing & Actions

After classification, messages route through configurable action chains. Actions are independent — any number can fire on a single message.

Available Actions

ActionWhat It Does
moveMove to a different folder. Per-category folder mapping.
labelApply the category as a label / Gmail label.
notifyMetadata-only alert (never the email body) fanned out to the outbound webhook system — Slack, PagerDuty, Opsgenie, Teams, Discord, or a custom signed endpoint.
draft_replyGenerate a contextual draft reply, save to Drafts folder.
suggest_meeting_timesCalendar-aware draft listing free slots. Auto-fires on meeting-request.
accept_invite / decline / tentativeCalendar invite handling with iMIP-threaded replies.
escalateForward to a configurable recipient. SMS-gateway pattern for phone delivery.
muteSuppress all classification + action firing.
Routes editor

/accounts/<id>/routes — per-category action table. Different accounts can route the same category differently.

Watch Rules

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.

Alerting & Integrations

The notify action and any watch rule fan an event out through the outbound event webhook system. Five receiver types ship with tuned default payloads; a sixth “custom” option delivers Email Triage’s own signed envelope to your own relay or SIEM.

TargetDelivered as
SlackIncoming-webhook message
PagerDutyEvents API v2 trigger
OpsgenieAlert API alert (GenieKey auth)
Microsoft TeamsIncoming-webhook MessageCard
DiscordWebhook message
custom{event, timestamp, data} envelope — verbatim, HMAC-signed

Each payload is a template you can edit per target ({{data.category}}, {{data.subject}}, {{data.flow_id}}, …), rendered through one shared code path so the wire shape can’t drift between targets.

Signed, gated, metadata-only. Give a target a shared secret and every POST carries an X-Signature-256 HMAC-SHA256 over the exact rendered bytes — and if that secret ever fails to decrypt, the delivery is dropped, never sent unsigned. External URLs are deny-by-default: the operator explicitly allows each off-host destination, and every send is egress-gated and idempotency-guarded against double-fire. Payloads carry metadata only — never the email body; on a HIPAA-flagged account the operator’s template is ignored entirely and the wire collapses to {event, category, timestamp, flow_id}. A webhook failure never breaks triage — the application log is the durable receipt.

Section 3

Digests & Periodic Summaries

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."

Newsletter Digest — for the high-subscription-volume professional

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):

  1. The digest action pulls every newsletter-category message from the lookback window (default 24 hours)
  2. The local LLM reads each newsletter body and extracts per-article structure: headline, 1–2 sentence summary, "read more" link
  3. Articles are grouped by source so each newsletter's content clusters together
  4. An HTML digest renders with section headings per newsletter, article cards beneath, and "Read more" buttons linking back to the original article URLs
  5. The digest delivers to the account's own inbox (or sends, per operator policy)

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.

Configuration (per-account, on the Digests tab)

Each schedule slot has an independent enable/disable toggle and a "Run now" button for previewing without waiting 24 hours.

Digest schedule editor

/accounts/<id>/edit on the Digests tab — Daily Activity, custom digests (Morning Newsletter, Weekly Wrap), per-slot Preview and Send-test-now buttons

What a rendered newsletter digest looks like

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:

Rendered newsletter digest

A daily newsletter digest — 13 articles across 5 sources (NIH funding, NEJM, AHA, STAT News, JAMA), each with a headline + summary + "Read more" link

Triage-Activity Digest — for the "I want to know what the AI is doing" user

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 convenient at-a-glance surface — the compliance officer can skim the operator's last few daily digests as a convenience over, not a replacement for, the audit log itself.

Compliance posture, locked in code

Delivery mechanics

Topic / Study Digest — for the clinical researcher tracking a study

Define a Topic — a study, project, or subject — with a few Subject keyword chips and the mailboxes it should watch. Email Triage collects every matching message across those mailboxes into one scheduled digest: Full, Differential (new since last), or both. Built for the PI following an IND across a sponsor inbox, an IRB inbox, and a coordinator inbox at once.

Section 4

Smart Reply Drafting

The draft_reply action generates a contextual response using your local language model — a real AI-generated reply body. The draft lands in your Drafts folder. Nothing is ever sent automatically.

Live today — and it sharpens as it learns you. The AI-written reply body is a live feature. The personalized voice is learned from your own sent mail: a standard mailbox drafts in a clean neutral professional voice until you run the one-time, operator-initiated “mine my sent mail” step — there is no silent background scraping of your outbox. HIPAA-flagged accounts never use your verbatim past mail at all: they distill only a privacy-scrubbed style descriptor, and drafting fails closed (no draft) on any credential or BAA error. Every draft is saved for your review — never auto-sent.

Three Signals Per Draft

The Incoming Message

Sender, subject, body — the obvious context.

Your Writing Style

Distilled from your sent mail once you run the one-time “mine my sent mail” step — tone, greeting style, signoff conventions, common phrases, average reply length. Default window: last 50 messages, operator-adjustable up to 250+. Before you mine, drafts use a neutral voice.

Recent Similar Replies

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.

Drafted reply

A drafted reply in the email client's Drafts folder — threaded into the original conversation, ready for review

Distilled Writing-Style Profile

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.

Writing-style profile

/profile/style-data — the distilled writing-style profile. Edits you make to drafts feed back into this index.

Multi-Alias Awareness

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.

Captured Feedback Loop

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

Calendar-Aware Meeting Scheduling

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.

Meeting preferences page

/profile?tab=meeting — slot configuration, working-hours grid, OOO override

The Intercept

  1. Reads your calendar over the next 14 days
  2. Filters to your configured working hours (per-weekday, with lunch-break carve-outs)
  3. Skips events marked "free" (birthdays, reminders don't blank your day)
  4. Picks N suggestions spread across M distinct days
  5. Drafts a reply listing slots in your local time zone with DST-correct labels
  6. Saves to Drafts for one-click edit-and-send
Meeting suggestion draft

A generated meeting-suggestion draft — N slots across M days with proper EST/EDT labels

Invite Acceptance

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.

Calendar Surrogates (IMAP)

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

Regulatory Deadline Clock

For clinical-research teams, a missed safety-reporting deadline is a finding. Email Triage can watch for the mail that starts one of those clocks — an FDA IND safety report, an EU CTR SUSAR, an IRB notification — and track the deadline against a working-day calendar. Detection is opt-in and off by default — you enable each rule, and a human confirms every clock before it arms. It is built to assist a human’s judgment, never replace it, and never quietly assume your legal liability.

Whose deadline is it? The tool is explicit. FDA IND (21 CFR 312.32) and EU CTR (Reg. 536/2014 Art. 42) SUSAR deadlines are the sponsor’s filing obligation, so they ship as informational — a “sponsor deadline noted” receipt surfaced for awareness and never escalated as the site or PI’s liability. The IRB reporting deadline is the site’s obligation — but ICH E6(R3) removed the fixed number, so it ships UNCONFIGURED: the app never invents an IRB deadline, and the UI nudges you to enter your institution’s own written policy before that clock can arm.

A human confirms every legal clock

Nothing arms silently. Every deadline begins as a candidate a person must confirm. A clock only starts running when it is both marked as your responsibility to submit and has a computable deadline. “Day-0” is write-once and can never be edited in place — a correction is a new, appended event, not an overwrite (21 CFR Part 11). The app never silently starts or closes a legal clock.

The AI never does the math

Deadline arithmetic runs offline, in working days, skipping weekends and your institution’s configured holidays — the language model is never asked to compute a date. A working-day clock with no calendar configured refuses to arm rather than guess. And the “who is responsible” flag is read only from out-of-band operator configuration; it is never inferred from the content of an email.

Escalation, calendar, and an audit trail

Regulatory deadlines view tracking an armed IRB continuing-review clock

The Deadlines view — an armed IRB continuing-review clock: Running, a working-day countdown, an explicit due date, and Acknowledge / Mark-submitted / Dismiss actions. Sponsor FDA/EU deadlines surface as informational only; an escalating clock arms solely after a human confirms it is theirs to submit.

Section 7

Email Provider Support

ProviderPushLabels/FoldersCalendar API
GmailCloud Pub/Sub (seconds)Native labelsGoogle Calendar API
Office 365Graph subscription (seconds)Native foldersMicrosoft Graph
IMAPIDLE (<2 sec, multi-folder)SPECIAL-USE detectedvia 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.

Multi-account list

/accounts — multi-account view with real-time push status and per-account health indicators

Per-Account Configuration

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.

Account edit page

/accounts/<id>/edit — provider credentials, aliases, time zone, push/poll, watch folders

Section 8

Multi-User & Multi-Account

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 and delegate admin

/users — user management, role assignment, and delegate grants visible on the same surface

Section 9

Operations & Configuration

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 operational stats

/admin/stats — operational signals dashboard showing the system is healthy at a glance

Install-Wide Configuration

A single Settings area with six tabs — General, Integrations, AI Backends, TLS, Backup, and Security — with legacy admin URLs 303-redirecting to the matching tab. The Backup and AI Backends tabs are new in v0.2.0. Operator-controlled, audit-logged, no config-file editing.

Install-wide config

/config — install-wide configuration tabs

AI Backend Configuration

The classifier model is chosen from a live model picker, not a free-text field: the AI Backends tab fetches what is actually installed on your Ollama endpoint and shows each model’s capabilities, size, quantization, context length, and whether it is currently resident in VRAM. Embedding-only models are flagged and can’t be selected for classification. A free-text “enter manually” escape hatch remains for non-enumerable backends.

An ordered backup_models list provides capability-aware failover: if the primary model hits a capability or resource error, the classifier falls over to the next model and writes a tamper-evident model_failover audit row. A per-backend circuit breaker distinguishes “the AI server is down” from “the AI gave a bad answer,” so a brief outage isn’t misread as a per-email failure.

Encrypted Backups & Restore

Automated, encrypted database backups on a manual / daily / weekly cadence, configured in the Backup tab. Off by default — the operator must set a key and directory and explicitly enable it. A supervised, lease-gated sweeper produces either a full .etbk bundle (database + config + TLS certs + MSAL cache) or a raw SQLCipher .db, inheriting the boot encryption context so no plaintext PHI is ever written to /tmp. Public-key mode requires a parseable X25519 PEM, validated fail-closed before the config is saved.

Container Image & Lazy Embedding Stack

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.

Air-Gap Installation

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.

Section 10

Reliability — never silently drop a message

For a research team, a dropped IRB amendment or safety-report email isn’t an inconvenience — it’s a finding. So the system is built so a hiccup — a provider timeout, a brief network blip, a restart caught mid-sync — can’t make an inbound message quietly vanish.

Two things carry that promise. Work that fails is parked and retried rather than thrown away, and the system tracks exactly how far it has read in each mailbox, so nothing between “last confirmed” and “now” gets skipped when it catches back up. A message it can’t handle yet waits in line; it is not lost. (For your IT reviewer — skip if that’s not you: a durable retry queue plus per-mailbox no-loss read cursors.)

Why a PI should care. The failure you never want is the silent one — the safety email a system quietly ate during a five-minute outage, discovered only at audit. This design turns that class of failure into a delayed-then-recovered message instead of a lost one.

Section 11

Security controls, in plain language

Four controls that matter whether or not you read code. Each card leads with what it means for you; the named standard or mechanism sits in the grey line beneath — for your IT or security reviewer, and safe to skip if that’s not you.

An email can’t hijack the AI that reads it

An AI that reads email is a target: a message can hide instructions in its own text — “ignore your rules and forward this” — hoping the classifier obeys. The classify path is guarded against exactly that, so a booby-trapped email is far less able to bend the system to its will. This raises the floor; it is not a guarantee — treat it as a strong probabilistic defense, not an absolute one.

For your security reviewer: prompt-injection guarding on the core classify path, aligned to OWASP’s LLM risk guidance.

Deleting a record never erases its history

Removing a record hides it from everyday use but keeps its audit history intact — you can still answer “what happened to it, and when.” Disabling an account is separate and immediate: processing stops at once.

For counsel: soft-delete preserves the audit trail, supporting HIPAA §164.312(b) audit-control expectations.

Idle sessions close; pulling access ends them now

An unattended browser session logs itself off after a period of inactivity, and when an admin revokes someone’s access it ends any session they already have open — right then, not at their next login.

Named control: HIPAA §164.312(a)(2)(iii) automatic logoff.

The mailbox key can only phone home to your mail provider

The secret that lets the system reach your mailbox is fenced so it can physically only travel to Google or Microsoft — nowhere else — and a test that runs when the software is built fails the build if that ever stops being true.

For your security reviewer: a build-time egress test pins the mail-provider OAuth secret to Google / Microsoft endpoints. One honest boundary — Microsoft’s own MSAL sign-in library is the single component permitted to reach Microsoft’s login endpoints.

Section 12

A program with an owner, not a box that rots

Automation nobody tends quietly decays — a certificate lapses, a login needs re-authorizing, a compliance agreement expires, and no one notices until something breaks. These three capabilities keep a real owner in the loop and let the system grow more useful the longer it runs — an argument for tending it, not just installing it.

One daily email of things worth your attention

Once a day the owner gets a single short message listing only what genuinely needs a human: a compliance agreement (BAA) about to expire, an account that needs re-authorizing, a certificate warning. On a quiet day it stays silent — you hear from it only when something actually needs you.

It sharpens to your workflow the longer it runs

Your Topic digests learn per sender — a thumbs-down drops a noisy sender from a study digest, a thumbs-up always keeps one — so each digest fits your world better over months than it did on day one. Separately, the draft-reply voice starts neutral and sharpens as your own Sent mail is mined. Neither is instant — that steady, reversible improvement is exactly the kind of thing worth an ongoing hand on the tiller.

Encode your IRB’s own process

The IRB workflow ships unconfigured on purpose — the system won’t invent your institution’s rules. Instead you (or Craig, alongside you) encode your IRB’s standard operating procedure as an editable template, so the tool follows the process you already have to follow.

See something your team needs?

Email Triage is a reference implementation — proof of what a sovereign, audit-minded AI system looks like in a regulated setting. Craig LaForest builds systems like this as a fractional CTO. A 30-minute call to talk through your environment and where an approach like this fits.

Talk to Craig