Didier Fuentes
Projectapps·

Huginn

Gmail triage with a language model that runs entirely on your iPhone.

The Idea

Email filters ask you to think like a computer. You want "the stuff I actually need to answer this week", and what you get to express is from contains @ AND subject contains RE:. The gap between those two sentences is the reason most people's inbox rules are three lines long and then abandoned.

Huginn closes that gap by putting a language model where the rule engine used to be. A category is a name plus a sentence describing what belongs in it, written the way you'd explain it to a person. The model reads the definition and the email and decides.

The catch with that idea, normally, is that it means shipping your mail to someone else's server. So the whole app is built around not doing that: every classification runs on the phone, on Apple Foundation Models or on a Qwen3 model downloaded and executed locally through MLX.

Named after one of Odin's ravens — the one that flies out and reports back what matters.

What It Does

Categories in Plain English

  • No rules to build — a category is a name and a natural-language definition. That definition is the prompt.
  • Per-category confidence — open an email and the model returns a short summary plus a confidence bar for every category, so you can see why before you accept a decision.
  • Seeded, not prescribed — a new account starts with Notifications, Newsletters and Personal, all of which are meant to be rewritten.

It Learns From Corrections

Correcting a category teaches three cheap local signals that increasingly let the app skip the model altogether:

  • A structural fast path keyed on sender, list headers and a normalized subject — once you've confirmed a pattern, matching mail is filed instantly.
  • A sender prior — how mail from this sender has been categorized before.
  • Few-shot examples — your own recent decisions, injected into the prompt so the model copies your taste rather than a generic notion of "important".

Actions

Each category carries an ordered list of typed steps, run idempotently — an interrupted run resumes without double-archiving or double-notifying.

  • Gmail edits — mark as read, archive, star, apply a label.
  • Notifications — templated with sender and subject, including ones that deep-link into another app when tapped.
  • Structured extraction — pull fields out of the body on-device (order numbers, dates, links) against a schema you define.
  • Save a link for later, or add an item to Reminders.

Privacy

  • No email content leaves the device. Inference is local, on Apple Foundation Models or Qwen3 via MLX.
  • One Gmail scope, gmail.modify — enough to read mail and change labels, nothing broader.
  • No analytics, crash reporting, or advertising SDK. The only servers the app talks to are Google's, plus a one-time model download if you choose Qwen.

Cautious By Default

Out of the box the app is read-only: it loads unread mail into a review queue and stops. You classify on demand, one email at a time. Automatic classification and mailbox writes are two separate switches you turn on when you trust it — the default avoids bulk automated edits to a real mailbox.

Tech Stack

LayerTechnology
UISwiftUI, targeting iOS 26
PersistenceSwiftData
InferenceApple Foundation Models, or Qwen3 4-bit via MLX
NetworkingURLSession against the Gmail REST API
AuthOAuth 2.0 with PKCE, via ASWebAuthenticationSession
ActionsEventKit, UserNotifications
DistributionFastlane

Architecture

Mail flows one way: Gmail → a local record → classify → apply actions, with a single pipeline object owning the run loop and every state transition. Processing is sequential and foreground-driven; only inference is asynchronous, so the UI stays responsive without ever running two generations at once.

Two decisions did most of the work:

Serialize all inference. MLX's Metal command queue is not thread-safe, and overlapping generations assert fatally. Actors don't save you here — they're reentrant at every await — so inference goes through a non-reentrant async mutex, and no code path is allowed to bypass it.

Two steps, not one. On-demand analysis summarizes the raw email first, then scores the summary plus header facts and history, never the raw body. Small models score a clean summary far better than a wall of marketing HTML. It costs a second pass and buys most of the accuracy.

The rest is memory discipline. A 1.7B model at 4-bit peaks around a gigabyte during inference, which is enough for iOS to kill the app — so the Metal buffer cache is capped, the model container is dropped on backgrounding and after a short idle, and the weights stay resident only during an active triage session.

Links