Case study · Open source · 2026

CleanYourMail

Your inbox, minus the noise.

A local, agent-native skill that reads email headers only, files newsletters, promotions and spam, and unsubscribes safely — without reading message bodies and without permanently deleting anything.

CleanYourMail website and inbox demo
145tests
10languages
7provider presets
0bodies read
0permanent deletes
1command to undo

Why I built it

Inbox cleaners usually ask for your password, read your mail and delete the evidence. I wanted the opposite: a small local tool that shows the plan first, touches headers only, and can undo every move. CleanYourMail is that tool.

The project is also an experiment in agent-native software. The CLI is the product; SKILL.md and a short prompt teach a coding agent to use it without hiding the safety rules. There is no hosted service, no account, no telemetry and no third-party API.

What it does

  • Scans headers only. Sender, subject, date, size, flags and List-Unsubscribe headers. No bodies, no attachments, no full-mailbox download.
  • Classifies newsletters, promotions, social notifications, cold outreach, spam, transactional and personal mail.
  • Protects banks, security codes, invoices, travel, medical, government, legal, personal, starred, answered and recent mail. Protected messages are never auto-moved or auto-unsubscribed.
  • Plans before acting. Writes a reviewable plan.json with exact UIDs, destinations and unsubscribe targets. Nothing changes during scan, report or plan.
  • Files reversibly. Moves to folders/labels, Junk or Trash; Gmail archive removes the Inbox label. Every move is journaled with COPYUID and Message-ID, and undo restores it.
  • Unsubscribes safely. RFC 8058 one-click POST, mailto: or a manual link. No body links, redirects, cookies or private/local targets.

How it works

The workflow is deliberately boring: read, protect, plan, apply, verify, undo if needed.

  1. Scan — cache headers and classify messages.
  2. Plan — write plan.json with the exact moves and unsubscribe targets.
  3. Apply — copy first, then UID EXPUNGE only the source copy. Moves run before any unsubscribe request.
  4. Undo — the append-only journal verifies the Message-ID (or X-GM-MSGID for Gmail archive) before restoring.
CleanYourMail CLI: scan, report and plan commands

Safety model

  • No permanent deletion. No empty Trash, no “delete forever”, no mailbox-wide EXPUNGE. If the server lacks UIDPLUS or MOVE, moves are refused.
  • Plan/apply split. apply re-checks UIDVALIDITY, flags, category and the protected list before each batch.
  • Protected mail. Transactional and personal mail, security codes, banks, invoices, travel, medical, government, legal, starred, answered and recent messages stay put.
  • Unsubscribe is the only irreversible step. It is explicit, capped, delayed and never applied to spam.
  • Prompt injection is data. Instructions inside an email are reported, never followed.

The interesting part is not what it can do. It is what it refuses to do.

Technical notes

  • Python 3.11+ standard library only: imaplib, smtplib, sqlite3, tomllib.
  • IMAP/SMTP with app passwords or OAuth2 access tokens. The CLI does not acquire or refresh tokens.
  • SQLite metadata cache and an append-only JSONL undo journal. No body column.
  • RFC 8058 one-click unsubscribe, mailto:, manual links and an SSRF guard.
  • Gmail label archive via X-GM-LABELS and undo via X-GM-MSGID.
  • 145 tests with an in-process fake IMAP server; no real mailbox or network in CI.
  • Static bilingual website with a strict CSP, JSON-LD, security.txt, sitemap and no analytics.
  • README translated into 10 languages.

What I learned

The hardest part was not classification. It was designing the refusal paths: what to do when the server cannot move safely, when the plan is stale, when the message is protected, or when an unsubscribe target looks hostile. A tool that can say “I will not do that” is more useful than one that can do everything.

A reviewable plan also turned out to be better UX than a hidden automatic action. The person sees the exact moves before anything changes, and the undo journal makes the reversible part real instead of a promise.

Links

← Back to the portfolio


Click here if you'd like to collaborate with me:)