Contact Cleaner

September 20, 2026 · View on GitHub

Clean your Google “Other contacts” in a few minutes.

Google quietly saves everyone who ever emailed you: newsletters, robots, shared inboxes, student IDs, spam, and a few real people. Contact Cleaner reads the list Google exports, asks TypeSafe’s Jev model a fixed set of questions about every address, and sorts them into three piles:

PileWhat lands hereWhat you do with it
KeepReal peopleDownload as a Google CSV and import it into Contacts
ReviewThe AI was not sureSkim and drag rows to Keep or Remove
RemoveNewsletters, robots, shared inboxes, student IDs, spamDownload the list and delete them in Google

No account, no database. Your contacts live in your browser tab only. For each contact, just the name and email address travel to the server, which asks the AI and forgets them.


1. Export your contacts from Google

  1. Open contacts.google.com and click Other contacts in the left menu.
  2. Tick the checkbox at the top of the list, then choose Select all.
  3. Click the three dotsExport → choose Google CSVExport.

Google downloads a file called contacts.csv. That is the file you upload.

2. Use the app

Open the app, drop contacts.csv on the page, and press Start cleaning. You will see:

  • how many rows and duplicates were found,
  • a progress bar with elapsed time and the AI cost so far (a 4,700-contact file costs well under a dollar),
  • a live count of Keep / Review / Remove.

When it finishes you get a table you can filter, sort by confidence, group by domain (to remove an entire university or spam domain in one click), and drag rows between piles. Then download:

  • Keep as Google CSV — same columns as Google’s export, so it re-imports cleanly. Rows get the label Cleaned by Contact Cleaner and land in your main Contacts list.
  • Remove list — a plain list of emails to find and delete in Google.
  • Full results — everything, including every probability, for your records.

What does “confidence” mean? How sure the AI is about the kind of sender. 100% means all its weight was on one answer; a low number means it was torn between several, so the row goes to Review.

Optional: before you start, type your own yes/no question in the box (for example “Is this person likely based in Israel?”). The answer shows up as a probability in its own column.

3. Deploy your own copy (one click)

You need two things: a free Vercel account and a TypeSafe key.

  1. Get a key at console.typesafe.ai/settings/keys. Copy it.
  2. Click the button below. Vercel will copy this project and ask for one value.

Deploy with Vercel

  1. Paste your key into the field named TYPESAFE_API_KEY and press Deploy. Two minutes later you have your own URL.

Adding or changing the key later

In Vercel: open your project → SettingsEnvironment Variables → add Name TYPESAFE_API_KEY, Value = your key, tick all environments → Save. Then open Deployments on the latest one → Redeploy.

If you leak the key, regenerate it in the TypeSafe dashboard and paste the new one in the same place.

Using Vercel AI Gateway instead

If you would rather bill through Vercel, leave TYPESAFE_API_KEY empty and set AI_GATEWAY_API_KEY. The app then calls Jev through the Vercel AI SDK’s experimental_evaluate with the model typesafe-ai/jev.


For the curious: how it decides

  • Deterministic first. Code computes facts Jev is bad at: digit counts, name-in-email checks, how many contacts share a domain, free-mail and disposable lists, role-word patterns. Only those facts, plus the name and email, are sent.
  • One call per contact. Every question is asked in that single call: what kind of sender is this, does the name look like a real person, what kind of domain is it, does it look like a professional contact, plus your optional question.
  • Buckets are code, not the model. lib/jev-questions.ts is the only file with question text and thresholds. Change a threshold there and nothing else needs to move.

Run locally

npm install
cp .env.example .env.local   # paste your key inside
npm run dev

Check accuracy on labeled rows

eval/labeled.csv holds real rows with the bucket a human expects. Run:

npx tsx scripts/eval.ts

It prints accuracy per bucket, a confusion table, every row where confidence and correctness disagree, and what accuracy would be at other confidence floors. Pass --floor 0.6 to try another floor or --source path/to/contacts.csv to compute domain counts from a full export.

Privacy

Contacts never leave your browser except the name and email fields going to the server route, which forwards them to TypeSafe and discards them. Nothing is stored and nothing is logged.