Skip to content

About

Sort your Gmail inbox into categories you write in plain English. TypeSafe's Jev makes each call with calibrated probabilities; uncertain mail gets a Review label. Built on One, runs with zero keys.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

1 Commit

Folders and files

Repository files navigation

jev-email-classifier

Your inbox, sorted into categories you describe in plain English, with the emails that need a reply today pulled to the top.

Every email gets one call to Jev, TypeSafe's decision model. Jev doesn't write anything. It answers four narrow questions about each email with real probabilities: which of your categories is it, is a person waiting on you, is it a machine talking, and how soon does it matter. Your code turns those numbers into Gmail labels and a short morning report. When Jev isn't sure, the email gets a Jev/Review label instead of a guess.

A whole day of mail costs a fraction of a cent. The demo's 16 emails cost $0.0009.

Built on One: Gmail and TypeSafe are both One connections, so it's one API key and two connection keys, not two separate integrations.

Run it in five minutes

git clone https://github.com/withoneai/jev-email-classifier
cd jev-email-classifier && npm install
npm start

That runs with no keys and no accounts. It replays a fictional founder's inbox (16 emails) and Jev's real, recorded answers to it through the same pipeline a live run uses, then writes the report to reports/:

── jev email classifier (demo: no keys in the environment, replaying a recorded inbox)

   9 categories from config/inbox.md: Customers, Sales leads, Investors, Partners and intros, Hiring, ...
   read 16 new inbox emails from the last 2 days (alex@fernhill.dev)
   sorted: 3 reply today, 3 reply this week, 2 for review, 8 filed
     - Dana Okafor: Sync has been failing since last night
     - Priya Raman: Following up on Tuesday
     - Sam Whitfield: Re: Senior backend engineer, next steps
   wrote reports/inbox-2026-09-25.md

The report is what you actually read. The top of it:

16 emails. 3 need a reply today, 3 this week, 2 need your eyes, and 8 were filed with nobody waiting.

Reply today

  • Dana Okafor: Sync has been failing since last night

    Hi Alex, our nightly sync has failed three runs in a row since yesterday around 11pm. Our finance team closes the month on Monday...

    Someone is waiting, and it's time-sensitive. Labelled Jev/Customers and Jev/Reply.

And the part that matters most, the two emails it would not guess on:

Jev wasn't sure

  • Ines Carvalho: Checking in

    Not sure what this is: Sales leads 50% or Partners and intros 25%. Labelled Jev/Review.

  • Linear: Mira commented on FERN-481

    Looks like Notifications, but Jev can't tell whether someone is waiting on you (50%). Labelled Jev/Review.

Both are honest calls. "Checking in" really could be a prospect or a partner. And the Linear email is a machine notification carrying a direct question from a teammate, which is exactly the case where "is anyone waiting on me?" deserves a human look.

Point it at your own inbox

npm install -g @withone/cli
one init
one add gmail
one add typesafe          # paste a TypeSafe API key (free at typesafe.ai)

cp .env.example .env      # your One key and the two connection keys
npm run check             # proves Gmail and Jev both answer, changes nothing
npm start                 # read-only: sorts and writes the report, no labels
npm start -- --apply      # same, and adds the labels in Gmail

one --agent list prints the connection keys. Run without --apply first and read the report. When the sorting looks right, add --apply.

Emails that already carry a Jev/ label are skipped, so running it every hour only ever judges new mail.

The one file to edit

config/inbox.md is your inbox, described in plain English. Jev reads it word for word on every email. It has three parts:

  • About me. Who you are and what lands in your inbox. One paragraph.
  • Categories. One ### heading per category, with a few sentences on what belongs there. Each heading becomes a Gmail label (Jev/Customers). Add, remove or rename them freely.
  • What needs a reply from me. Your own rules. The shipped version says a cold pitch asking for "15 minutes" never needs a reply.

