Mail user agent backed by notmuch.
README.md

übersicht #

Not much of mail a user agent.

Übersicht lists mail, reads threads and writes mail. It does not fetch mail, index mail, send mail or keep an address book. Those jobs are better left to other tools, like notmuch, isync, msmtp and khard. Übersicht draws the screen.

Screens #

Three screens, to be exact. Übersicht starts in

  1. The Pager lists messages that are the result of a notmuch query. Each query is a tab. Übersicht reads your notmuch saved queries (query.* in the notmuch config) on startup. Press / to add new queries and t to modify tags. Press enter to switch to ...

  2. The Reader shows a thread. Get a thread overview on the left. Read the message and inspect headers on the right. Press h to expand header display. Press tab to cycle message parts. Press s to save parts to files and o to open it externally (e.g., text/html in the browser; of course Übersicht tries its best to sanitize the content before). Pressing r drafts up a reply in ...

  3. The Composer drafts mails. Edit the body and headers with your favourite $EDITOR (that's right, übersicht doesn't even ship its own editor - I'm that lazy). Send a mail with y or decide to save it as a draft for later.

Why another one? #

Mostly because of multiple accounts. I tried mutt, neomutt, aerc, alot, meli and astroid. neomutt came closest, but its multi-account setup is a chore, and none of the others did all of what I wanted.

What I wanted, and what übersicht does about it:

  • notmuch native. Übersicht reads a single notmuch database that holds every account. Searching and reading don't care which account a message came from.
  • Sending is the only per-account part. Fetching is isync's job and indexing is notmuch's. Neither knows übersicht exists.
  • Replies come from the right address. Replying pre-fills From from the message's Delivered-To and that determines the account to use for replies and forwards (i.e., sendmail command, archive maildir and signature).

The price is that übersicht does nothing on its own. You need a sync tool, notmuch and an MTA wrapper set up first. If you want a client that handles all of it, this isn't it.

If you dare to try, docs/setup.md walks you through a complete setup.

Building #

You need a terminal, libnotmuch (headers at build time, shared library at runtime), a notmuch database with mail in it, a sendmail-compatible binary and $EDITOR.

Optional: notmuch on PATH for the saved-query tabs, xdg-open and a mailcap for opening parts, khard for address lookup, a desktop portal for the attachment file chooser.

libnotmuch is linked into the binary. Unless notmuch is installed system-wide, its shared library must be on LD_LIBRARY_PATH when you run it.

With Nix #

nix build
./result/bin/ubersicht

nix develop gives you a shell with cargo, just and notmuch wired together. nix flake check builds the package and runs the tests.

Without Nix #

just build      # ./target/debug/ubersicht
just release    # ./target/release/ubersicht

The recipes find libnotmuch via your notmuch binary and set RUSTFLAGS and LD_LIBRARY_PATH. To call cargo yourself, set both:

export RUSTFLAGS="-L /usr/lib"
export LD_LIBRARY_PATH="/usr/lib"
cargo build --release

Configuration #

The config is KDL, read from $XDG_CONFIG_HOME/ubersicht/config.kdl (or ~/.config/ubersicht/config.kdl). Übersicht won't start without at least one account block:

account {
    match ".*@example\\.com"
    maildir "~/Mail/example.com"
    sendmail "msmtp" "--account=example" "-t"
}
  • match is a regex tested against the outgoing From address. It picks the account.
  • maildir is where sent mail and drafts get archived. Put it inside your notmuch database root, or notmuch won't index them.
  • sendmail is the command that delivers the mail. Default: sendmail -t. Übersicht pipes the message in and passes no recipients, so the command has to read them from the headers (-t).

All other options are in docs/config.md. Loading is strict: unknown nodes, unknown action names, duplicate keys, and bindings that don't work on their screen are errors. The message gives file, line and column.

First run #

If you start from nothing: read docs/setup.md to set up isync, notmuch and msmpt for two accounts.

Otherwise: Run notmuch new, write a config with one account block, start ubersicht. The pager opens on your first tab's query. q quits.

Multiple accounts #

Add one account block per identity. Übersicht picks the account whose match regex hits the From address, then archives to that account's maildir and sends through its sendmail command. Starting the composer from a message pre-fills From, so a reply usually ends up on the right account by itself.

Keys #

These are the defaults. All of them can be rebound, see docs/config.md.

Pager

Key Action
q quit
enter open message
/ search
space toggle row in/out of selection
escape clear selection
up down previous/next message
left right previous/next branch
j k h l like the arrow keys (see below)
pageup pagedown home end scroll
n new mail
r g f reply, group reply, forward
t tag prompt
e reopen draft

Reader

Key Action
q back to pager
up down left right previous/next message, previous/next branch
j k l next message, previous message, next branch
pageup pagedown home end scroll
tab next MIME part
o s open / save active part
h cycle header display
e reopen as draft
n r g f new mail, reply, group reply, forward
t tag prompt

Composer

Key Action
e edit message in $EDITOR
t c b edit To, Cc, Bcc
a d add / remove attachment
y send

q on an unsaved draft asks whether to save it. save and send are configurable actions; only send has a default key.

j, k, h and l are fallbacks, not bindings. They only fire when nothing else binds the key on the current screen. That's why h cycles headers in the reader (the builtin wins) but steps to the previous branch in the pager.

Documentation #

specs/ states intent. Agents never edit it.

Development #

Übersicht is written by LLM agents working from a spec. The toolchain they use lives in the repo: a set of skills in .opencode/skills/, chained into three loops. jj is the VCS. just check, just test and just build are the build.

Skills, loops and AGENTS.md change along with the project.

Iteration loop: move toward the specs one planned step at a time.

  1. plan-iteration reads the specs and the last review and writes iterations/$N/plan.md.
  2. implement-iteration carries out the plan, one jj change per phase, and checks the acceptance criteria.
  3. review-iteration compares the result with plan and specs, runs review-panel and writes iterations/$N/review.md, which feeds the next plan.

The review panel runs multiple lenses as subagents. It then summarizes where they agree, where they clash, and what to do about it.

Backlog loop: small stuff.

  1. create-ticket drafts a user-story ticket.
  2. groom-backlog prunes, merges, splits and prioritizes backlog/.
  3. implement-backlog works through it in priority order, one jj change per ticket, reviewing each adversarially until the review passes.

Maintenance loop: housekeeping.

code-refactor audits comments, readability and tests, and files tickets for anything too big to fix on the spot. code-review-guide says what a review here should catch.