An open-source tool that uses AI to filter your Gmail inbox. Write rules in plain English — clearmail classifies every incoming email and applies Gmail labels so you wake up to a clean inbox.
- Polling — Every N seconds, clearmail checks for new unread emails via the Gmail API (OAuth2, no app passwords).
- Pre-filter — Emails from senders on your whitelist skip the AI entirely and stay in your inbox. Emails on your blacklist are rejected immediately. No API credits wasted on obvious decisions.
- Classify — Everything else goes to the LLM, which reads your per-category rules (written in plain English) and returns a single category plus a one-sentence explanation.
- Apply — Kept emails stay in your inbox (optionally starred) with a category label. Rejected emails are moved out of your inbox to a rejection label.
Clearmail never deletes email — it only adds and removes labels. You can review every decision in Gmail.
- Node.js 20+
- A Gmail account
- A Google Cloud project with the Gmail API enabled
- An API key for an OpenAI-compatible LLM (DeepSeek is the default — cheap and fast)
- Go to Google Cloud Console and create a project.
- Enable the Gmail API at:
https://console.cloud.google.com/apis/library/gmail.googleapis.com - Go to APIs & Services → Credentials → Create Credentials → OAuth client ID.
- Choose Desktop app as the application type.
- Copy the Client ID and Client Secret.
cp .env.example .envEdit .env and fill in:
PROVIDER_API_KEY— your DeepSeek (or OpenAI) API keyGOOGLE_CLIENT_ID— from step 2GOOGLE_CLIENT_SECRET— from step 2
npm install
npm run setup # Opens browser for Google OAuth consent, stores refresh tokenEdit config.yml. Write your rules in plain English:
globalRules:
keepIf: |
* security alerts, one-time passwords, and login codes
* direct replies to emails I've sent
rejectIf: |
* mass marketing, promotions, sale announcements
categories:
- label: Financial
keepIf: |
* bank statements, transaction alerts, tax documents
rejectIf: |
* credit card offers, loan promotions
whitelist:
- "partner@example.com"
- "*@mycompany.com"
blacklist:
- "*@spammy-newsletters.com"npm startClearmail runs continuously and prints a summary each cycle:
═══════════════════════════════════════════════════════════
Starting run at Mon May 11 2026 14:25:00 GMT-0700 (PDT)
═══════════════════════════════════════════════════════════
[1/3] # KEEP alice@example.com "Your flight itinerary" whitelist
[2/3] x REJECT noreply@spam.com "50% off everything!" Marketing — promotional offer
[3/3] # KEEP bank@bank.com "Monthly statement" Financial — bank statement
───────────────────────────────────────────────────────────
Run complete — 3 email(s) in 1.2s
Stop with Ctrl+C.
| Section | Key | Description |
|---|---|---|
globalRules |
keepIf / rejectIf |
Rules that apply to all emails before per-category rules |
categories |
label, keepIf, rejectIf |
Categories the LLM chooses from — one per email |
whitelist / blacklist |
Addresses or *@domain.com wildcards. Whitelist wins if both match. |
|
provider |
baseURL, model, maxTokens |
LLM settings (OpenAI-compatible). Falls back to .env if omitted. |
behavior |
pollIntervalSeconds |
How often to check for new email (default: 120) |
maxEmailChars |
Max body characters sent to LLM (default: 750) | |
maxEmailsPerBatch |
Max emails processed per cycle (default: 50) | |
dryRun |
Log actions without modifying Gmail | |
markRejectedRead |
Mark rejected emails as read | |
starKept |
Star emails that are kept | |
rejectedLabel |
Gmail label for rejected emails (default: AI Rejects) | |
unknownLabel |
Fallback label when LLM can't classify (default: CATEGORY_UNKNOWN) | |
deepBreathSuffix |
Enable the silly-but-effective prompt suffix | |
logging |
level |
debug, info, warn, error |
format |
plain or json | |
server |
enabled, port, apiKey |
Optional debug HTTP endpoint (POST /classify) |
| Variable | Description |
|---|---|
PROVIDER_BASE_URL |
LLM API base URL (default: DeepSeek) |
PROVIDER_API_KEY |
Your LLM API key |
PROVIDER_MODEL |
Model name (default: deepseek-chat) |
GOOGLE_CLIENT_ID |
Google OAuth client ID |
GOOGLE_CLIENT_SECRET |
Google OAuth client secret |
GOOGLE_REFRESH_TOKEN |
Automatically populated by npm run setup |
Clearmail uses the OpenAI SDK in provider-agnostic mode — any OpenAI-compatible API works.
| Provider | baseURL | model |
|---|---|---|
| DeepSeek (default) | https://api.deepseek.com/v1 |
deepseek-chat |
| OpenAI | https://api.openai.com/v1 |
gpt-4.1-mini |
| Groq | https://api.groq.com/openai/v1 |
llama-3.3-70b-versatile |
| Anthropic (via compatible proxy) | proxy URL | claude-sonnet-4-20250514 |
Set these in .env or override individual values in config.yml → provider.
Test your rules without touching real email:
behavior:
dryRun: trueOutput lines show [DRY] and no Gmail changes are made.
npm test # Run once
npm run test:watch # Watch modenpm install -g pm2
pm2 start src/index.js --name clearmail
pm2 save
pm2 startup # Auto-start on rebootQuestions or contributions? Reach out to Andy Walters.
Project sponsored by Emerge Haus, a custom Generative AI consultancy & dev shop.