2026Solo developer — CLI, classifier and OpenCode skill
mail-triage — Junk mail triage CLI
A local CLI that sorts your junk mail into delete, rescue or review — with reasons.
- Python
- Microsoft Graph
- SQLite
- OpenCode
- Keychain
- Cloudflare
- Junk Mail
Problem
My junk mail folder fills with two very different populations: scams impersonating banks and Microsoft, and legitimate promotions I actually subscribed to. Deleting everything risks losing the receipt I was waiting for; ignoring the folder means the real phishing hides among the discount codes.
Provider-side spam filters give a binary answer and no reason, and cloud mail cleaners want full mailbox access. I wanted a local tool that classifies each message, tells me why, and never moves anything without my confirmation.
Approach
Local-first, no cloud services
Everything runs on Python's standard library plus SQLite and macOS Keychain, storing decisions and learning from my corrections on disk. Nothing leaves the machine.
I rejected cloud mail-cleaning services: they require root access to the mailbox over OAuth or password, and the data they see is more sensitive than the problem warrants.
A classifier with explicit reasons
Every message gets a 0–1 risk score and a human-readable reason: suspected impersonation, a reply-to different from the sender, a brand-new domain, SPF/DKIM failures, urgency cues. Legitimate mail rescued to the inbox prints why it was rescued.
I rejected a black-box model: without reasons I cannot audit false positives, and one wrong auto-delete teaches you never to trust the tool again.
Human-in-the-loop, learning from corrections
Nothing is deleted or moved without an explicit confirmation, and `undo` reverses the last batch. Corrections feed allowed/blocked domain lists; a domain gets an automatic rule only after three matching explicit corrections.
I rejected automatic deletion: the cost of a mistake (losing a receipt, an appointment, a contract) is asymmetric, so the design keeps a human as the final gate.
Provider-agnostic core, one backend today
The engine talks to a `MailProvider` interface; only Outlook (Microsoft Graph, device-code flow) is implemented today, with Gmail and generic IMAP ready to plug in without touching the classifier. `scan`, `purge`, `undo` and `feedback` all accept `--provider`.
I rejected hard-wiring Graph into the classifier: the triage logic should not change when the transport does.
Results
- Open-sourced with CI tests (GitHub Actions) and an OpenCode skill wrapping the CLI for interactive use.
- Every triage run produces an auditable report: score and reason per message, and full undo for any batch.