When something lands in the wrong place, fix it here, in a sentence, the way you'd explain it to an assistant. That is how the shipped file got its Investors rule: on the first recording, Jev filed a VC's "the partnership met this morning" under partnership pitches. One added sentence ("anyone writing from a venture firm belongs here, even when they mention the partnership") fixed it, and nothing else moved.

How it works

  1. Read the inbox. Everything in in:inbox from the last two days, skipping anything already labelled. The first 2,000 characters of each body, plus one observed fact: whether it carries a List-Unsubscribe header.
  2. Ask Jev four questions per email, in one call. A Choice (which of your categories, or "Other" if none fits), two yes/no questions (is a person waiting on you, was it sent by a system), and a Score (how soon it matters, on four levels from "whenever" to "this hour"). They run in parallel inside the one request.
  3. Decide, in code. src/sort.ts is 60 lines and no model. Category confidence under 0.75 goes to Review. A "someone is waiting" answer near 50% goes to Review. A person waiting and not a machine talking gets Jev/Reply, and lands in "reply today" or "this week" by urgency. Everything else is filed under its category.
  4. Label (only with --apply): creates any missing Jev/... labels, then one batch call per label.
  5. Write the report to reports/inbox-<date>.md.

The split the repo commits to: Jev judges, code decides. Jev tells you "Sales leads, 50%". Whether 50% is good enough to act on is your call, and it lives in a number you can change without calling any model again.

Tuning it

All in .env, all optional:

Variable Default What it changes
CONFIDENCE_MIN 0.75 Raise it and more mail goes to Review. Lower it and more gets filed automatically.
NEEDS_REPLY_MIN 0.6 How sure Jev must be that someone is waiting before it adds Jev/Reply
REPLY_TODAY_MIN 1.5 Urgency (0 to 3) at which a reply moves to "today"
AUTOMATED_MAX 0.5 Above this, it's a machine talking and never needs a reply
LOOKBACK_DAYS 2 How far back to read
LABEL_PREFIX Jev Jev/Customers, or Inbox/Customers, or whatever you like
JEV_MODEL jev-latest Pin a version once you've tuned thresholds on it

What it costs

Jev charges $0.042 per million input tokens and nothing for output. Each email is one call of about 1,300 tokens, so:

  • The demo's 16 emails: about $0.0009.
  • A heavy inbox of 200 emails a day: about a cent a day.

No other model is called. The Gmail reads and label writes are free against your own quota.

What it does not do

  • It never archives, deletes, marks read, or sends. The only write is adding a label, and only with --apply. Nothing is ever removed, so undoing a run is deleting the Jev/ labels.
  • It doesn't draft replies. Jev chooses, it doesn't write. If you want drafts for the Jev/Reply pile, that's a separate step with a writing model, and this repo deliberately stays out of it.
  • It reads the first 2,000 characters. Enough to tell what nearly every email is. Long threads are judged on their latest message.
  • It reads one page of Gmail. Up to MAX_MESSAGES (default 100, max 500) per run. Run it more often rather than raising the lookback.
  • English is where Jev is strongest. Other languages work, less well. Watch the Review pile if your inbox is multilingual.

Swapping the mailbox

Every call goes through the One SDK. Gmail is typed; TypeSafe goes through One's raw passthrough with its catalog action id, which still handles auth and retries:

const res = await one.connection(process.env.ONE_CONNECTION_TYPESAFE).runRaw({
  systemId: "conn_mod_def::GNh4bXcHPaU::V2ndvy5CQJmYHvkElwVM_w", // POST /v1/systemone
  method: "POST",
  path: "/v1/systemone",
  body: { model: "jev-latest", state, questions },
});

Pointing it at Outlook is a new src/gmail.ts for the read and the label write. The questions, the policy and the report don't change.

What you need

MIT licensed. Fork it, rewrite config/inbox.md for your own inbox, and run it without --apply until you trust it.

About

Sort your Gmail inbox into categories you write in plain English. TypeSafe's Jev makes each call with calibrated probabilities; uncertain mail gets a Review label. Built on One, runs with zero keys.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages