How to Build Your Own Gmail Client (So Your Email Isn’t Tied to a Google Storage Plan)
Google’s answer to a full mailbox is a bigger storage plan, but you can keep a synced copy of your mail on a computer you already own and let Gmail keep doing the delivering.
Ingredients
- Gmail API access — the same self-hosted setup from my Gmail MCP post (June 12) (free)
- Python + Flask — a small web server for the inbox, plus a separate background process for syncing (free)
- SQLite with full-text search — the searchable index (free, built into Python)
- An always-on computer you own — where your copy of the mail lives (one you already own)
- Claude Code — writes most of the code and runs the testing rounds
The Problem: Paying Rent on Your Own Email
Gmail’s free 15 GB is shared with Google Drive and Google Photos. When it fills up, the only path Google offers is a paid plan, and then a bigger one. Past 200 GB, the plans also bundle AI features you may not want.
The alternative is to delete old mail and big attachments. People don’t, because deleting from Gmail feels final. Receipts, leases, warranties, the thread where someone agreed to something in writing: you can’t guess which ones you’ll need later.
Google Takeout gives you a one-time export, which is a snapshot, not a habit. What you want is a copy that stays current on its own and that you can read and search like email. Once you have that, cleaning up Gmail stops being risky, and staying on the free tier becomes realistic. Gmail goes back to being the post office instead of the storage unit.
Where Mine Landed
My version, Mailroom, behaves like Gmail closely enough that I stopped noticing the difference. It doesn’t clean up Gmail for me, and I didn’t want it to. What it changes is the risk: clearing out old mail and big attachments stopped being scary, because a searchable copy already exists outside Google.
One warning for your own build: before you delete anything from Gmail, make sure your copy is backed up somewhere else too. Leaving Google’s storage only works if your own is at least as durable.
Here’s how to build one.
Rule #1: Let Gmail Stay in Charge
The tempting version of this project is “replace Gmail”: run your own mail server, move your domain, never look back. Don’t. That means taking on spam filtering, deliverability, and a mail server that has to be up every minute of every day.
Gmail stays the post office. Your client is just a better window into it. Mail still arrives at Gmail and still leaves through Gmail. Your client downloads a copy, gives you your own inbox on top, and pushes your changes back. That one rule settles a lot:
- The Gmail app on your phone keeps working. Nothing about your mailbox moved.
- Conflicts have an obvious winner. If your client and Gmail disagree, Gmail is right.
- There’s always an escape hatch. If your client breaks, open Gmail.
How the Setup Works
The whole thing is four pieces, with one rule between them: only one piece is allowed to change your real mailbox.
- The sync process pulls new mail down and pushes your changes up. It’s the only piece with Gmail access.
- The local store keeps every message as a raw
.emlfile, plus a search index built from those files. The files never get edited. The index can be deleted and rebuilt any time, which makes bugs cheap to fix. - The web app is the inbox you use. When you archive something, it updates the local copy instantly and leaves a note in a queue; the sync process makes the matching change in Gmail a few seconds later. Because the web app has no Gmail keys, a bug there can’t send or delete real mail.
Step 1: Keep the Copy in Sync (Twice)
Gmail offers a change feed: ask what changed since last time and it tells you. Build on it, but don’t trust it alone. It occasionally skips events, and a missed event is never sent again. You’ll see it as messages you read in Gmail still showing as unread in your client.
The fix is a second, slower loop that re-asks Gmail directly (“which messages are unread right now?”) and corrects anything that drifted. The fast feed keeps you current; the slow check keeps you honest.
🔧 Developer section: sync gotchas
- Add
in:anywhereto reconciliation searches. Gmail search skips Trash and Spam by default, so without it your check “fixes” messages that were fine. - Pace requests. Gmail throttles bursts even when your per-minute total is legal.
- Only give up on a real “no.” A 400, 403, or 404 means stop. A 429 means try again later.
Step 2: Make Search Match Gmail, Not Just Look Like It
You’ll want Gmail’s search syntax to work: from:, has:attachment, is:unread, - for “not.” To test it, run the same query in both and compare which threads come back, not how many.
Watch for word stemming: many search engines treat “order,” “orders,” and “ordered” as one word. Gmail doesn’t. A query can return the exact same count as Gmail, which looks like a pass, while the threads don’t overlap at all. Also make sure a search your code can’t understand says so, instead of showing zero results. An empty list looks exactly like “nothing matched.”
Step 3: Add Sending, With an Undo That Works
Replicate the parts of Gmail you actually use: replies that stay in the right thread, drafts that are real Gmail drafts (start on your laptop, finish on your phone), forwards that keep their attachments. Sent mail shows up in Gmail’s Sent folder and syncs back down like anything else.
Undo send works the way Gmail’s does: hold the message for 30 seconds before it really goes out. A common trap: undo can look like it worked while the email still goes out, if you hit it in the split second the sync process is picking the message up. The screen says cancelled, the database says cancelled, and the email sends anyway. The fix is small:
🔧 Developer section: check that your claim actually won
UPDATE outbox SET status='sending' WHERE id=? AND status='held'only protects you if you checkrowcountafterward. Zero rows means Undo got there first. Don’t send.
Step 4: Keep It Off the Internet
A page with all your email behind it shouldn’t be on the public internet, even behind a password. Run it on a private network, such as Tailscale, instead, which connects only your own devices. Your phone and laptop can reach it from anywhere; to everyone else, there’s nothing there. Keep your Gmail token outside the code, and keep mail data out of git entirely.
Step 5: Test It Like a User, Not a Checklist
Before switching over, have Claude agents use your client next to real Gmail and report every difference. Tell them to check that things work, not just that they show up. A few tests worth adding to the list:
- Click a link inside an email. Rendering an email and making its links work are two different jobs.
- Search for something outside the Inbox. Archived mail should come back too.
- Download an attachment with an accent or emoji in its name. Then open it and make sure it isn’t empty.
Do the thing a person would actually do: click the link, download the file, run the search, and read what comes back.
Build Notes
What went fast
- The first working reader. Download mail, index it, show a list.
- Reusing Gmail access from my earlier MCP server.
- Deciding up front that only one piece writes to Gmail.
What needed patience
- Matching Gmail exactly. “Close enough” search isn’t close enough when you’re looking for a lease.
- Testing behavior, not appearance. The bugs worth finding don’t crash.
Try Building One
If your Gmail is creeping toward the storage limit, this is a very buildable project with Claude Code, as long as you start with the rule: Gmail delivers, you keep the copy. If you build one, or get stuck partway, tell me about it. I’d like to hear what you ran into.