Skip to content
CaffeinatedTechPublic

About

Terminal email client that speaks JMAP. live push, multi-account, no local mail storage. Works with Stalwart and Fastmail.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

jmap-tui

Your email, in the terminal. Fast, live, and easy on the eyes — a modern mail client for people who'd rather type than click.

jmap-tui is a full email client that lives in your terminal. Read, reply, search, archive, and send — across as many accounts as you like — with a keyboard-first interface that stays out of your way. It talks JMAP, the modern email protocol, which means mail updates arrive live and nothing is ever copied to your hard drive.

  • Instant. Every keystroke draws immediately. No spinners, no lag, no jank — even with 100,000 messages in a folder.
  • Live. New mail, flag changes, and moves appear within a second. You never press refresh.
  • Private. Your mail is never written to disk. Your password lives in your OS keyring. Quit, and not a trace of your mail is left behind.
  • Multi-account. One sidebar for all your accounts, an instant switcher, and a unified inbox that merges everything by date.
  • Beautiful. Clean typography, one accent colour, dark and light themes that follow your terminal.
jmap-tui  Inbox  1432 messages · 3 unread
─────────────────────────│━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━│──────────────────────────────────────────
█ ▾ Work                █│  ★   Dana Ops         ▸ Deploy pipeline…  35m    │From: Eve Security <from@example.test>
  Inbox                 3│   ↩  Eve Security       Quarterly audit…  3h     │To: me@example.test
  Sent Items             │      Bob Thread       ▾ Re: planning sync 1d     │Date: Mon, 21 Sep 2026 07:00
▾ Archive                │ ●    Alice Root         └ planning sync   1d     │Subject: Quarterly audit report attached
    agent-test           │↓ more                                            │──────────────────────────────────────────
                         │                                                  │Hello,

Folders on the left, messages in the middle, the message itself on the right. The heavy line under a column shows where your keyboard is.

Works with any JMAP server — Stalwart and Fastmail are verified today, and Cyrus, Apache James, and friends speak the same protocol.

JMAP only. One protocol, done properly — no other mail protocols, now or on the roadmap.

Everything you'd expect from a mail client, and then some:

  • Read threads, expand and collapse replies
  • Archive, delete, move, copy, star, mark unread — with a 5-second undo on anything destructive
  • Server-side search across one folder or all of them, plus a filter form
  • Compose, reply, reply-all, forward — with attachments, drafts that live on the server, and a send delay you can cancel
  • Contacts, unified across accounts, with autocomplete in the composer
  • Multi-account with an instant switcher and a unified inbox
  • Add an account from your email address alone — the JMAP server is discovered for you
  • Credentials in your OS keyring, never on disk
  • Rebind any key, pick your layout, dark or light

Install

One command, on Linux or macOS:

curl -fsSL https://raw.githubusercontent.com/CaffeinatedTech/jmap-tui/master/install.sh | bash

It downloads the latest binary for your machine into ~/.local/bin and tells you if that folder isn't on your PATH yet.

Other ways to install

Prefer Go?

go install github.com/CaffeinatedTech/jmap-tui/cmd/jmap-tui@latest

Build from source

git clone https://github.com/CaffeinatedTech/jmap-tui
cd jmap-tui && go build ./cmd/jmap-tui

Manual download — grab the archive for your OS from the releases page, unpack it, and put jmap-tui somewhere on your PATH.

Uninstall

rm ~/.local/bin/jmap-tui            # wherever you installed it
rm -rf ~/.config/jmap-tui           # optional: config + preferences

Verifying your binary

The one-liner trusts this repository and GitHub: install.sh picks the release asset matching your machine and checks no signature or checksum — a checksum shipped next to the binary would prove download integrity, not origin, so none is offered. If "HTTPS from GitHub" isn't enough assurance:

  • Build from source — the git clone + go build steps above; the Go toolchain verifies every dependency against go.sum.
  • Download directly — fetch the archive yourself from the releases page instead of piping a script to your shell.
  • Compare out of band — if you obtain a SHA-256 from a trusted channel (signed tag, separately verified announcement), compare it with sha256sum jmap-tui on the file you downloaded.

Status: pre-release and under active development. It already does everything described on this page, verified against Stalwart and Fastmail; v0.1.0 was the first release, and new features land between tags.

Getting started

1. Install it with the one-liner above, then run:

jmap-tui

2. The account wizard opens. Nothing to configure by hand — it walks you through four short screens:

Add an account                       1 of 4 · details
Email          you@example.com       the server is found from this address
Password       ••••••••••••••        → saved to your OS keyring, never to disk
Account name   Work                  (defaults to your username)

2 of 4 · finding server     DNS _jmap._tcp, then https://<domain>
2 of 4 · connection         tests the login and lists your mailboxes
3 of 4 · opening mailbox    picks the folder that opens first (Inbox is fine)
4 of 4 · save               writes the config — you're done

There is no Server URL to type: from your email address the wizard discovers the JMAP server (the _jmap._tcp DNS record first, then the domain itself — RFC 8620), verifies it with one anonymous request, and fills the field in. When it can't find one, the Server URL field appears for you to fill in; ctrl+u reveals it at any time.

Use an app password (Stalwart: any credential your admin gives you; the usual rule) — not your real login password. Fastmail is the exception: its JMAP API only accepts an API token (Settings → Privacy & Security → Manage API tokens, scopes: mail, submission, contacts). The wizard probes the server, works it out, and records it as auth = "bearer" in your config.

3. Mail. The wizard closes and your inbox appears. That's it.

Anything you can do in the wizard you can do again any time: run jmap-tui login, or press ctrl+a inside the app to add another account or edit this one.

Guides

Read your mail

  1. Move around the folder list with j/k, press Enter to open a folder. Focus jumps to the message list automatically.
  2. j/k walks the list; the preview pane follows along as you go.
  3. Tab moves focus into the preview to scroll it, or press v to read full-screen.
  4. Threads show a ▸ in front of the subject — Enter expands the replies, Enter again collapses them.
  5. PgUp/PgDn pages the message from anywhere — focus never moves.
  6. Links in a message collect as numbered footnotes ([1], [2]…) — or, in a plain-text message, are detected from the text as-is. Press ctrl+o to list them and open one in your default browser — http, https and mailto links are opened; anything else is refused.

Triage your inbox

  1. J / K jumps to the next / previous unread message.
  2. e archives, d deletes (to Trash), * stars, u marks read/unread. The list closes the gap, so you're straight onto the next message.
  3. Made a mistake? A toast appears for five seconds — ctrl+z undoes it.
  4. Doing several at once? x selects messages (they show a ×), then hit e or d to act on the whole selection in one go.
  5. m moves to another folder, y copies. Start typing to filter the folder picker.

Find anything

  1. Press / and type. Results stream in as you type — the search runs on the server, so it's fast even across 50,000 messages.
  2. Enter confirms the search and drops you into the results; Tab widens the scope from this folder to all folders.
  3. Need filters? ctrl+s opens a form: from, to, subject, dates, keywords, has-attachments.
  4. Esc backs out — close the search, exit full-screen, clear your selection — one key, in that order.

