ecr

A client/server mail client: a Rust server over notmuch, one SolidJS UI for browser, desktop and Android.

Email Client in Rust — a keyboard-driven mail client for a notmuch maildir you already run.

I built it because terminal mail clients are fast and cannot render HTML mail, and graphical ones render it and are slow to drive. ecr puts a real browser engine behind vim keys. A Rust server owns all the mail state; one SolidJS client ships to the browser, the desktop and Android.

It does not fetch, store or index mail. It reads the maildir notmuch already indexes and shells out to mbsync and msmtp for the parts those tools do well. If you have a working notmuch setup, this is an interface for it. If you do not, it is not the place to start.

Browser ─┐
Desktop ─┼─ HTTP/JSON + SSE ─→ ecr-server ─→ ecr-store ─→ notmuch / mbsync / msmtp
Android ─┘   bearer token                       │             maildir
                                          revision (lastmod)

Beyond these pages: README for installation and the roadmap, CONTRIBUTING for conventions, SECURITY for the threat model, and THIRD-PARTY for every dependency's licence.

🔗Quick start

direnv allow          # or: nix develop
just install          # once
just run              # builds the client, starts the server, opens a browser

That is the whole thing. just run serves the UI and the API from the same origin on http://127.0.0.1:8399, so there is nothing to configure — no URL to paste, no CORS, and no token needed for local use. An installed server uses 8383 instead, and the recipes stay off that port so both can run at once.

For the desktop window instead of a browser tab:

just desktop          # starts the server too, unless one is already running

Other useful commands:

just                  # list every recipe
just doctor           # explain the mail setup; the server refuses to start unless healthy
just token phone      # issue a device token, with a pairing QR
just dev              # web client with hot reload on :1420, against a running server
ECR_BIND=<tailnet-addr>:8399 just serve   # reachable from a phone

doctor is the gate. It resolves every config file, reports which one it chose and why, and refuses to let the server start if the setup cannot work. The previous implementation of this project failed precisely because it looked for mail configuration in the wrong places and had no way to say so.

🔗Status

PieceState
ecr-core, ecr-storecomplete: search, threading, MIME, tagging, sync, send
ecr-servercomplete: REST, SSE, bearer auth, maildir watcher
ecr-cli (ecr)doctor, serve, tokens, oauth, help, man and completions; init, web, qr and the background lifecycle are declared but not yet implemented
webcomplete: read, search, tag, mark queue, compose, mobile layout
shell (Tauri desktop)builds; external-$EDITOR command, mailto: handling and notifications wired; packaged as deb, AppImage and a Nix derivation
Androidbuilds and runs; APK and AAB per release, own launcher icon, mailto: handler — see operations.md
SQLite mail indexbuilt: mailbox listings and counts answered from SQLite, with anything it cannot answer identically — text search included — falling through to notmuch
  • Installing

    The two published channels, Home Manager, NixOS, and what each release artifact carries.

  • Operations

    Running the server, device tokens, doctor, Android, and troubleshooting.

  • Architecture

    The crates, the data flow, and why the pieces are shaped this way.

  • HTTP API

    Every endpoint ecr-server exposes, with its shapes and status codes.

  • Development

    Building, testing and verifying a change.

  • Releasing

    How a release is cut, what CI produces, and what has to be true first.

  • Parity

    What ecr has, what it does not, and what is deliberately missing, against Thunderbird, Gmail and Outlook.