- Rust 100%
- Nix <1%
- Shell <1%
ü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
-
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 andtto modify tags. Pressenterto switch to ... -
The Reader shows a thread. Get a thread overview on the left. Read the message and inspect headers on the right. Press
hto expand header display. Presstabto cycle message parts. Presssto save parts to files andoto open it externally (e.g.,text/htmlin the browser; of course Übersicht tries its best to sanitize the content before). Pressingrdrafts up a reply in ... -
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 withyor 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
Fromfrom the message'sDelivered-Toand that determines the account to use for replies and forwards (i.e.,sendmailcommand, 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"
}
matchis a regex tested against the outgoingFromaddress. It picks the account.maildiris where sent mail and drafts get archived. Put it inside your notmuch database root, or notmuch won't index them.sendmailis 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 #
- docs/config.md: every config option, with type and default
- docs/architecture.md: how it's put together
- docs/security.md: what is sanitized, what is not, what runs
- specs/goal.md: what it does and what it refuses to do
- specs/ui.md: the screens
- specs/architecture.md: the original architecture sketch
- specs/config.md: the normative config shape
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.
- plan-iteration reads the specs and the
last review and writes
iterations/$N/plan.md. - implement-iteration carries out the
plan, one
jjchange per phase, and checks the acceptance criteria. - 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.
- create-ticket drafts a user-story ticket.
- groom-backlog prunes, merges, splits and
prioritizes
backlog/. - implement-backlog works through it in
priority order, one
jjchange 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.