All demosInbox triage

Inbox triage

500 seeded support emails, seven judgments each. Sort them into lanes, label them with an intent you type, and compare every judgment against a keyword-rule baseline on 20 hand-written traps.

/classify/yes-no/rate

How it works

The inbox holds 500 synthetic emails built by a seeded generator (mulberry32, seed 20260917), including 20 hand-written traps that defeat keyword rules: sarcasm, quoted history, a newsletter that lists “refund” and “cancel” as feature names, a gift-card scam with no scam words. They are the original experiment's fixtures, copied unchanged.

The original asked one model seven questions about one email in one request. Here each request carries a batch of 32 emails, and the seven questions become four calls per batch. Each email becomes one text: From: …, Subject: …, blank line, body.

  • /classify — category. Nine described labels, each named for what the sender wants rather than for the enum key behind it: A question about our invoice · A customer reports our product misbehaving · A customer wants a capability we lack · A prospect wants to buy · Someone broke into an account · A legal or privacy demand · Bulk mail nobody asked for · None of the above · One employee writing to another. There is no instruction field, so the instruction lives in each description, for example Bulk mail nobody asked for = “A stranger pitching their own product, a stranger warning about a problem in order to sell the fix, a weekly digest or product-update blast, or a scam impersonating a person or a brand”. We keep label, confidence and the full scores map; anything under 0.7 confidence goes to the Needs review lane instead of being trusted.
  • /yes-no — four flags in one call. 32 texts × 4 statements nest into one request: “This email needs a reply from a person.” · “The sender wants the reader to click a link, pay an invoice or hand over a login.” · “The sender is cancelling or moving to another vendor.” · “The sender asks us to refund money they paid.” Each probability is used as-is, thresholded at 0.5 — except the phishing one, which is shown as an advisory percentage and routes nothing. See the honest miss below.
  • /rate — urgency, low to high: marketing or a suggestion nobody is waiting on · a real request to answer within a few days · the sender is blocked today, or money is at stake today · the sender's system is down, their data is lost or their account is breached.
  • /rate — sentiment, low to high: happy with us and says thank you · reports something plainly · annoyed, disappointed or sarcastic about a mistake we made · furious: insults, threats to escalate, or a demand for a manager. The score comes back on the 0–3 scale, so the original's priority arithmetic needed no change.

Label by intent. What you type becomes one short declarative sent to /yes-no over a batch of 32 emails — “customers threatening to cancel” becomes “The sender is cancelling or moving to another vendor.” The ten suggested intents ship with hand-written statements; free text is wrapped as “This email matches the description: …”, capped at 200 characters. Several labels intersect with no new inference.

The baseline. Press b to run a hand-tuned regex triage of the kind most support inboxes actually use. It is instant and free, it agrees with the model most of the time, and the Rules ≠ model lane lists every email where it does not. It runs entirely in your browser. Every run judges all 20 traps first and then fills the rest of the window with seeded mail, so that lane is never empty. What the model adds over the regexes is judgments the regexes cannot make at all — sarcasm, negation, quoted history, a sentiment score — not a clean sweep of the traps.

Batching and budget. One run is 4, 16 or 32 calls (32, 128 or 256 emails, the 20 traps first) at two calls per second page-wide, and every loop stops on its own after 45 seconds — the deadline is checked before every call, not only between batches, so the longest a run can overshoot is one call. Nothing fires until you press a button; the seeded inbox is already on screen before the first request. The metrics strip reports the model time from x-inference-ms, the browser round trip, and the input tokens from x-input-tokens at $0.04 per million.

The honest miss. The phishing statement does not separate the two scam traps on this model build. The gift-card CEO-impersonation mail (m293) reads 6% and the lookalike-domain signing phish (m449) reads 19%, while a loud marketing blast (m300, “ASAP: 70% off ends at midnight!!!”) reads 76%. Six rewordings were tried, including a two-way /classify; none separated them, so the flag routes nothing and the page claims nothing about scam detection.

Category, measured. The traps are the only part of this inbox with a ground truth: the original experiment recorded its own answer for every email, so smoke.mjs scores /classify against those 20 and fails below a floor. The label set here gets 12 of 20. Fourteen label sets were measured to get there, and two things moved the number. Naming the labels after the enum keys scores 5 of 20, because internal then acts as a catch-all and swallows every external sender — the gift-card scam landed there at 97%. And the order is load-bearing: whichever label sits second to last absorbs the mail the model cannot place, so None of the above is parked in that slot on purpose. Over the 32-email smoke batch that trade shows up plainly: the old set auto-routed 19 emails past the 0.7 bar and got 11 of them wrong, this one auto-routes 8 and gets 2 wrong. The rest lands in Needs review, which is what that lane is for. Both remaining confident misses are the same shape — a stranger selling security (m494) and a newsletter about a breach (m346) read as Someone broke into an account.

One deviation from the original. The when_true / when_false hints on /yes-no push nearly every email to true on the current model build, so the four statements run bare and carry the nuance in their wording instead. Bare, a refund request reads 0.99 and a refund named in a newsletter reads 0.12.

Ported from the inbox-blitz Jev experiment. Every request here is live; the shared demo key allows about 2 requests per second across all visitors.