A fast Gmail and Google Calendar client for macOS and Linux.
I got tired of waiting on Gmail, so I wrote my own. All my accounts in one list, every key where Gmail put it. There's an agent in it too, and it can archive and reply rather than just tell me what's in there. Gmail and Google Calendar only — no IMAP, no other providers.
Five accounts in one inbox
Everything in date order. None of it's real mail, though. That's the test suite's fixture data, and the status bar says so.
Everything you look at comes out of a SQLite database on your own disk. Opening a thread doesn't ask Google for anything, so there's no spinner, and it works on a plane. Sync runs behind that and writes into the same database.
- Local data Twelve months per account, on your disk. Sync fills in behind you.
- Search Gmail's operators, compiled to SQL. 61,000 messages, no waiting.
- Keyboard Gmail's own keys, so there's nothing to learn twice.
- Every account One list. Replies go out from the account the mail came in on, and a new message picks.
- Calendar Day, week and month. Drag to make an event, or to move one.
- Writing Drafts save themselves, several at a time. Ten seconds to change your mind.
- Attachments Drag a file in to send it. The type comes from the bytes.
- Notifications Only for mail that earned one. Newsletters stay quiet.
- The agent Runs on your Claude Code CLI. Stops before anything leaves.
-
From a shell
machhas every verb ⌘K has. Searching works with the app shut. - Unsubscribe One key archives it and unsubscribes. Spam gets reported instead.
- Copy for a chatbot The thread as plain text, quotes stripped, ready to paste.
- Plugins Sandboxed: no network, no files, none of your credentials.
The long version
- Local data
- Twelve months backfilled per account, then incremental history sync from a stored watermark. The backfill runs in the background, so you can read mail while it's still filling in behind you — which is just as well, because Gmail's quota caps it at five messages a second and a big mailbox takes over an hour.
- Search
-
Gmail's operator syntax, parsed as you type and compiled to SQL
against an FTS5 index.
from:,has:attachment,older_than:2m, quoted phrases,ORand-all work. Mine is around 61,000 messages and I've never waited on it. - Keyboard
- Gmail's own, so there's nothing to learn twice. J K to move, E to archive, B to snooze, C to write, R to reply, G I for the inbox. ⌘K for search and commands, ? for the full list. ⌘Z undoes over a stack that does not expire.
- Several accounts
- One list, in date order, with a colour bar on each row telling you whose it is. Replies go out from the account the mail came in on, so you can't answer your mother from work by accident. A new message has a From row to pick with; it starts on whichever account the list is filtered to.
- Calendar
- Day, week and month, and you can drag to make an event or drag it somewhere else. Open one and you get the guests and who's actually coming, the recurrence rule, the reminders, and the Meet link.
- Writing
- Drafts save themselves and go up to Gmail, so one started here can be finished on your phone. Several can be open at once, as tabs along the bottom, and any of them can be popped out into its own window. The body is plain text with a bit of markdown in it, sent as HTML. A send sits in the outbox for ten seconds before it leaves — longer or shorter in preferences — and ⌃S schedules it for three hours' time, tomorrow morning or Monday instead.
- Attachments
- Drag a file onto a composer to send it, or press ⇧⌘A. Incoming ones are fetched, cached, opened and saved, with inline images drawn in the message. What kind of file it is comes from the bytes themselves, not from what the sender claimed, and executables don't open.
- Notifications
- New mail gets a notification and a Dock badge, but only if it has earned one: unread, in the inbox, not from you, and either Gmail filed it as personal or it's a thread you've already written into. Newsletters stay quiet.
- The agent
- Every action in Mach is a typed command in Rust, and the agent gets that same list and nothing else. There's no second path to Google. It runs on the Claude Code CLI if you have one, so it spends the subscription you're already paying for and never asks for an API key. Anything that actually leaves the building stops and waits for you to say yes. Everything else is undoable and shows up in the same log as the things you did yourself. ⌘K will also hand a thread off to an agent running somewhere else.
- From a shell
-
machis the same list of commands the agent gets, from a terminal. The verbs aren't written down anywhere in the CLI — it reads them off the app at runtime, so it can't fall behind what the app can do.mach search invoiceand the other reads open the SQLite file directly and work whether or not Mach is running. Anything that writes goes over a loopback socket to the running app, which is the only process holding the tokens, the outbox and the undo stack; with the app shut, a write fails and says so rather than queueing. - Unsubscribe
-
⇧⌘U on a newsletter archives it and unsubscribes in the
background. Most bulk senders support one-click now, so that's a
single request and no page; the rest get the unsubscribe email
sent for them. If all a sender offers is a link to a form, you get
a button that opens it. Mach won't click through a page it hasn't
read, and I'd rather hand you the link than send a request that
only might have worked.
It doesn't offer on everything that carries an unsubscribe link, either. Unsubscribing tells the sender your address is live and somebody reads it. Fine for a newsletter you asked for. For spam it's the most useful thing you could hand them, so mail that looks like spam gets offered the spam report instead. - Copy it for a chatbot
- ⌥⌘C puts whatever's on screen on the clipboard as plain text — the whole thread, quotes stripped, ready to paste into whatever you're asking. Or the week, or a search and its results. ⇧⌥⌘C takes just the one message you're reading, which is usually what I actually wanted.
- Plugins
- Somebody else's code runs in a sandbox with no network, no files and none of your Google credentials. All a plugin can do is send the same commands your keyboard sends, and only the ones you let it, which is also why ⌘Z takes its work back.
Five calendars in one grid
Week view, with every event tinted by the calendar it came from and a red line on the current time. Fixture data again.
Ask from ⌘K and the answer comes back in a drawer under the list, with every tool it called and what each one returned. I don't take its word for things either.
The agent shows its work
Here it drafted a reply, then stopped and waited for me to say send. The monospaced lines are the tools it called on the way.
Or hand the thread somewhere else
The same conversation on its way to Claude Code in a terminal. The first time you send a thread somewhere new, you get to read the whole thing first: the command, the directory, and the text itself.
I'm not planning to do any of it.
- No IMAP or SMTP. It's the Gmail API or nothing.
- No other providers. Not Outlook, not Fastmail, not iCloud.
- No mobile, no web, no Windows. macOS and Linux desktop. The tokens live in the macOS Keychain or the Secret Service depending on which you're on, and the window is a WKWebView or WebKitGTK. Wayland only — X11 isn't covered.
- No teams and no sharing. One user, and it's me.
- No packaged build, no releases, no auto-update. You build it yourself.
-
Prerequisites
- macOS, Apple silicon or Intel — or Linux on Wayland, tested on Omarchy (Arch with Hyprland)
- Rust stable. On macOS, the Xcode command line tools; on Linux, webkit2gtk-4.1, gtk3, libsoup3 and wl-clipboard
- Bun
- A Google account you can create a Cloud project with
- For the agent, either the Claude Code CLI or an Anthropic API key. Mach uses the CLI when it finds one.
-
Bring your own Google OAuth app
This is the worst part, and there's no way around it: Mach ships with no credentials, so you register your own Google Cloud project and OAuth client. I'm not putting a personal mail client through Google's verification. Hand
skills/mach-setup/SKILL.mdto a coding agent that can drive a browser and it'll click through the console with you: the project, the two APIs, the consent screen, a Desktop app client, the seven scopes, publishing, and writing.env.local. It gives the keyboard back when it hits your password and the User Data Policy. If you'd rather do it by hand, the same file reads as a checklist.Google will warn you about the app. The first time you authorise an account you get "Google hasn't verified this app", which is true — it's your own unverified app. Click Advanced → Go to Mach (unsafe). Once per account.
Set it to In production anyway, unverified. Leave it in Testing and Google throws away every refresh token after seven days, which means signing every account back in once a week, forever.
-
Build and run
git clone https://github.com/bborn/mach cd mach bun install bun run tauri devThen ⌘K → Add account. If you got the credentials wrong it still boots and the window still paints — it just tells you it can't add an account.
The command line is a second binary,
mach-cli, sincemachis the application.scripts/machbuilds it if it's missing and runs it; symlink that onto yourPATHasmachfor the short name.
This is my mail client. I built it for me and put it up here in case it's useful to anybody else. It isn't a product, there's no support, and there are no releases. I read my mail in it every day; the parts I don't use are probably broken.
Nothing is packaged, nothing updates itself, there's no onboarding past
adding an account, and marketing mail still
renders like a ransom note more often than I'd like.
cargo test and bunx vitest run are the whole
safety net — no end-to-end suite. ⌘K →
Send feedback grabs the window, lets you scribble on
the screenshot, and drops the result in
.feedback/inbox/ for a coding agent to pick up. That's how
most of this got fixed.
I wrote down why it's built this way, at some length, in the design record and the README.