---
title: Email Cleanup
---

# Email Cleanup

## What it is

> **RETIRED SURFACE (2026-08-27): the standalone Email Cleanup sidebar panel (`__email_cleanup__`) is retired** in favor of the **Inbox Concierge** mission (Missions hub — [missions-hub.md](missions-hub.md)). Its sidebar row is hidden for everyone (the Dashboard visibility filter) and any stale pointer redirects to the Inbox. **Everything below — the cleanup ENGINE, its safety guarantees, the `/gmail/cleanup/*` routes, and the ✨ Clean Up button inside the Gmail inbox — is UNCHANGED and still works.** Only the standalone sidebar entry was removed.

Email Cleanup is the Gmail "Clean Up" tool, reimagined as an **AI-guided walkthrough** and originally given its own first-class sidebar home (that standalone panel is now RETIRED — see the note above; the engine described here is unchanged). It first works out **who matters to you** — your bank and other high-consequence senders, colleagues on your own company domain, people you write back to, and anyone you've explicitly chosen to keep — and **never** recommends removing them (the trust fix: no more "unsubscribe from your bank"). For everything else it judges each sender from your real **behavior** — Gmail's own category, how much of their mail you read, and how you **dispose** of it (archiving vs deleting) — to recommend an action with a **reason that cites the numbers** ("You've opened 2 of their 40 emails"). The primary way to clean up is now a **chat box** — ask in your own words ("unsubscribe from all promos, keep anything from my bank") and it proposes the changes for you to confirm — with the group-by-group walkthrough (one-tap batch cleanup, always confirmed) kept a tap away in a collapsed **Browse by category** section. A **Protected Senders** panel lets you manage who's kept, and self-sent + automated system mail gets its own lane. The classic flat sender-list is preserved one toggle away. It is the SAME execution engine (and the same safety guarantees) as the ✨ Clean Up button inside the Gmail inbox — the who-matters intelligence and the front door are what's new.

## Where to find it

### UI surface

- **Sidebar row "Email Cleanup" — RETIRED (2026-08-27).** The standalone `__email_cleanup__` sidebar panel no longer renders (hidden for all users via the Dashboard visibility filter); its virtual-project sentinel + panel component ([EmailCleanupView.tsx](/src/renderer/src/features/email-cleanup/EmailCleanupView.tsx)) stay in-tree but dormant. Reach the cleanup via the in-Gmail ✨ Clean Up button (below) or the **Inbox Concierge** mission — see [missions-hub.md](missions-hub.md).
- The ✨ **Clean Up button inside the Gmail inbox** toolbar is unchanged and still works — the tool is reachable from both places. Both entry points render the same view.
- **The walkthrough is chat-first.** It shows a smart summary ("N emails from M senders"), then a **chat box** as the primary surface — a welcoming "Tell me what to clean up" prompt with tappable example requests when it's empty, and a message box pinned front-and-center at the bottom. The click-through **category stages** (Promotions, Newsletters, Receipts, Social, Updates, People, **Automated & self-sent**, Likely-spam) — each with the AI's recommendation + reason, a one-tap **Clean up** batch, and per-sender drill-in — are shrunk into a **collapsed "Browse by category"** disclosure you can open to do it yourself. A protected sender shows a **shield + a plain "kept because" reason** (e.g. "Important account mail") so you can see why it is kept, and a collapsible **Protected Senders** panel lets you view / add / remove who is protected by email address or whole domain.
- Because the tool reads and archives your Gmail, the panel needs Gmail connected. If it isn't, the panel shows the shared **Connect Gmail / Reconnect Google** gate (the same one the Gmail inbox uses).
- Renders on desktop and mobile (it mounts through the standard sidebar panel host; no new push channels, so it stays within the mobile contract).
- **Rollback:** Settings → Gmail → **Classic Email Cleanup Layout** (`emailCleanupClassicLayout`, default off) restores the original flat sender list instantly.

## How it behaves

### How it works

Selecting **Email Cleanup** mounts a thin panel ([EmailCleanupView.tsx](/src/renderer/src/features/email-cleanup/EmailCleanupView.tsx)) that self-checks the Gmail connection (a brief "Checking your Gmail connection…" spinner so you never see a false "not connected" flash), gates on it (shared `GmailConnectGate` when not connected), and on entry auto-starts the inbox scan. It then renders [GmailCleanupView.tsx](/src/renderer/src/features/gmail/GmailCleanupView.tsx), which **branches**:

- **Default → the AI walkthrough** ([GmailCleanupWalkthroughView.tsx](/src/renderer/src/features/gmail/GmailCleanupWalkthroughView.tsx)):
  1. **Scan enriches with a free behavior signal.** The scan sweeps your **whole inbox** (bounded by a high safety ceiling, not just the most recent page) and groups by sender; from the message `labelIds` it ALREADY fetches, it tags each sender with Gmail's own category (Promotions / Social / Updates / …) and how many of their scanned messages are unread — **zero extra API calls**.
  2. **Who-matters protection FIRST, then behavior.** A Main-process call runs a shared decision engine over EVERY sender. It **first** resolves who is protected — your explicit keep-list, high-consequence senders (bank / payments / tax-gov / account-security) that pass an **"is this real?" legitimacy gate** (a bulk-marketing sender wearing a financial-sounding name — Gmail Promotions — is never mistaken for your bank), your own organization domain (derived from the connected account **and its verified "send mail as" identities**, so it works even when you've connected a personal Gmail), people you correspond with, and contacts the profile-miner already knows you know (consumed **read-only**) — and a protected sender is **never** a removal candidate (a confident `keep` with a generic, no-PII reason). Your explicit keep/remove choices persist in a soft-deleted store and take precedence over every auto-rule on the next scan. For the rest it judges the **lifetime** engagement signal — read-rate, reply-rate, and how you **dispose** of the mail (archive-rate + delete-rate) — via cheap count-only Gmail queries (`resultSizeEstimate`, no message bodies). Deleting a sender's mail is active rejection, so a consistent delete-rate drives a confident removal even at a high read-rate; a passive open-rate alone never does. Self-sent + transactional-automation senders route to their own **Automated & self-sent** lane — and a non-marketing service notification (CI, forums, account alerts) is filed there rather than mislabeled **unsubscribe**, even when it carries an unsubscribe header; only genuine marketing is ever unsubscribed. The engine makes the confident calls itself with a **reason that cites the numbers**; only genuinely **ambiguous** senders (low confidence) escalate to a stronger (Sonnet-tier) model. Groups render instantly and enhance when the classify lands — **never blank, never hard-depends on the model** (offline / no key / daily cap / breaker → the deterministic verdicts still stand, `source:'ai'`).
  3. **Chat is the primary surface.** The chat box — front-and-center, with a "Tell me what to clean up" welcome + tappable example-request chips before you type — turns free-form requests ("unsubscribe from all promos, keep anything from my bank") into **proposed action batches you approve**. The chat never executes anything itself, and clicking an example chip only *fills* the box (it never sends), so you always review before anything happens.
  4. **Guided batches (always-ask), in the "Browse by category" fallback.** Open the collapsed **Browse by category** disclosure to work through it by hand: each category stage's header reflects its **real** dominant recommendation — **Clean up N** when there's a confident actionable subset, **Review N** when the recommendations are present but low-confidence (act per-row), and **Recommended: keep** only when the group genuinely should be kept. A low-confidence group never collapses into a false whole-group "keep" (the label is decoupled from the confidence-gated batch). The one-tap **Clean up N** runs every confident sender's recommended action in a single batch — one bulk archive, one undo, one toast. Nothing fires without a confirm dialog; `keep` senders are never touched, and a **low-confidence recommendation is excluded from the batch** (below `BATCH_MIN_CONFIDENCE`) — you can still act on it per-row, so a one-tap never acts on a guess.
- **Rollback → the classic flat list** (`GmailCleanupClassicView`, same file) when `emailCleanupClassicLayout` is on: scan-by-sender with per-sender Archive / Unsubscribe + Archive / Never again, exactly as before.

**Execution is unchanged either way.** Every archive / unsubscribe / auto-archive filter still flows through the existing main-process engine and IPC — the AI only _reasons_, it never bypasses a safety guarantee (SSRF-guarded one-click unsubscribe, replied-to/starred protection, undo). The model runs ONLY on the ambiguous residual (Sonnet-tier) behind a **$0.50/day cap** (a local-midnight day, charged against the `email-cleanup` cost label — there is no setting for it), with injection-safe prompts (sender subjects are treated as untrusted data) and every model-returned address validated against the scanned set; the count-queries are read-only and need no new Gmail permission. See the cleanup contract (`.claude/memory/contracts/gmail-inbox-cleanup-contract.md`) for the full invariant list (1–10 engine, 11–20 AI + signal layer, 21–23 whole-inbox scan · real-recommendation group label · read-only CLI route, **24–33 the who-matters trust layer** — protection-first, memory precedence, mined-by-source-message, generic no-PII reasons, soft-delete store, own-domain (account + verified send-as identities, freemail-excluded), archive/delete discard signal, self-sent/automation lane, the shield protection flag, and the **legitimacy gate** — a marketer is never granted operational protection, and a service notification is never mislabeled unsubscribe).

## For agents

### Code references

- Branch wrapper + classic view: [GmailCleanupView.tsx](/src/renderer/src/features/gmail/GmailCleanupView.tsx)
- AI walkthrough UI: [GmailCleanupWalkthroughView.tsx](/src/renderer/src/features/gmail/GmailCleanupWalkthroughView.tsx); shared per-sender row + confirm copy: [gmail-cleanup-shared.tsx](/src/renderer/src/features/gmail/gmail-cleanup-shared.tsx)
- AI module (Main, deterministic-first + LLM residual): [gmail-cleanup-ai.ts](/src/main/services/email/gmail-cleanup-ai.ts); the shared decision engine + grouping + `dominantGmailCategory` + discard signals + self-sent/automation lane: [gmail-cleanup-heuristic.ts](/src/shared/gmail-cleanup-heuristic.ts); count-query helper `estimateCount`: [gmail-read.ts](/src/main/services/email/gmail/gmail-read.ts)
- Who-matters protection resolver (memory / operational / own-domain / mined, read-only): [cleanup-who-matters.ts](/src/main/services/email/cleanup-who-matters.ts); shared real-person-vs-bulk primitive: [gmail-sender-kind.ts](/src/shared/gmail-sender-kind.ts)
- Protected-senders MEMORY store (soft-deleted, reactivating) + management UI: [verdicts.ts](/src/main/db/queries-cleanup-memory/verdicts.ts), [cleanup-memory-types.ts](/src/shared/cleanup-memory-types.ts), migration [20260817233911-add-cleanup-sender-verdicts-table.ts](/src/main/db/migrations/20260817233911-add-cleanup-sender-verdicts-table.ts), panel [GmailCleanupProtectedSenders.tsx](/src/renderer/src/features/gmail/GmailCleanupProtectedSenders.tsx)
- Store (classification + chat + batched actions): [gmail-cleanup-store.ts](/src/renderer/src/stores/gmail-cleanup-store.ts)
- IPC: `gmail:cleanup-classify` + `gmail:cleanup-chat` (read-only), plus the verdict-memory writes `gmail:cleanup-set-verdict` / `-delete-verdict` / `-list-verdicts`, in [gmail-handlers.ts](/src/main/ipc/gmail-handlers.ts)
- Read-only CLI analysis route + shared `senderToCleanupInput` projection: [gmail-mail-routes.ts](/src/main/services/cli/gmail/gmail-mail-routes.ts), [gmail-cleanup-heuristic.ts](/src/shared/gmail-cleanup-heuristic.ts)
- Standalone panel + connect gate: [EmailCleanupView.tsx](/src/renderer/src/features/email-cleanup/EmailCleanupView.tsx), [GmailConnectGate.tsx](/src/renderer/src/features/gmail/GmailConnectGate.tsx)
- Execution engine (unchanged): [gmail-cleanup-service.ts](/src/main/services/email/gmail-cleanup-service.ts), [gmail-list-unsubscribe.ts](/src/main/services/email/gmail-list-unsubscribe.ts), [ssrf-guard.ts](/src/main/utils/ssrf-guard.ts)
- Rollback setting: `emailCleanupClassicLayout` in [channels-settings.ts](/src/shared/types/settings/channels-settings.ts), surfaced in [GmailSettings.tsx](/src/renderer/src/features/settings/GmailSettings.tsx)

### CLI

One **read-only** analysis route: `GET /gmail/cleanup/analysis` (bearer-auth, read-scoped, read-budgeted). It runs the same scan + reason-only classify so an agent or script can _drive and verify_ the recommendations without touching the desktop UI — it returns the grouped recommendations (per-group category, label, sender/email counts) plus the per-sender classifications and the classify `source`. `?limit=` (default a fast 500-message sample, up to the whole-inbox ceiling) trades speed for coverage. It **never executes an action**: the route module intentionally does not import the archive / unsubscribe / auto-archive-filter functions, so a CLI caller can analyze but can never fire a destructive Gmail action — a build guard ([gmail-cleanup-cli-route-readonly.test.ts](/tests/unit/lint/gmail-cleanup-cli-route-readonly.test.ts)) asserts that import boundary. Every real cleanup action stays confirm-gated in the desktop UI; the two AI calls remain renderer-driven IPC (the manifest still declares no `cliSkillIds`). The who-matters **memory** is CLI-manageable too — `POST /gmail/cleanup/memory/set` (protect/unprotect a sender or whole domain), `POST /gmail/cleanup/memory/delete`, and `GET /gmail/cleanup/memory/list` — so an agent can curate your protected-senders list; these write only the local keep/remove verdict store, never a Gmail action.

## Related

### See also

- [gmail-integration.md](gmail-integration.md) — the Gmail inbox, the in-inbox Clean Up button, and every cleanup action in detail
- [INDEX.md](INDEX.md)