Write, reply, and send

  1. Press n for a new message, r to reply, a reply-all, f forward. Replies quote the original for you.
  2. Tab moves between To / Cc / Bcc / Subject / body. In an address field, contacts are suggested as you type — Enter accepts one. ctrl+g opens a full contact search.
  3. ctrl+a attaches a file (a directory browser: j/k move, Enter attaches), ctrl+i picks the From address — every identity on every connected account, so you can change which account sends mid-draft. New, reply and forward open on the account's own address by default, and a pick is remembered for that account next time.
  4. ctrl+s sends — and then you have five seconds to change your mind: ctrl+z cancels the send and puts you back in the composer with everything intact.
  5. Drafts save themselves to the server every couple of seconds, so you can quit and pick the draft up later (it's in your Drafts folder).

Add more accounts

  1. Press ctrl+a (the app restarts into the wizard) or run jmap-tui login — choose + add a new account and go through the same four steps.
  2. A switches accounts instantly — everything stays warm in the background, so there's no reload.
  3. i toggles the unified inbox: every account's mail, interleaved by date, each row tagged with a colour for its account. Actions always go to the account that owns the message. The choice is remembered — quit while it's on and the next start opens there.
  4. Every account's folder tree sits in one sidebar under its own tinted header. With a folder selected, ctrl+↑/ctrl+↓ moves that whole account block up or down — the order is remembered, and the account that ends up on top becomes the default: the one that opens first next time.
  5. One account offline? It shows a quiet error in the status bar and the others keep working.

Contacts

  1. c opens the contacts screen — address books, contact list, and details, merged across every account that supports contacts.
  2. n new, e edit, d delete (held five seconds behind an undo toast), / filters the list.
  3. Reading a message from someone new? Shift+N adds them as a contact, already filled in.
  4. The composer's address fields suggest from these same contacts automatically.

Make it yours

  • [ hides the sidebar for a distraction-free view; z flips between side-by-side and stacked panes.
  • s sorts (newest, oldest, sender, subject, size) — remembered per account. S shows message sizes.
  • --theme dark, --theme light, or the default, which follows your terminal.
  • Every key can be rebound in config (see Configuration).
  • ? shows the full key help, always contextual to where your cursor is.

Keys

Everything is keyboard-driven. press ? at any time for a full-screen cheat sheet — every command grouped by purpose in columns.

Sidebar (folders)

Key What it does
j / k · ↑ / ↓ Next / previous folder
g / G Top / bottom of the tree
Enter Open the folder (focus moves to the message list)
h / ← Fold a folder's subfolders — or the whole account
l / → Unfold (never opens — that's Enter)
ctrl+r Mark every unread message in the folder read
ctrl+↑ / ctrl+↓ Move the account's block up / down

Message list

Key What it does
j / k · ↑ / ↓ Next / previous message
g / G First / last message
Space or ctrl+f / ctrl+b Page down / page up
ctrl+d / ctrl+u Half page down / up
J / K Next / previous unread
Enter Expand / collapse the thread
u Toggle read / unread
* Toggle star
x Select for a batch action
e Archive
d (or #) Delete — to Trash
m / y Move / copy to another folder
s (or o) Sort by… (remembered per account)
S Show / hide message sizes
n / r / a / f Compose / reply / reply-all / forward

Reading pane

Key What it does
j / k Scroll a line
d / u Half page down / up
ctrl+f / ctrl+b Page down / page up
g / G Top / bottom of the message
PgUp / PgDn Page the message — works from any pane
s Save attachments…
ctrl+o Open a link from the message in your browser (numbered like the footnotes)
v Full-screen message (press Esc to leave)

Composer

Key What it does
Tab / Shift+Tab Next / previous field (To → Cc → Bcc → Subject → body → attachments)
Enter Next field (in the body: new line)
type Contact suggestions pop up under address fields — ↑/↓ choose, Enter inserts
ctrl+g Full contact search inside an address field
ctrl+i Choose the From address — one row per identity on every connected account (named for its account); picking another account's moves the draft, and its attachments, there. The choice is remembered for that account
ctrl+a Attach a file — j/k move, l open, h back, Enter attach, Esc cancel
ctrl+x Remove the highlighted attachment
ctrl+s Send — then ctrl+z within 5s to cancel
Esc Close (asks before discarding; n keeps the draft)

Contacts screen

Key What it does
j / k · g / G Move through the list
Tab / Shift+Tab Cycle columns (books · contacts · details)
/ Type-to-filter (Esc clears, Esc again closes)
n / e / d New / edit / delete (delete waits 5s — ctrl+z cancels)
Enter Show the selected contact
c Close

Search bar, pickers & dialogs

Where Key What it does
Search bar type Search runs as you type
Enter Confirm and jump into the results
Tab Scope: this folder ↔ all folders
Esc Close (position restored)
ctrl+s Advanced search form
Folder / mailbox picker type Type-to-filter
j / k · Enter · Esc Choose / confirm / cancel
Account switcher (A) j / k · Enter · Esc Pick an account
Help (?) ? / q / Esc Close

Anywhere (global)

Key What it does
/ · ctrl+s Search · advanced search
Esc Back — closes search, then full-screen, then clears selection
A Switch account
i Toggle the unified inbox
c · Shift+N Contacts · add the sender as a contact
Tab / Shift+Tab Cycle panes
[ · z Show/hide sidebar · switch pane layout
ctrl+a Add or edit an account
ctrl+z Undo the last action (while its toast shows)
PgUp / PgDn Page the reading pane
ctrl+o Open a link from the message under the cursor
? Key help
q Quit (ctrl+c twice cancels in-flight work first)

Paste works in every field you can type in — the composer, the search bar, the contact form, and the account wizard: ctrl+v reads your system clipboard, and your terminal's own paste (ctrl+shift+v in most) arrives as bracketed paste. Neither is a keymap action, so neither is rebindable.

Rebinding a key is one line in your config — the action name is what you see in ?:

[keys]
"list.down" = "ctrl+n"
"ui.quit"   = "ctrl+d"

Conflicts are rejected at startup with a clear error rather than silently shadowing each other.

Configuration

Two small files live in ~/.config/jmap-tui/ ($XDG_CONFIG_HOME honoured).

config.toml — your accounts and settings. The wizard writes the account bits for you; the parts you might edit yourself:

  • default_account — which account opens first (the app keeps it on the account at the top of your sidebar order)
  • [accounts.<id>] — server URL, username, display name, optional initial_mailbox (the folder that opens at startup), default_identity (the From address the composer defaults to — with none set the account's own address is used, and a From you pick in-app is remembered for that account), and auth ("bearer" for API-token servers like Fastmail; default is HTTP Basic)
  • [keys] — rebind any key to an action id (see Keys)
  • theme — dark, light, or auto (follow your terminal)
  • [compose] undo_delay — how long you get to cancel a send
  • [window] — list chunk/prefetch sizes; rarely worth touching
default_account = "work"

[accounts.work]
display_name = "Work"
url          = "https://mail.example.com"
username     = "you@work.example.com"

[accounts.personal]
display_name = "Personal"
url          = "https://mail.example.com"
username     = "you@example.com"
# auth = "bearer"   # API-token servers (Fastmail); default is HTTP Basic

[compose]
undo_delay = "5s"   # how long you get to cancel a send; "0s" sends instantly

Passwords never land in this file — the wizard stores them in your OS keyring or, on headless machines, in a password_file you pick. And if you'd rather not edit TOML at all, jmap-tui login edits an account through the wizard instead.

prefs.toml — your in-app choices, saved as you make them. Pane layout, whether the unified inbox was left on, the order of account blocks in the sidebar, which folders are folded shut, and per-account sort order and archive destination. You shouldn't need to edit it; delete it if you ever want those choices back to defaults.

Troubleshooting

Symptom Fix
"couldn't find a JMAP server" in the wizard Your address's domain published nothing to discover: publish a _jmap._tcp.<domain> SRV record pointing at your JMAP host, or just fill in the Server URL field the wizard reveals (ctrl+u shows it any time).
"connection failed" in the wizard Check the server URL (it should be the JMAP URL — https://api.fastmail.com for Fastmail) and that you're using an app password — or on Fastmail an API token (the wizard probes and saves auth = "bearer") — not your login password.
No OS keyring (SSH session, minimal container) The wizard offers a password_file fallback — a chmod-600 file holding the app password.
Folders look stale The status bar shows live, polling, or connecting…. polling means push dropped and it's falling back — it recovers on its own.
A search finds nothing Server search matches whole words; jmap-tui then falls back to a fuzzy scan with a scanning n/N indicator. Esc cancels it.
Need details for a bug report jmap-tui --log-file /tmp/jmap-tui.log --log-level debug writes a redacted log (secrets never appear in it).

Security

A hostile-server / hostile-mail / hostile-filesystem audit (2026-09-27 — 17 findings, 4 High / 9 Medium / 4 Low) probed the client end to end. Every vector below is mitigated in code with regression tests.

Credentials & transport

  • Authorization following cross-origin redirects (C-1)
  • Credentials sent to session-supplied cross-origin URLs — now scoped to the authenticated session's own https endpoints (regional API/CDN hosts); cleartext, IP-literal and unadvertised origins — and every cross-origin redirect — still get nothing (C-2 revised, S-2); hostile blob names in the authenticated download path stay validated (S-2)
  • Cleartext http:// to non-loopback hosts (C-3)
  • user:pass@ userinfo accepted by config/flags or written to the debug log (C-3b, C-4)
  • Redirect loops; untrusted TLS failing open (S-1, S-8)
  • Unbounded JSON responses and attachment downloads — capped at 32 MiB / 100 MiB (S-3, S-4)

Mail → terminal

  • Terminal escape/control injection via subject, preview, sender and mailbox names, body, error lines (T-1, T-3, T-5, T-6)
  • HTML entities decoding to controls after conversion (T-2)
  • UTF-8-splitting truncation in status/error lines (T-7)

Files on disk

  • Attachment path traversal and filename-driven save hangs (W-1, W-2)
  • Symlink clobber via crash reports and config/prefs writes — O_EXCL random temp names (W-4, W-5)
  • Pre-existing world-writable debug log (W-7)
  • password_file FIFOs, directories, and loose symlink targets (W-6)

Config & identity

  • Plaintext passwords smuggled into config (I-1)
  • CR/NUL carried through parsed address fields (I-6)
  • Env-var name collisions (C-7) — env vars are test-only (FR-J2)

Supply chain

  • install.sh curl | bash trust model — documented residual; verification options under Install
  • Dependency vulnerabilities — govulncheck ./... clean

Also probed and holding: SSE overlong-line recovery, HTML conversion termination, goroutine hygiene under churn, config fuzzing, and a race-free test suite.

Roadmap

  • v0.1 — released: v0.1.0, the Fastmail verification pass and release binaries (2026-09-28)
  • v0.2 — server auto-config (add an account from your email address alone), a remembered unified inbox, and the top account opening by default
  • v0.3+ — push subscriptions, Sieve script management, vacation responder, quota display, richer theming
  • Never — a second mail protocol. jmap-tui is JMAP only; the mail.Provider seam is an internal boundary, not a roadmap.

Development

jmap-tui is written in Go with Bubble Tea, Lipgloss, and go-jmap. The repo is built for AI-assisted development — read the rules before contributing:

Doc Purpose
REQUIREMENTS.md What we're building — scope & requirements
PLAN.md How we're building it — architecture & milestones
AGENTS.md Contribution rules (for agents and humans)

License

MIT

About

Terminal email client that speaks JMAP. live push, multi-account, no local mail storage. Works with Stalwart and Fastmail.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages