Install & upgrade
curl -fsSL https://raw.githubusercontent.com/lubabs770/parcel/main/install.sh | shThe installer picks the right binary for your OS and architecture (macOS on Apple silicon or Intel, Linux on x86_64 or aarch64), downloads it with the release's checksums.txt, and refuses to continue unless the sha256 matches. It then test-runs the binary and installs it to ~/.local/bin atomically: it writes a temporary file and renames it into place, so a running parcel is never half-overwritten.
BIN_DIR=/some/dir ... | shinstalls somewhere else.VERSION=v0.1.0 ... | shpins a release instead of the latest.- On any other platform, build it:
cargo install --git https://github.com/lubabs770/parcel.
Upgrading
parcel upgrade asks GitHub for the latest release and, if it's newer, re-runs the installer into the directory the running binary lives in, with the same checksum check. parcel upgrade --check only tells you whether a newer release exists.
parcel drives an email CLI, so you need one installed and configured first. The default backend is himalaya (2.x); any other CLI works with a backend file.
The TUI
Run parcel with no arguments. Every key below is rebindable in keys.toml by the name in the last column.
| Key | Action | keys.toml |
|---|---|---|
| k | move up | up |
| j | move down | down |
| g | top | top |
| G | bottom | bottom |
| enter | open | open |
| esc | back | back |
| tab | next pane | next_pane |
| / | search | search |
| r | reply | reply |
| R | reply all | reply_all |
| f | forward | forward |
| c | compose | compose |
| s | star | star |
| u | toggle read | toggle_read |
| d | delete | delete |
| m | move to… | move |
| a | archive | archive |
| ctrl+p | command palette | palette |
| t | themes | themes |
| ? | help | help |
| q | quit | quit |
The arrow keys, Home and End always move, and ctrl+c always quits, whatever you rebind.
Panes and narrow mode
At 100 columns or wider you get three panes side by side: mailboxes, the message list, and the reader. Narrower than that, parcel shows only the focused pane. tab moves focus from mailboxes to the list to the reader (once a message is open) and back round. esc steps back: reader to list, list to mailboxes. If a search is active, esc in the list clears it first.
In the list, ● marks unread mail and ⚑ starred mail. Each sender gets a coloured initial chip, and dates are relative: now, 5m, 2h, a weekday within the last six days, then Oct 3, then the year. The status bar shows a spinner while the backend works, the latest message on the left, and the backend and theme name on the right.
Mouse
Scroll a pane, click to select, double-click to open.
Search, pickers and the palette
- / opens a search prompt in the status bar. enter searches the current mailbox; esc cancels. With himalaya, a query matches the subject or the sender.
- m opens a picker of the other mailboxes to move the message to. a moves it straight to your
archivemailbox. - ctrl+p opens the command palette: every action by name, plus
go to <mailbox>. - t opens the theme picker. The screen repaints as you move through it; enter keeps the theme for this session, esc puts the old one back. To make it stick, set
themeinconfig.toml.
Pickers filter as you type. ↑/↓, tab/shift+tab, ctrl+p/ctrl+n and ctrl+k/ctrl+j move the selection.
Triage
Star, toggle read, archive, move and delete change the screen immediately; the backend call runs afterwards. If it fails, the change is undone and the error shows in the status bar.
Compose, reply, forward
c, r, R and f open a draft in $EDITOR. Reply and forward drafts come from the backend; a new message starts from the backend's compose template, or from a blank To: / Cc: / Subject: header if there isn't one. When the editor closes, the status bar asks:
s send · e edit · d discard
esc leaves the prompt and keeps the draft on disk, and the status bar shows where it is.
An action whose backend template is missing is refused, and the help sheet shows it dimmed. The himalaya preset has no reply_all, because himalaya 2.x has no reply-all, so R is off there.
Config directory
~/.config/parcel/
├── config.toml # backend, theme, page_size, inbox, archive
├── keys.toml # key overrides
├── backends/
│ └── himalaya.toml # command templates for the himalaya backend
└── themes/
└── <name>.toml # your own themes
parcel config init writes the default config.toml, keys.toml and backends/himalaya.toml for editing. It never overwrites a file that already exists. backends/himalaya.toml is written fully commented out: uncomment only the lines you want to override, and the rest keeps following the built-in preset. parcel config path prints the directory. It honours $XDG_CONFIG_HOME (on macOS too), and $PARCEL_CONFIG_DIR overrides it.
Nothing here is required. The himalaya preset is built in, so you need no file until you want to change something.
config.toml
backend = "himalaya" # backends/<name>.toml, or a built-in preset
theme = "auto" # "auto" follows Omarchy, else tokyo-night; or any theme name
page_size = 50
inbox = "inbox" # mailbox opened at start
archive = "Archive" # where the archive key moves mailEvery key is optional, and these are the defaults. page_size fills the {size} placeholder.
keys.toml
One name = "key" line per action you want to rebind, using the names in the key table. An override replaces that action's default key and wins any clash. An unknown action name is an error.
star = "x"
palette = "ctrl+k"Keys are written "x", "X", "ctrl+x", "alt+x", or one of enter esc tab backtab space up down left right home end pgup pgdown backspace delete.
backends/<name>.toml and themes/<name>.toml
Backends
A backend is any email CLI, described by one command template per action and a [fields] table that maps its JSON onto parcel's envelope. Here is the built-in himalaya preset, verbatim:
# parcel backend: himalaya 2.x. Each action is a command template; {x} fills
# one argument, {x...} splits on spaces. Leave an action out to disable its key.
mailboxes = "himalaya --json mailbox list"
list = "himalaya --json envelope list -m {mailbox} -p {page} -s {size}"
search = 'himalaya --json envelope search -m {mailbox} -p {page} -s {size} subject "{query}" or from "{query}"'
read = "himalaya message read -m {mailbox} {id}"
flag = "himalaya flag add -m {mailbox} -f {flag} {id}"
unflag = "himalaya flag remove -m {mailbox} -f {flag} {id}"
move = "himalaya message move -f {mailbox} -t {target} {id}"
delete = "himalaya message delete -m {mailbox} {id}"
send = "himalaya message send"
compose = "himalaya message compose --to= --subject="
reply = "himalaya message reply -m {mailbox} {id}"
forward = "himalaya message forward -m {mailbox} {id}"
seen_flag = "seen"
flagged_flag = "flagged"
[fields]
items = "envelopes"
id = "id"
subject = "subject"
from = "from.0.name|from.0.email"
date = "date"
flags = "flags"
flag_name = "iana|raw"
mailbox_items = "mailboxes"
mailbox_name = "name"A backends/himalaya.toml of your own is layered over the preset: keys you set win, keys you leave out keep the preset's value.
Actions
mailboxes list search read flag unflag move delete send compose reply reply_all forward. Leave one out to disable its key. send gets the message on stdin; compose, reply, reply_all and forward print the draft to open in your editor. seen_flag and flagged_flag are the flag names passed as {flag}.
Placeholders
{mailbox} {id} {page} {size} {query} {flag} {target}. No shell is ever involved: the template is split on whitespace first, then each placeholder is filled inside its own argument, so a value with spaces or shell characters stays one argument. Write {name...} to split the value on whitespace into several arguments instead. A word that is just a placeholder with an empty value is dropped.
[fields]
Each field is a dot path into the CLI's JSON output. Numeric segments index arrays (from.0.name), and a|b tries each alternative in turn, taking the first that is neither null nor empty. The empty path "" means the value itself, for a CLI that prints a bare array.
Adding another CLI
Set backend in config.toml and write backends/<name>.toml:
# ~/.config/parcel/config.toml
backend = "mycli"
# ~/.config/parcel/backends/mycli.toml
mailboxes = "mycli folders --json"
list = "mycli ls --folder {mailbox} --page {page} --json"
read = "mycli show {id}"
[fields]
items = "" # output is a bare array
id = "uid"
subject = "subject"
from = "sender"
date = "date"
flags = "flags"
mailbox_items = ""
mailbox_name = "name"Then run parcel doctor to check every command is on your $PATH and mailboxes load.
Themes
Eight are built in: tokyo-night catppuccin gruvbox rose-pine nord everforest kanagawa latte. You can try them on the home page. The default, auto, follows Omarchy and falls back to tokyo-night.
For your own, drop a themes/<name>.toml in the config directory with all 12 tokens as hex, then set theme = "<name>" or pick it with t. This is tokyo-night written out:
bg = "#1a1b26" # the screen behind the panes
surface = "#16161e" # panes and the status bar
overlay = "#292e42" # the list highlight when it isn't focused
fg = "#c0caf5"
muted = "#565f89"
accent = "#7aa2f7"
unread = "#7dcfff"
flagged = "#e0af68"
danger = "#f7768e"
ok = "#9ece6a"
selection = "#283457"
border = "#3b4261"Colours are "#rrggbb" or "0xrrggbb".
Omarchy
With theme = "auto" (the default), parcel follows the active Omarchy theme. It reads the theme's colors.toml, or alacritty.toml for older themes, from ~/.local/state/omarchy/current/theme or ~/.config/omarchy/current/theme, and derives any colour layer the palette doesn't define from its background.
parcel omarchy hook installThis adds one line to Omarchy's ~/.config/omarchy/hooks/theme-set hook, which signals running parcels to repaint the instant the theme changes. Without the hook, parcel still notices within about a second by polling; the hook just makes it immediate. parcel omarchy hook uninstall removes the line and leaves the rest of the hook alone. Both are safe to run twice.
CLI reference
Reading and triage are also commands; compose, reply and forward live in the TUI, or write your own draft and parcel send it. --json works with every command and makes the data-producing ones print machine-readable JSON.
| Command | What it does |
|---|---|
parcel | Open the TUI |
parcel mailboxes | List mailboxes |
parcel list [-m MAILBOX] [-p PAGE] | List envelopes in a mailbox (default: your inbox, page 1) |
parcel search QUERY [-m MAILBOX] [-p PAGE] | Search a mailbox |
parcel read ID [-m MAILBOX] | Print a message |
parcel flag ID [--flag seen|flagged] [-m MAILBOX] | Set a flag on a message (default: flagged) |
parcel unflag ID [--flag seen|flagged] [-m MAILBOX] | Clear a flag on a message (default: flagged) |
parcel move ID TARGET [-m MAILBOX] | Move a message to another mailbox |
parcel delete ID [-m MAILBOX] | Delete a message |
parcel send [FILE] | Send a raw RFC 5322 message from a file, or from stdin |
parcel doctor | Check the backend, its templates and the theme setup |
parcel log [-n LINES] | Show the end of the error log (default: 40 lines) |
parcel config init | Write the default config files (never overwrites) |
parcel config path | Print the config directory |
parcel omarchy hook install|uninstall | Repaint running parcels instantly on an Omarchy theme change |
parcel upgrade [--check] | Upgrade to the latest release (or just check) |
The long forms are --mailbox, --page and --lines. parcel --version prints the version.
parcel list --json | jq -r '.[] | select(.seen | not) | .subject'Each envelope in --json output has id, subject, from, date, seen and flagged.
Troubleshooting
Start with the doctor
parcel doctorIt checks that the config loads, that every configured backend command is on your $PATH, that mailboxes actually load, and whether Omarchy and its hook are set up. Each line is ✓ (fine), ✗ (a problem) or · (not configured, which is fine). With himalaya, reply_all shows ·.
Read the log
Every failed backend call is recorded with its command and stderr, even when the TUI only shows a short status-bar message. parcel log prints the last 40 lines; parcel log -n 200 prints more. The log lives in ~/.local/state/parcel/parcel.log ($XDG_STATE_HOME, or $PARCEL_STATE_DIR, if set).
himalaya can't reach the macOS keychain
This happens when parcel is launched from a session without keychain access, such as a detached tmux or a headless or SSH session. The backend error shows up in the status bar; run parcel log for the full command and stderr.
A key does nothing
Its action probably has no template in your backend. The status bar says so, and ? shows it dimmed.