A free, privacy-focused tool to bulk unsubscribe from emails, delete emails by sender, and mark emails as read. No subscriptions, no data collection - runs 100% on your machine.
No Subscription Required - Free Forever
| Feature | Description |
|---|---|
| Bulk Unsubscribe | Find newsletters and unsubscribe with one click |
| Delete by Sender | Scan and see who sends you the most emails, delete in bulk |
| Bulk Delete Multiple Senders | Delete emails from multiple senders simultaneously with progress tracking |
| Mark as Read | Bulk mark thousands of unread emails as read |
| Archive Emails | Archive emails from selected senders (remove from inbox) |
| Label Management | Create, delete, and apply/remove labels to emails from specific senders |
| Mark Important | Mark or unmark emails from selected senders as important |
| Email Download | Download email metadata for selected senders as CSV |
| Smart Filters | Filter by date range, email size, category (Promotions, Social, Updates, Forums, Primary), sender, and labels |
| Privacy First | Runs locally - your data never leaves your machine |
| Super Fast | Gmail API with batch requests (100 emails per API call) |
| Gmail-style UI | Clean, familiar interface with real-time progress tracking |
Works on all major platforms - both Docker and local installation:
| Platform | Docker | Local (Python) |
|---|---|---|
| Linux (x86_64) | Native | Native |
| Windows (x86_64) | Native | Native |
| macOS Intel | Native | Native |
| macOS Apple Silicon (M1/M2/M3/M4) | Native | Native |
credentials.json and token.json never get committedA few people reached out to me on Reddit and via email saying they love the idea, but don’t have the technical expertise to run this software themselves. I’d also like to grow the project further, so support would really help make the time I invest in it more worthwhile.
Struggling with Docker, Google Cloud Console, or credentials.json? I can help you set it up personally!
I offer a 1-on-1 Setup Service ($8) where we hop on a Google Meet, you share your screen, and I guide you through the entire installation until it's working perfectly.
Book a Setup Session Here - mail me at guruvelu85@gmail.com, i will reply and setup an gmeet call

