Delete or move IMAP emails by sender, domain, or nested rules - CLI and local web UI.
Project description
IMAP Cleaner
Delete or move IMAP emails in bulk - by sender, by domain, or by nested rules (a query builder). Works from the command line and from a local web interface. The CLI uses only the Python standard library; the web UI is an optional extra (FastAPI).
- Match by a target file (one sender/domain per line) or by a rule
expression like
sender contains amazon.com OR (subject is Invoice AND date starts 2025-01-01). - Fast server-side search for huge folders, or strict local matching.
- Count how many emails a filter matches before deleting anything.
- Move matched emails to another folder instead of deleting them, and create new folders (or labels on Gmail) right from the app.
- Gmail mode: moves matches to Trash (the only way to truly delete on Gmail).
- Empty a whole folder (e.g. Trash) without scanning.
- List senders with counts and export them to CSV (with timestamp).
- Stop button / cooperative cancellation for long runs.
- Scheduler: save jobs and install them into the system scheduler (Windows Task Scheduler / cron) - once, hourly, daily, weekly, monthly, or every N minutes - so they run even when the app is closed, with per-job logs.
⚠️ Deleting email is destructive. Always do a
--dry-runfirst. Without--expunge, messages are only flagged deleted (often hidden by the client but recoverable until expunged).
Table of contents
- Quick start - web interface
- Install
- Quick start - command line
- Command-line usage
- Rule expressions
- Target file format
- Web interface
- Folders vs labels, and moving
- Remote / headless server (SSH port forwarding)
- Scheduling
- Gmail notes
Quick start - web interface
The web interface is the easiest way to use the tool and the recommended path for most users.
Prerequisite: Python 3.10 or newer. Check with python --version (or
python3 --version). If it is missing, download it from
python.org/downloads - on Windows, tick
"Add python.exe to PATH" in the installer. Linux/macOS usually ship Python, or
install it with the system package manager (sudo apt install python3 python3-pip,
brew install python, etc.).
Windows (PowerShell):
# 1. Create and activate a virtual environment (keeps the install isolated)
python -m venv .venv
.venv\Scripts\Activate.ps1
# 2. Install. The [web] extra installs BOTH the core CLI and the web UI
# (it pulls in the base package automatically - no separate step needed).
pip install "imap-cleanup-tool[web]"
# 3. Launch. Serves http://127.0.0.1:8765 and opens the default browser.
imap-cleanup-tool-web
macOS / Linux (bash/zsh):
# 1. Create and activate a virtual environment (keeps the install isolated)
python3 -m venv .venv
source .venv/bin/activate
# 2. Install (core CLI + web UI in one go)
pip install "imap-cleanup-tool[web]"
# 3. Launch. Serves http://127.0.0.1:8765 and opens the default browser.
imap-cleanup-tool-web
The virtual environment is recommended but optional - you can skip steps 1 and just
pip install "imap-cleanup-tool[web]"globally. Either way, activate the same environment (.venv\Scripts\Activate.ps1/source .venv/bin/activate) in every new terminal before runningimap-cleanup-tool-web.If
pipis not found, usepython -m pip ...(Windows) orpython3 -m pip ...(macOS/Linux); on some systems the command ispip3. On Windows, ifActivate.ps1is blocked, runSet-ExecutionPolicy -Scope Process RemoteSignedfirst, or use.venv\Scripts\activate.batfrom cmd.exe.
Then, in the browser:
- Connect - pick a provider preset (or type host/port), enter your username and password (for Gmail, an App Password - see Gmail notes), and click Connect. Optionally save it as a connection profile so you do not retype it next time.
- Pick folders - select one or more folders to scan (each shows its message count); use Select all / Deselect all as needed.
- Choose what to match - either paste a target list (one sender or domain per line) or build a rule visually (field ▸ operator ▸ value, with AND/OR groups). Click Count matching emails to see how many would be hit.
- Review, then run - dry-run is on by default, so the first run only reports. Watch the live log; use Stop to cancel. When the preview looks right, turn off dry-run (or pick an action - Move to another folder, Gmail: move to Trash, Expunge) and run for real.
- (Optional) Schedule it - in the Scheduling tab, turn the same settings into a job and install it into the system scheduler. See Scheduling.
On a server with no desktop, the GUI is still usable from a local browser via SSH port forwarding - see Remote / headless server.
⚠️ Deleting email is destructive. Keep dry-run on until the count and log look right. Without Expunge, messages are only flagged deleted (often hidden by the client but recoverable until expunged).
Install
Requires Python 3.10 or newer (python --version). See
python.org/downloads if you need it.
From PyPI (recommended)
A virtual environment keeps the install isolated (optional but recommended):
python -m venv .venv
# Windows: .venv\Scripts\activate
# macOS/Linux: source .venv/bin/activate
Then install. The base package is the CLI only; the [web] extra installs the
same base package plus the web UI in one go:
pip install imap-cleanup-tool # core CLI only
pip install "imap-cleanup-tool[web]" # core CLI + web UI (imap-cleanup-tool-web)
You do not need to install the base separately before [web] - the extra
includes it. The CLI stays dependency-free; only the web UI pulls in FastAPI,
uvicorn and cryptography (the last for encrypted connection profiles).
From source
git clone https://github.com/mrpickles007/imap-cleanup-tool.git
cd imap-cleanup-tool
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -e ".[dev,web]" # editable install + dev tools + web UI
Running the tests
The test suite uses only the standard library (unittest) - nothing extra to
install:
python -m unittest discover -s tests -v
Quick start - command line
# 1. See your folders (find the real Trash/Sent names)
imap-cleanup-tool --host imap.gmail.com --user you@gmail.com --list-folders
# 2. Preview what would be deleted (changes nothing)
imap-cleanup-tool --host imap.gmail.com --user you@gmail.com \
--targets targets.txt --dry-run
# 3. Do it for real (Gmail: move to Trash)
imap-cleanup-tool --host imap.gmail.com --user you@gmail.com \
--targets targets.txt --gmail-trash
Credentials are read from flags, then environment variables
(IMAP_HOST, IMAP_USER, IMAP_PASSWORD, IMAP_PORT), then an interactive
prompt. Prefer the prompt or env vars over --password so the secret does not
land in your shell history.
Command-line usage
| Option | Meaning |
|---|---|
--host, --port, --user, --password |
Connection (port default 993). |
--timeout N |
Socket timeout in seconds (default 120). |
--folder NAME |
Folder to scan; repeat for several. Default INBOX. |
--targets FILE |
Match by a target list file. |
--rule "EXPR" |
Match by a rule expression (see below). |
--scan-mode search|full |
Server-side search (fast) or local match (strict). |
--include-subdomains |
In full mode, also match subdomains. |
--batch-size N |
Messages per IMAP request (default 500). |
--list-folders |
Print folders and exit. |
--list-senders |
Print unique senders with counts and exit. |
--save-senders CSV |
With --list-senders, append to a CSV. |
--empty-folder |
Delete ALL messages in the folder(s); no filtering. |
--gmail-trash |
Move matches to Gmail Trash via labels. |
--move |
Move matches to --dest-folder instead of deleting them. |
--dest-folder NAME |
Destination folder/label for --move. |
--create-folder NAME |
Create a folder (a label on Gmail) on the server, then exit. |
--delete-folder NAME |
Delete a non-system folder/label on the server, then exit. |
--dry-run |
Report only; make no changes. |
--expunge |
Permanently remove after flagging. |
--yes |
Skip the confirmation prompt (for scripts/cron). |
--verbose, -v |
Debug logging with per-batch progress. |
--run-job NAME |
Run a saved scheduled job by name (used by the OS scheduler). |
--profile NAME |
Load host/user/password from a saved, non-encrypted profile. |
Examples:
# Save a sender report (timestamp, account, folder, sender, count)
imap-cleanup-tool --host HOST --user USER --list-senders --save-senders senders.csv
# Empty the Trash, fast, no scan
imap-cleanup-tool --host HOST --user USER --folder Trash --empty-folder --dry-run
imap-cleanup-tool --host HOST --user USER --folder Trash --empty-folder
# Strict local matching including subdomains
imap-cleanup-tool --host HOST --user USER --targets targets.txt \
--scan-mode full --include-subdomains --dry-run
# Create a folder/label, then MOVE matched mail into it (instead of deleting)
imap-cleanup-tool --host HOST --user USER --create-folder "Archive/2025"
imap-cleanup-tool --host HOST --user USER --targets targets.txt \
--move --dest-folder "Archive/2025" --dry-run
Rule expressions
In the web UI you build rules visually with the query builder (no typing).
The text grammar below is what the CLI --rule flag accepts and what
scheduled jobs store - the visual builder produces exactly these expressions
under the hood.
Rules are an alternative to target files, evaluated server-side via IMAP
SEARCH. Fields and operators:
| Field | Operators | Maps to |
|---|---|---|
sender |
is, contains |
FROM |
subject |
is, contains |
SUBJECT |
date |
is, starts, ends |
ON, SINCE, BEFORE |
Combine conditions with AND / OR, and group with parentheses for nesting.
Dates accept YYYY-MM-DD. Quote values with spaces.
imap-cleanup-tool --host HOST --user USER --dry-run \
--rule 'sender contains amazon.com OR (subject is "Black Friday" AND date starts 2025-11-01)'
isandcontainsboth map to IMAP substring matching on the header; IMAP has no exact-header match, so treatcontainsas the reliable operator and use the target-file--scan-mode fullpath when you need strict exactness.
Target file format
One entry per line; # starts a comment.
spam@example.com # exact sender address
*@newsletter.com # that domain EXACTLY - never subdomains
annoying.com # that domain, plus subdomains if --include-subdomains
mail.annoying.com # that specific (sub)domain
The *@domain form always matches the domain exactly; the bare domain form
also matches subdomains when --include-subdomains is given. This distinction
applies to local --scan-mode full; server-side search is a substring match
either way.
So in full mode *@paypal.com is the same as paypal.com without
--include-subdomains. The useful part is mixing them: with
--include-subdomains on, *@paypal.com stays exact while bare domains
expand to their subdomains - per-entry control in a single list. Example:
*@paypal.com # exact, even with --include-subdomains
newsletter.com # this one DOES include its subdomains
Web interface
A local web UI (FastAPI) is the tool's graphical interface. Install the extra and run:
pip install "imap-cleanup-tool[web]"
imap-cleanup-tool-web # serves http://127.0.0.1:8765 and opens your browser
Options: --host, --port, --no-browser. It runs only on your machine
(127.0.0.1) by default. The IMAP connection lives on the local server and is
reused across actions, surviving a page refresh; it is dropped automatically
after a period of inactivity. Your password is never stored.
Highlights:
- Many provider presets, connect-and-load-folders (with per-folder message counts), multi-folder selection, Select all / Deselect all.
- Connection profiles: save host / user / password to a local SQLite DB - optionally encrypted with a password - and pick one from a dropdown.
- Match by a target list (paste or load from a file, with inline format help) or a visual nested query builder (field ▸ operator ▸ value, AND/OR groups).
- Count matching emails before deleting; dry-run is on by default.
- Move matches to another folder instead of deleting (pick the destination from a dropdown of your folders, or create a new one inline); plus create and delete folders/labels on the server (system folders are protected). The folder box distinguishes Add to scan (just lists a folder to scan, creates nothing) from Create on server (really creates a folder, a label on Gmail) - see Folders vs labels.
- Context-aware options with tooltips (e.g. Include subdomains only in
"full"scan mode; Gmail: move to Trash only for Gmail). - Background runs with a Stop button and a persistent, live log panel.
- List senders with counts (export to CSV), and a Scheduling tab to create jobs and install them into the OS scheduler.
Folders vs labels, and moving
Over IMAP a "folder" and a Gmail "label" are the same thing, so creating one works everywhere: on a normal mailbox you get a folder, on Gmail you get a label.
Two different actions in the app are easy to confuse, so they are kept distinct:
- Add to scan (the folder box) only adds a name to the list of folders you will scan. It does not create anything on the server - use it to scan a folder that was not auto-listed.
- Create on server actually creates a new folder/label on your mailbox (via
IMAP
CREATE). Use it to make a destination before moving. - Delete on server (the 🗑 on a folder row, or
--delete-folder) removes a folder/label from your mailbox. System folders (INBOX and special-use ones like Trash, Sent, Drafts, All Mail) are protected and cannot be deleted - only folders you created.
When you choose Move, the destination is a dropdown of your existing folders; pick one, or choose ➕ Create new… to make a new folder/label on the spot.
Moving copies the matched messages into the destination and removes them from
the source. The tool uses the server's MOVE command when available, otherwise
COPY + delete + expunge. On Gmail a move relabels the messages (removes
the source label, adds the destination one); the message itself still lives in
All Mail. Move is mutually exclusive with delete / Gmail-trash / expunge; only
Empty folder overrides everything. From the CLI:
imap-cleanup-tool --host HOST --user USER --create-folder "Receipts"
imap-cleanup-tool --host HOST --user USER --targets bills.txt \
--move --dest-folder "Receipts" --dry-run
Move jobs can be scheduled like any other job (the Scheduling tab carries the same Move setting and destination into the saved job).
Remote / headless server (SSH port forwarding)
The tool can be installed on a remote, desktop-less server (e.g. a VPS or a
home server reached over SSH) and still be driven through the web GUI in a
local browser. The web server binds to the server's loopback
(127.0.0.1:8765) and is not exposed to the network; the browser reaches it
through an encrypted SSH tunnel that maps a local port to that loopback port.
This is the same "local port forwarding" mechanism used by the VS Code Remote
extension and SSH clients such as Bitvise or PuTTY.
On the server (in the SSH session), start the web server without trying to open a browser it does not have:
pip install "imap-cleanup-tool[web]"
imap-cleanup-tool-web --no-browser # listens on 127.0.0.1:8765
On the local machine, open an SSH tunnel that forwards a local port to the
server's 127.0.0.1:8765:
# Forward local 8765 -> server's localhost:8765
ssh -N -L 8765:localhost:8765 user@your-server
Then open http://localhost:8765 in your local browser. Traffic travels inside the SSH connection; nothing is published on the server's public interface.
- VS Code Remote-SSH: open the folder on the server, run
imap-cleanup-tool-web --no-browserin its terminal - VS Code auto-forwards the port and offers to open it locally. (Add it manually in the Ports panel if needed.) - Bitvise / PuTTY: add a Local (C2S) forwarding rule - listen interface
127.0.0.1, listen port8765, destination hostlocalhost, destination port8765- then browse tohttp://localhost:8765. - Pick a different local port if 8765 is busy, e.g.
-L 9000:localhost:8765→ openhttp://localhost:9000. To run the server on another port, useimap-cleanup-tool-web --no-browser --port 8800and forward to that.
Keep it on loopback. Prefer the SSH tunnel over
--host 0.0.0.0(which would expose the unauthenticated UI to the whole network). The tunnel gives you SSH's authentication and encryption for free.
Keep it running after logout. A plain SSH session stops the server when you disconnect. To leave it running, start it under
tmux/screen, withnohup imap-cleanup-tool-web --no-browser &, or as asystemdservice. For unattended recurring cleanups you usually want a scheduled job instead of a long-lived server - see Scheduling.
Scheduling
Jobs are stored as JSON in your user config directory
(%APPDATA%\imap-cleanup-tool on Windows, ~/.config/imap-cleanup-tool elsewhere).
Scheduling is handled entirely by the operating system scheduler - there is
no background process to keep running.
Click Install to system scheduler to register a job directly (a schtasks
task on Windows, a crontab line on Linux/macOS) so it runs even when the app
is closed. Export command shows the equivalent line.
Frequency - pick one in the Scheduling tab; the form shows only the inputs that apply:
| Frequency | Inputs | Windows | Linux/macOS |
|---|---|---|---|
| Run once | date + time | schtasks /SC ONCE |
at (must be installed) |
| Every N minutes | minutes | /SC MINUTE /MO N |
*/N * * * * |
| Hourly | minute of hour | /SC HOURLY |
M * * * * |
| Daily | time | /SC DAILY |
MM HH * * * |
| Weekly | weekday + time | /SC WEEKLY /D |
MM HH * * <dow> |
| Monthly | day 1-28 + time | /SC MONTHLY /D |
MM HH <dom> * * |
The time/date pickers use your system locale; the one-time date is rendered in
the system's short-date format for schtasks. One-time jobs on Linux/macOS use
at: the tool records the at job number on install, so they show as
installed (via atq) and can be uninstalled from the panel (via atrm),
just like recurring cron jobs. A one-time job that has already fired drops back
to saved (it is no longer queued).
Linux/macOS one-time jobs need
at. Theat/atq/atrmcommands must be installed and theatddaemon must be running, otherwise the job will not fire. On many distributionsatis not installed by default:sudo apt install at(Debian/Ubuntu) orsudo dnf install at(Fedora), then enable the daemon withsudo systemctl enable --now atd. (macOS shipsat, butatrunis disabled by default - enable it withsudo launchctl load -w /System/Library/LaunchDaemons/com.apple.atrun.plist.) Recurring jobs use cron instead and have no such requirement.
Each job connects with a saved connection profile (chosen in the Scheduling
tab), so different jobs can target different accounts. The scheduled task runs
the job by name (imap-cleanup-tool --run-job NAME) via the current interpreter
(so it works inside your virtualenv without relying on PATH); at run time the
CLI loads host / user / password from the profile's local SQLite DB. Only
non-encrypted profiles can be scheduled - a cron has no way to type the
password to decrypt an encrypted one.
Logs - every scheduled run appends to a rolling log file under
<config dir>/logs/<job>.log. In the Scheduling tab, click logs on any
saved job to view (or download) its run history.
Gmail notes
- Enable 2-Step Verification, then create an App Password and use it instead of your normal password.
- Enable IMAP in Gmail settings.
- Host is
imap.gmail.com. Folder names are special:[Gmail]/Trash,[Gmail]/All Mail,[Gmail]/Spam(localised, e.g.[Gmail]/Cestino). - Use
--gmail-trash: a plain delete inINBOXonly removes the label, not the message. Target[Gmail]/All Mailto catch archived mail too.
Support
Questions, bugs, or feature ideas? Open an issue or email support@imapcleanuptool.com.
License
GNU Affero General Public License v3.0 or later (AGPL-3.0-or-later) - see LICENSE.
This is free, open-source software with a strong copyleft: you may use, study, modify, and redistribute it, but any derivative work - including software that reuses any part of this code, and modified versions offered over a network as a service - must also be released as open source under the AGPL-3.0. You cannot incorporate this code into a closed-source or proprietary product.
Contributions are welcome - see CONTRIBUTING.md.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file imap_cleanup_tool-0.3.0.tar.gz.
File metadata
- Download URL: imap_cleanup_tool-0.3.0.tar.gz
- Upload date:
- Size: 982.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f49c9169cd9238d348587cc20a80d919487c93948b97a261cb335b5bd3b703ae
|
|
| MD5 |
e69167327917f6a1bd73d48ecc915588
|
|
| BLAKE2b-256 |
2c1a4393f56d01d6d1c2746503c6a3740bd377b0fe768f065f47786df3c3bd59
|
Provenance
The following attestation bundles were made for imap_cleanup_tool-0.3.0.tar.gz:
Publisher:
build-and-release.yml on mrpickles007/imap-cleanup-tool
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
imap_cleanup_tool-0.3.0.tar.gz -
Subject digest:
f49c9169cd9238d348587cc20a80d919487c93948b97a261cb335b5bd3b703ae - Sigstore transparency entry: 1818828101
- Sigstore integration time:
-
Permalink:
mrpickles007/imap-cleanup-tool@1a8fbec4ece609708009e82b60591abd7c33fdfe -
Branch / Tag:
refs/tags/v0.3.0 - Owner: https://github.com/mrpickles007
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
build-and-release.yml@1a8fbec4ece609708009e82b60591abd7c33fdfe -
Trigger Event:
push
-
Statement type:
File details
Details for the file imap_cleanup_tool-0.3.0-py3-none-any.whl.
File metadata
- Download URL: imap_cleanup_tool-0.3.0-py3-none-any.whl
- Upload date:
- Size: 974.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2135f95c4e8853391c31b49aa9c454d7fdcd87b9818f642be4eba4b695618c45
|
|
| MD5 |
273cb658ca63abb1d7bd1322fea032bc
|
|
| BLAKE2b-256 |
a34a1946abcb5d189e3ab0724ef7192d982c33c5c2c233f50bac0f21ba8f3741
|
Provenance
The following attestation bundles were made for imap_cleanup_tool-0.3.0-py3-none-any.whl:
Publisher:
build-and-release.yml on mrpickles007/imap-cleanup-tool
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
imap_cleanup_tool-0.3.0-py3-none-any.whl -
Subject digest:
2135f95c4e8853391c31b49aa9c454d7fdcd87b9818f642be4eba4b695618c45 - Sigstore transparency entry: 1818828213
- Sigstore integration time:
-
Permalink:
mrpickles007/imap-cleanup-tool@1a8fbec4ece609708009e82b60591abd7c33fdfe -
Branch / Tag:
refs/tags/v0.3.0 - Owner: https://github.com/mrpickles007
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
build-and-release.yml@1a8fbec4ece609708009e82b60591abd7c33fdfe -
Trigger Event:
push
-
Statement type: