How mimux works

mimux does not host your mail. It connects to the mailboxes you already have, keeps a local copy, and gives you one fast interface over all of them.

The model

You already have mail somewhere — Gmail, Fastmail, Zoho, a mailbox at your host, a self-run Dovecot. Those servers speak IMAP for reading and SMTP for sending. mimux is a client for them: you give it the credentials, it syncs the mail down, and from then on it is the thing you look at.

Four sentences describe the whole system:

  • One binary. A single Go executable serves the web interface, runs the sync workers and talks to the mail servers. No application server, no queue, no separate worker process.
  • One file. All state — accounts, credentials, downloaded messages, folders, sessions, filters, saved searches, OAuth tokens, API tokens — lives in one SQLite database, next to an auto-generated secret file. Back up that directory and you have backed up everything.
  • One inbox. Every configured account feeds a unified inbox, and each account also keeps its own view and its own folder tree.
  • One user. mimux is single-user by design: one admin account, created by the first-run wizard.

Zero configuration to boot

There is no config file. The handful of settings that cannot live in the database — where the database is, what to bind to, what the public URL is — come from environment variables, each with a working default, so an install with no environment at all boots and runs. Everything else is edited in the Settings GUI and stored in the database: accounts and their credentials, sync cadence, notification preferences, integration keys.

That means the first run is: start it, open it, create your admin user, add an account. See Installation for the details.

What syncing looks like

Each account gets a background worker holding an IMAP connection. When the server supports IDLE, that connection sits idle and the server pushes a notification the moment something arrives; the worker wakes and syncs. When it does not, or when IDLE drops, the worker falls back to polling on the interval from Settings → Syncing. IDLE is also broken deliberately whenever a foreground command needs the connection — opening an attachment, running a server-side search — and resumes afterwards.

A sync cycle walks a set of folders, not just the inbox: the inbox, Sent and Drafts to begin with, plus whatever else you tick per account in Settings → Syncing. That is what makes mail you sent from your phone turn up here within the cycle rather than on the next reconnect. The inbox goes first in every cycle and is the mailbox IDLE waits on, and it always syncs — it is the one folder the unread badge and notifications assume is current.

What a folder costs per cycle is proportional to what changed in it: new mail is fetched by UID, flag changes come from CONDSTORE where the server offers it, and a message deleted elsewhere is noticed from the mailbox's own message count. The expensive half — checking older messages for gaps and comparing stored UIDs with the server — runs on a deep pass instead of on every cycle: on the first pass of a folder mimux has not synced yet — a folder you have just ticked in Settings → Syncing, or one whose UIDVALIDITY changed under it — and every 30 minutes after that. That is what keeps an account with a very large All Mail from spending every cycle moving tens of thousands of UIDs over the wire, which is what providers throttle. Folders outside the set are re-read whenever the connection is re-established.

A deep pass checks mail in batches and serves queued reading actions between batches. This keeps opening a message responsive while a large folder is being reconciled.

A cycle is also bounded. A set too big to walk in one go stops at a time budget and resumes from where it stopped on the next one, so such an account reports “ok” on a schedule rather than sitting on “syncing” for minutes. A foreground command queued while a sweep is running is served between the sweep's own steps, and if a body fetch does end up waiting behind a sync the reading pane says so instead of showing a loading skeleton indefinitely — and a real message, with a retry, if the wait runs out.

Sync is one-directional-with-writeback rather than a full two-way mirror: messages and flags come down, and your actions (read, star, move, delete) are applied locally first and pushed to the server behind the request, so the interface never waits on IMAP. Flag writes that fail are retried by the sync loop.

Filters run on arrival, and only there

A filter rule is a list of conditions — from, to, subject or body preview; contains or regular expression — that must all match, and a list of actions to run when they do: mark as read, star, label, move to a folder, move to trash, forward, or notify you. Rules are evaluated top to bottom and every rule that matches applies, not just the first, which is why their order is yours to set.

They run on new mail arriving in an inbox. Not over the mail already on the server when you install mimux, or when you tick another folder to sync: that is a download, not a delivery, and a rule that trashed or forwarded a year of history on first run would be a disaster. Not on the Sent copy of what you write, not on Gmail's second copy in All Mail, and not on the draft you are still typing. Mail your provider filed into a folder by its own server-side rules is already filed; mimux leaves it where it is.

Before you enable a rule you can test it against your last few hundred stored inbox messages: the editor lists what its conditions would have matched, and changes nothing at all. And notify me is the same notification path as everything else — one buzz per batch, wording and transport from Settings → Notifications, where the master switch can also be set to only what my filter rules say.

What mimux is not

  • Not a mail server. It does not receive mail from the internet, has no MX record, and delivers nothing itself. It is a client for servers that do.
  • Not a hosted service. There is no mimux account, no central API host, no server of ours in the path. Your credentials go from your machine to your mail provider.
  • Not multi-tenant. One admin user per install. If two people need mailboxes, run two installs.
  • Not a groupware suite. Mail, with calendar invites understood well enough to read and answer them.

Privacy defaults

  • Remote images and every other external resource in an email are blocked until you click Load external content, or allow a sender permanently.
  • No CDNs, no analytics, no tracking: fonts, scripts and styles are all bundled into the binary and served from it.
  • The optional AI and translation features are off until you add your own API key, and both fail gracefully when unset.

Free, and pro

Everything above is free and AGPL-3.0, permanently. The paid layer is automation only: a REST API, an MCP server for agents, and webhooks. The line is who is driving — a human is free, a machine is pro. It is enforced with a Go build tag, so the free binary does not contain the pro code at all rather than disabling it at runtime; see Architecture.

Drafts follow you to your other clients

Saved drafts — and autosaved ones, if you turn autosave on — are written to SQLite first and then appended to your account's IMAP Drafts folder, so the half-written reply you left here is waiting on your phone. The local write is what makes that safe: publishing happens in the background, and a mail server that is slow, unreachable or mid-reauth costs a retry on the next sync cycle, never the draft.

IMAP has no "edit this message", so every save appends a fresh copy and then removes the one it replaces, keyed by a Message-ID that stays the same for the life of the draft. Interrupted halfway, you get a duplicate revision rather than a hole where the draft used to be. An account whose server has no Drafts mailbox gets one created; a server that refuses is not an error, the draft simply stays local until it can be published.

Attachments are part of the draft. A file picked in compose is stored with it and goes up in the published copy, so the draft on your phone is the whole unfinished message; reopening lists the files with a way to preview, download or remove them, and sending attaches them exactly as if you had just picked them. The same 25MB total a send allows applies to what a draft may keep. Undoing a delayed send puts the files back too.

A forward starts with the original message's attachments selected in its draft. You can remove any of them before sending; the kept files are fetched from the original when you submit the forward.

Drafts written elsewhere are editable. The drafts page is one list — yours and whatever is sitting in the account's Drafts folder — reached from Tools for every account at once, or from an account's own Drafts folder in the sidebar for just that one, the same split as the unified inbox and the per-account ones. Clicking any draft opens the editor, and deleting one removes the copy in the mailbox as well as the row here. A draft from another client opens in compose the first time you click it: it is fetched, read back into compose fields and attachments, and adopted as a local draft that remembers the copy it came from, so your first save replaces that copy rather than leaving a second one beside it. A rich-text draft comes back into the rich-text editor; anything else opens as plain text, because a message carries no trace of having been written in markdown and guessing would rewrite your words. A draft mimux cannot reproduce faithfully — signed, encrypted, or built around inline images the editor cannot author — is left alone and stays readable rather than being adopted damaged.

Known limitations as of v0.20. Offline mode is read-only: the service worker falls back to your last-synced inbox, and any action needs the server. Gmail label display is dormant, because the upstream go-imap v2 library cannot fetch X-GM-LABELS/X-GM-THRID yet — labels you apply here, by hand or with a filter rule, are stored and shown by mimux but do not travel back to Gmail; threading falls back to the standard JWZ algorithm, which works well.