Reference
The durian CLI is the engine — it handles IMAP sync, SMTP send, SQLite
storage, and exposes the HTTP API the GUI talks to. This page covers each
subcommand with one or two practical examples. Run durian (no args) for
the full list, or check the installed man pages:
man durian-sync
man durian-search
durian <cmd> --helpsync — fetch and push mail
durian sync # all accounts, all mailboxes
durian sync personal # one account by alias
durian sync personal INBOX # one mailbox
durian sync --debug # verbose logging to stderrBidirectional by default — local tag changes are uploaded as IMAP flags / folder moves, and server-side flag changes are pulled down. The first sync of a large mailbox can take a few minutes; subsequent syncs are incremental.
The GUI runs durian serve, which keeps a long-lived IDLE connection open per account — explicit durian sync is mainly useful for cron jobs or troubleshooting.
search — query the local store
durian search "tag:inbox" -l 10
durian search "from:boss@company.com AND has:attachment:pdf"
durian search "group:vip AND date:1w.." --json
durian search count "tag:unread"Uses notmuch-style syntax — terms are ANDed by default; OR/NOT are explicit. --json emits machine-readable output for piping into other tools.
tag — modify tags
durian tag "tag:inbox AND from:newsletter" +newsletter -inbox
durian tag <thread-id> +todo
durian tag list # show all tags + countsTags must be prefixed with + (add) or - (remove). Both can be mixed in one call.
show — display a thread
durian show <thread-id>
durian show <thread-id> --htmlRenders the thread to stdout — useful for piping into less or grepping a specific thread.
attachment — list or download
durian attachment <message-id> # list parts
durian attachment <message-id> --part 2 --save ./out/ # download part 2Part IDs come from the list output. --save writes the original filename into the target directory.
send — send an email
durian send --to bob@x.com --subject Hi --body "Hello"
durian send --to bob@x.com --subject Draft # opens $EDITOR
durian send --to bob@x.com --subject "PR" --attachment patch.diffIf --body is omitted, your $EDITOR opens with a temp file.
draft — manage IMAP drafts
durian draft save --to alice@x.com --subject WIP --body "..."
durian draft save --replace --message-id "<original-id>" ...
durian draft delete "<message-id>"--replace overwrites an existing draft on IMAP by Message-ID — useful for autosave loops in scripts.
rules — apply filter rules
durian rules apply # apply rules.pkl to all messages
durian rules apply --dry-run # preview changes without writingRules normally run automatically on incoming mail during sync. apply is for backfilling — e.g. after editing rules.pkl you may want to re-tag your existing inbox.
validate — check config
durian validate # all files
durian validate config # just config.pkl
durian validate rules
durian validate profiles
durian validate keymaps
durian validate groupsReports the offending field with file path and line. Run before auth login or sync if you’ve edited Pkl files.
auth — manage credentials
durian auth login personal # interactive (password or OAuth)
durian auth status # all accounts + token state
durian auth refresh personal # force OAuth token refresh
durian auth logout personal # remove from keychainCredentials live in the macOS Keychain — see OAuth setup and Password setup.
contacts — local address book
durian contacts init # create the contacts DB (auto on first sync)
durian contacts import # extract addresses from email store
durian contacts list
durian contacts search alice
durian contacts add bob@x.com "Bob Roberts"
durian contacts delete bob@x.comUsed by the GUI compose autocomplete. import walks your existing mail and seeds the DB.
group — list contact groups
durian group list # all groups + member counts
durian group members vip # members of one groupGroups are defined in groups.pkl — edit the file to add or remove members. The CLI is read-only.
tag-sync — multi-machine tag replication
durian tag-sync push # push local tag changes to server
durian tag-sync pull # pull remote changes
durian tag-sync push-all # initial sync (all tags)Optional. Requires a self-hosted tag sync server configured in config.pkl:
sync {
tag_sync { url = "http://nas:8724"; api_key = "your-secret" }
}Run only on a trusted network — the protocol has no TLS or rate limiting.
serve — HTTP API for the GUI
durian serve # default port 9723
durian serve --port 8080
durian serve --debug # debug-level logging to serve.log
durian serve --no-auth # skip bearer-token auth (experimental clients)Used by the GUI as a child process — you don’t normally need to start this yourself. Logs go to ~/.local/state/durian/serve.log (truncated on each start).
Auth & bind
serve binds to 127.0.0.1 only and enforces a per-session bearer token. On startup it prints a single machine-readable line to stdout:
READY token=<hex> addr=127.0.0.1:9723The macOS GUI captures this line from the child process’s stdout pipe and includes the token as Authorization: Bearer <hex> on every request. Requests without a valid token get 401. Requests from a non-loopback Host header get 403.
--no-auth disables the bearer-token check (loopback host check is still enforced). Useful for experimental clients that don’t implement the stdout-READY handshake — e.g. the Linux Qt GUI — and for ad-hoc curl testing. The READY line is still printed (with empty token=) so parsers don’t break.
Threat model note: bearer-token auth raises the bar against curious local processes, but it is not a hardened sandbox. Any process running as your user can already read your config, dbus, browser session, etc. — and could just spawn its own
durian serve --no-authon another port. Treat the token as defence-in-depth, not isolation.
Global flags
| Flag | Effect |
|---|---|
--debug | Debug-level logging |
--json | Machine-readable JSON output (where supported) |
-c, --config <file> | Override config file (default ~/.config/durian/config.pkl) |
--help | Per-command help |