Watch Setup Video on YouTube - Step-by-step video on how to setup the repo and run the project locally.
Lets make this tool a better one by improving as much as possible, All features are welcome, To request a feature, open a GitHub issue.
Important: You must create your OWN Google Cloud credentials. This app doesn't include pre-configured OAuth - that's what makes it privacy-focused! Each user runs their own instance with their own credentials.
Video Tutorial: Watch on YouTube for a visual walkthrough
| Setup | Application Type | Redirect URI |
|---|---|---|
| Local/Desktop (Python with browser) | Desktop app | Not needed |
| Docker (localhost) | Web application | http://localhost:8767/ |
| Docker/Remote Server (public domain) | Web application | http://YOUR_PUBLIC_DOMAIN:8767/ |
⚠️ Important: Redirect URIs must use a domain name (e.g.,
gmail.example.com), NOT an IP address (e.g.,192.168.1.100). Google OAuth does not allow IP addresses. If you need to use a server IP, use a Dynamic DNS service to get a domain name.
credentials.json💡 Which should I choose? - Running locally with Python (
uv run python main.py)? → Desktop app - Running with Docker or on a remote server? → Web applicationNote: If using custom port mapping or a custom domain, see Advanced Configuration for redirect URI details.
git clone https://github.com/Gururagavendra/gmail-cleaner.git
cd gmail-cleaner
credentials.json file in the project folder.docker compose pull && docker compose up
http://localhost:8766
Click "Sign In" button in the web UI
Check logs for the OAuth URL (only after clicking Sign In!):
docker logs $(docker ps -q --filter ancestor=ghcr.io/gururagavendra/gmail-cleaner)
Or if you built locally:
docker logs $(docker ps -q --filter name=gmail-cleaner)
🌐 Using a custom domain, remote server, or custom port mapping? See Advanced Configuration for setup instructions.
The docker-compose.yml includes a data directory volume mount that automatically persists your authentication token.
How it works:
./data directory on your host is mounted to /app/data in the containertoken.json is automatically saved to /app/data/token.json inside the container./data/token.json on your host filesystemNo manual steps required!
docker compose up - the data directory is created automatically./data/token.json on the hostTo reset authentication:
If you need to sign in with a different account or reset authentication:
# Stop the container
docker compose down
# Remove the token file
rm -f ./data/token.json
# Start again (will prompt for new authentication)
docker compose up
uv sync
uv run python main.py
The app opens at http://localhost:8766
Q: Why do I need to create my own Google Cloud project?
Because this app accesses your Gmail. By using your own OAuth credentials, you have full control and don't need to trust a third party.
Q: Is this safe?
Yes! The code is open source - you can inspect it. Your emails are processed locally on your machine.
Q: Can I use this for multiple Gmail accounts?
Yes! Click "Sign Out" and sign in with a different account. Each account needs to be added as a test user in your Google Cloud project.
Q: Emails went to Trash, can I recover them?
Yes! The delete feature moves emails to Trash. Go to Gmail → Trash to recover within 30 days.
Q: Can't delete or modify files in the ./data directory?
Docker containers run as root by default, so files created in
./data(liketoken.json) are owned by root. To fix permissions:bash sudo chown -R $USER:$USER ./data/Or to delete a specific file:bash sudo rm ./data/token.jsonThis is a common Docker behavior - the files are safe, just owned by root for security reasons.
Q: Having OAuth authentication issues?
Check the Troubleshooting section for common solutions.
If you're using custom port mappings in Docker (e.g., mapping 18766:8766 and 18767:8767):
yaml
services:
gmail-cleaner:
ports:
- "18766:8766" # Web UI (external:internal)
- "18767:8767" # OAuth callback (external:internal)
environment:
- WEB_AUTH=true
- OAUTH_EXTERNAL_PORT=18767 # External port that browser will use
http://localhost:18767/ (or http://YOUR_DOMAIN:18767/ if using custom domain)Note: Must be a domain name, not an IP address
Restart the container:
bash
docker compose down && docker compose up
💡 How it works: The app listens on port 8767 inside the container, but sets the OAuth redirect URI to use port 18767 (the external port). Docker forwards the external port to the internal port.
If you're accessing via a custom domain (e.g., gmail.example.com) instead of localhost:
⚠️ Important: - Use Web application credentials (not Desktop app) for remote server setups. See Step 7 in Get Google OAuth Credentials. - IP addresses are NOT allowed in Google OAuth redirect URIs. You must use a domain name (e.g.,
gmail.example.com), not an IP address (e.g.,192.168.1.100). - Google requires redirect URIs to use a public top-level domain (.com,.org,.net, etc.)
Allowed redirect URIs:
- ✅ http://localhost:8767/ (for local access)
- ✅ http://gmail.example.com:8767/ (custom domain)
- ✅ http://mygmail.duckdns.org:8767/ (dynamic DNS)
- ❌ http://192.168.1.100:8767/ (IP addresses not allowed)
- ❌ http://10.0.0.5:8767/ (private IPs not allowed)
If you need to use a server IP:
- Use a dynamic DNS service (free options: DuckDNS, No-IP, Dynu)
- Point the domain to your server's IP address
- Use the domain name in OAuth (e.g., http://mygmail.duckdns.org:8767/)
http://YOUR_DOMAIN:8767/ (or external port if using custom mapping)Must be a domain name, not an IP address
Update docker-compose.yml:
yaml
environment:
- WEB_AUTH=true
- OAUTH_HOST=gmail.example.com # Just the hostname - NO http:// or https://
# Optional: If using custom port mapping
- OAUTH_EXTERNAL_PORT=18767
⚠️ Common mistakes: - Use only the hostname (e.g.,
gmail.example.com), NOT the full URL (e.g., ~~https://gmail.example.com~~) - Use a domain name, NOT an IP address (e.g., ~~192.168.1.100~~)
http://YOUR_DOMAIN:8767/ (HTTP, not HTTPS) or use the external port if mappedYour app is missing test users in the OAuth setup:
Why? Since your app is in "Testing" mode, only emails listed as test users can sign in. This is normal and expected!
$ claude mcp add gmail-cleaner \
-- python -m otcore.mcp_server <graph>