预编译二进制只含默认 cargo features,需要特定后端时得改用 cargo 自行编译;默认 vendored feature 会从源码编译 SQLite,想链接系统 SQLite 需加 --no-default-features;ManageSieve 需在账户配置里单独写可选的 sieve 块,且明文连接默认拒绝密码认证
curl -sSL https://raw.githubusercontent.com/pimalaya/himalaya/master/install.sh | sudo shREADME 未说明
用一条命令管理邮箱的 Rust 命令行工具,支持 IMAP、JMAP、Gmail 等多种后端
用 Rust 写的命令行邮件客户端,支持 IMAP、SMTP 和 sendmail,可在终端里收发、搜索和管理邮件,并支持 GPG/PGP 加密。
核心能力
- 统一 API 操作邮箱、信封、标记、邮件和附件
- 支持 IMAP、SMTP、JMAP、Gmail、Microsoft Graph 等协议
- 内置跨后端搜索 DSL,支持 and、or、not 和 order by
- 提供 REPL 复用会话,避免每次重新握手
- 可启动 MCP server,默认只读暴露共享 API
容易踩的坑
- v2.2.1最新正式版2026-10-02 · 2026-10-02 11:26 UTC
Added
-
Added
message.send.save-copy, the mailbox a sent message is copied to when--saveis not passed, and--no-saveonmessage send,compose,replyandforwardto skip it.It takes a mailbox name, alias or role, and the v1
truestill reads as thesentmailbox. Leave it unset on Gmail and Microsoft Graph, which file the sent message themselves. -
Added
envelope.list.datetime-relative, rendering recent dates relative to today: the time for today,yesterday, then the weekday for the past week, older dates falling back todatetime-fmt([#510]). -
Added
proxy, a per-account SOCKS5 or HTTP proxy every network backend connects through, and<backend>.proxyto override it for one backend ([#742]).The password is a secret like any credential, so it stays out of the URL. Without either, the
all_proxyandhttps_proxyenvironment variables are still read. -
Added
--posting-style nonetomessage replyandmessage forward, sending the written body without quoting the source. -
Added a decoded template to
message compose,message replyandmessage forwardunder--jsonwhen neither saving nor sending.It carries
from,to,cc,bcc,subjectand abodywithout the signature, so an editor can lay the message out and hand it back through the flags. -
Added
messages-added-detailstogmail history list --json, preserving each arrival's message id, optional thread id and supplied label ids alongside the existing message-id arrays. -
Added
message addon Gmail, inserting the raw message throughmessages.insertwith the mailbox and flags as labels, dated by itsDate:header. -
Added
envelope searchon Microsoft Graph, its filter translated to a KQL$search. Flag clauses are refused, Graph accepting no$filterbeside$search, and the sort applies within the page. -
A pimdir write now shows on the next read instead of on the next sync.
A pimdir store is a replica the sync engine owns, so Himalaya appends its writes to the store's queue rather than apply them. A read used to project the committed index alone, so flagging a message lost the flag from the listing until Neverest ran.
Reads now overlay the pending queue, so a staged flag, edit, deletion, move or copy shows straight away. All of them address a message that already exists and keeps its public id, so nothing changed about how a message is addressed.
A staged creation has no id until the sync engine applies it, so it can be no envelope and does not list.
envelope listreports how many the mailbox has and where to see them, andenvelope searchreports none, a queued message never having been matched against the query. -
himalaya pimdir queue listandhimalaya pimdir queue cancel, for the staged creations the mailbox listing cannot show.The
pimdirbinary is kind-agnostic and prints ids, hashes and flags. Himalaya holds the mail conventions and the blobs, so it renders a queued message with its flags, subject, recipient and the moment it was queued.cancelretracts one by the row id that view prints, which is the only way back for a message that does not exist yet: a staged flag or move is undone by doing the opposite.Cancelling is the store owner's write, so it takes that role for the length of the call and reports a sync in flight rather than wait on it.
-
Added sending from a pimdir account: the message is queued in the store for its owner to send, the one holding the credentials.
The row is filed under the
--savemailbox, else undermailbox.alias.sent, and shows inpimdir queue list. The command prints its row id,queueIdunder--json, whichpimdir queue canceltakes and the owner reports against. -
Added ManageSieve support behind the
sievecargo feature, on by default ([#739]).An account gains an optional
[sieve]block, andhimalaya sieveexposescapability,list,get,put, `check
-
- v2.2.02026-10-02 · 2026-10-02 10:42 UTC
查看更新说明
Tagged and released on GitHub, but never published to crates.io: the upload failed on
Cargo.tomldeclaring six keywords, one over the limit. Its changes ship in 2.2.1. - v2.1.02026-08-16 · 2026-08-16 20:04 UTC
查看更新说明
Added
-
Added the
pimdirstorage backend, Himalaya over the local store the sync engine populates.Reads build envelopes from the stored meta and report a body that is not local as "body not fetched"; writes are staged as replica mutations attributed to
pimdir.source, auto-detected when the store has a single source. -
Added back the
emailanddisplay-nameconfig fields, at the global and the account level ([#721]).They fill the
Fromheader ofmessages compose,replyandforwardwhen--fromis not passed, and are also read asfromandfrom-name, the spellings himalaya-tui writes. -
Added back the
signatureandsignature-delimconfig fields, at the global and the account level.The composers append the signature when neither
--signaturenor--signature-fileis passed,signature-delimdefaulting to the RFC 3676 §4.3"-- \n"and written verbatim. -
Added
in_reply_toto the shared envelope ([#734]).A list of ids stripped of their angle brackets like
message_id, riding the JSON output only, left empty by Microsoft Graph. -
Added the shared
message delete <ids>command (aliasdel).Trash-first: messages are moved to the trash mailbox, resolved from the backend role then from
mailbox.alias.trash, or permanently removed when already in it. -
Added the
maildir.keywords.dovecotandmaildir.keywords.headerconfig fields.Surface custom Maildir keywords as flags, read from each mailbox's dovecot-keywords file or from
X-Keywords/X-Label; reading only, both off by default. -
Added the
imap.sasl-irconfig field, forcing the RFC 4959 initial response on or off ([#729]).Unset follows the advertised
SASL-IRcapability;falseis needed by Coremail (126.com, 163.com), which advertises it then rejects the inline form. -
Added a
--seenflag tomessage read, marking the message as seen while reading it. -
Added the
configurecommand (aliaswizard), running the account wizard by name.The first-run offer is now a hook rather than a gate: the command carries on afterwards, and nothing prompts when stdin is not a terminal or
--jsonis set. -
Added back the
HIMALAYA_CONFIGenvironment variable, read like-cand taking the same:-delimited list. -
Added the shared Pimalaya
--helpfooter, naming the bug tracker and the sponsoring page.
Changed
-
Made the configuration wizard discovery-only, removing the hand-entry flow.
An email, a domain, a
scheme://URL or a local folder path all run discovery, and the wizard stops when nothing is found rather than guessing an SMTP host. -
Reworked
message read's plain-text output into a concise reading view.A header block (Date, From, To, Cc, Subject), then one summary line per MIME part with plain-text parts inlined;
--rawand--jsonare unchanged. -
Aligned the
IDofmessage read,attachments listandattachments downloadon the MIME part's 1-based position, making attachment ids a sparse subset of it. -
Right-aligned the
SIZEcolumn of the envelope listing table ([#723]). -
Made the wizard write the account to the configuration file rather than only print it.
The file is where
-corHIMALAYA_CONFIGpointed, or the XDG default; an existing one gets a plain text append, so comments and formatting survive. -
Made the first-run offer open with a welcome banner on stderr, naming the configuration file it looked for;
himalaya configureskips it. -
Changed bare
himalaya --account <NAME>to show the help instead of running the account wizard. -
Made the wizard offer only the IMAP authentication mechanisms an unauthenticated CAPABILITY advertises, falling back to the full list on any probe failure.
-
Time-bounded the wizard's service discovery, so one unreachable endpoint no longer stalls it for the operating-system connect timeout.
-
Made the wizard offer to reuse the IMAP credentials for SM
-
- v2.0.02026-07-26 · 2026-07-26 00:17 UTC
查看更新说明
Added
-
Added Gmail REST API support: a
[gmail]account backend behind the shared commands and a protocol-specificgmailcommand.Exposes the full REST surface (profile, labels, messages, attachments, drafts, threads, history, settings), authenticating with a single OAuth 2.0 bearer token, the only authorization the API accepts.
-
Added Microsoft Graph REST API support: a
[msgraph]account backend behind the shared commands and a protocol-specificmsgraphcommand.Exposes the Graph mail surface (profile, mail-folder, message, attachment) and mirrors
[gmail], authenticating with a single OAuth 2.0 bearer token. -
Restored the RFC 2971
ID-after-auth quirk asimap.id.{auto, fields}, replacing the v1.2.0imap.extensions.id.send-after-authflag. -
Brought the
m2dirbackend to CLI feature parity withmaildir, minus mailboxrenameand messagecopy/move, which await io-m2dir support. -
Added
--save <MAILBOX>tomessages send, mirroring the flag onmessages compose/reply/forward. -
Added
--sendtomessages add(aliasmessages save), mirroringmessages send --save. -
Added the raw passthrough commands
imap raw <command>andsmtp raw <command>. -
Added
mailbox.alias.*pre-filling to the wizard, so a generated account has a workinginboxand known Sent/Drafts/Trash targets.JMAP reads the RFC 8621 roles live, Gmail and Microsoft Graph map their fixed system ids, and IMAP pins the reserved
INBOXalone until io-imap can issue LISTRETURN (SPECIAL-USE). -
Added the
json-schema <DIR>command, writing one JSON Schema per structured-output command ([#547]).Describes each
--jsonpayload for downstream tooling; only compiled-in backends are emitted. -
Added a system DNS resolver fallback to discovery, making the chain
HIMALAYA_DNS_RESOLVERthen system then1.1.1.1.This keeps the email domain off a third-party resolver and works around networks blocking the default.
-
Added local socket-proxy support:
imap.serverandsmtp.serveraccept aunix:///pathscheme ([#264]).The session arrives already authenticated from a proxy such as sirup, so no SASL is negotiated over the socket.
Changed
-
Flattened the
imapcommand tree into top-level verbs mirroring the protocol's flat command list (select,create,append,store,fetch), replacing themailbox/envelope/message/flagsubgroups. -
Unified raw-message input behind a single
MessageArg(file path, inline raw or piped stdin) across every send and add command, removing the legacy--fileflag onmessages add. -
Split
Accountout of every client wrapper: subcommands now receiveaccountandclientas sibling arguments. -
Breaking: changed bare
himalayato run the account wizard instead of listing the default account's envelopes. -
Changed the wizard to print the generated account as a ready-to-save TOML document on stdout instead of writing it to disk.
The connection is tested before printing, and a config that exists but lacks the requested account is now a hard error rather than a wizard trigger.
-
Reworked the wizard's account-setup flow into one entry per reachable service, then a service-specific authentication prompt.
OAuth 2.0 folds into the credential prompt, which offers the OS keyrings and the token brokers (Ortie, pizauth, oama) together.
-
Changed the wizard to test IMAP first, then ask whether SMTP reuses the same credentials, then test SMTP.
-
Removed the wizard's account-name prompt, the name being derived from the domain's first label or the folder name.
Fixed
-
Fixed compilation error when the
wizardfeature was disabled ([#634]). -
Fixed mailbox-alias resolution to apply across every shared
messagessubcommand and the--saveflag oncompose/reply/forward. -
Fixed swapped save and send success messag
-
- v1.2.02026-02-19 · 2026-02-19 10:14 UTC
查看更新说明
Added
- Added
--quietflag (#545). - Added support for custom TLS certificate.
- Added support for multiple config files via
HIMALAYA_CONFIG=path1:path2:…(#612). - Added
-d|--downloads-dirargument toattachment downloadcommand (#559). - Added
ViewandView MIMEpost edit choices (#486). - Made
message sendandtemplate sendreturn id (#627). - Enabled shell expansion for email, IMAP and SMTP login fields, which allows usage of environment variables (#632).
Fixed
- Bumped all deps, fixing
rustls-platform-verifiercompatibility version issue. - Fixed invalid fetch with NIL body encoding (#525).
- Fixed freeze when sending large attachments (#593).
- Fixed abusive warn logs from IMAP libs (#552).
- Downgraded
[email protected]due to regression (#611).
- Added
用户点评
登录用户的真实使用记录,可按版本和硬件筛选还没有人点评。第一个说出真实体验的人最有价值。

Table of contents
- Features
- Installation
- Configuration
- Usage
- Interfaces
- FAQ
- AI policy
- License
- Social
- Contributing
- Sponsoring
Features
- Shared API for
mailboxes,envelopes,flags,messagesandattachments - Protocol-specific APIs exposing each backend's full surface
- IMAP, SMTP, ManageSieve, JMAP, Gmail (REST API), Microsoft Graph (Outlook / Microsoft 365) support
- Maildir specs, m2dir specs, mbox specs, pimdir specs support
- Simple auth support for IMAP, SMTP and ManageSieve (anonymous, login, plain, oauthbearer, xoauth2, scram-sha-256)
- HTTP auth support for JMAP: basic, bearer
- TLS support:
- Rustls with ring crypto
- Rustls with aws crypto (requires
rustls-awsfeature) - Native TLS (requires
native-tlsfeature)
- Discovery support:
- SOCKS5, HTTP proxy support, per account or via
$ALL_PROXYand$HTTPS_PROXY - TOML configuration with multi-account support
- JSON output via
--json - REPL via
himalaya repl, running commands over connections kept for the session - MCP server via
himalaya mcp, the shared API as tools, read-only by default
[!TIP] Himalaya is written in Rust and uses cargo features to gate backend support. The default feature set is declared in Cargo.toml.
The default
wizardfeature brings the interactiveconfigure; leave it out of a scripted or embedded build that never prompts.The default
vendoredfeature builds SQLite from source; leave it out (--no-default-features --features …) to link the system one.
Installation
Pre-built binary
Himalaya can be installed with the installer:
As root:
curl -sSL https://raw.githubusercontent.com/pimalaya/himalaya/master/install.sh | sudo sh
As a regular user:
curl -sSL https://raw.githubusercontent.com/pimalaya/himalaya/master/install.sh | PREFIX=~/.local sh
These commands install the latest binary from the GitHub releases section.
For a more up-to-date version than the latest release, check out the releases GitHub workflow and look for the Artifacts section. These pre-built binaries are built from the master branch.
[!NOTE] Such binaries are built with the default cargo features. If you need specific features, please use another installation method.
Cargo
cargo install --locked --git https://github.com/pimalaya/himalaya.git
With only IMAP+SMTP support:
cargo install --locked --git https://github.com/pimalaya/himalaya.git \
--no-default-features \
--features imap,smtp,rustls-ring
Arch Linux
From the community repository:
pacman -S himalaya
Or the user repository:
git clone https://aur.archlinux.org/himalaya-git.git
cd himalaya-git
makepkg -isc
Or with yay:
yay -S himalaya-git
Homebrew
brew install himalaya
[!NOTE] Cargo features are not compatible with brew. If you need a different feature set, please use another installation method.
Scoop
scoop install himalaya
Fedora Linux/CentOS/RHEL
From the COPR repo:
dnf copr enable atim/himalaya
dnf install himalaya
Nix
If you have the Flakes feature enabled:
nix profile install github:pimalaya/himalaya
Or run without installing:
nix run github:pimalaya/himalaya
Sources
Requires Rust 1.89 or later:
git clone https://github.com/pimalaya/himalaya
cd himalaya
cargo build --release --locked
The binary lands at target/release/himalaya.
Configuration
Run himalaya. With no configuration file on disk, the wizard asks for an email address and discovers the services reachable from it, probing PACC, Thunderbird Autoconfiguration, RFC 6186 SRV and RFC 8620 JMAP resolution in parallel.
Each domain, mail and sending, gets the best option found without a question (a provider's own API first, then JMAP, then IMAP and SMTP), shown in a review where any pick can be changed, a custom server URL included. Then each sign-in runs once: a password is typed once, tested at once, and stored in an installed keyring (or kept raw, or read by your own command); OAuth 2.0 hands off to Ortie when it is installed. When discovery finds nothing, the wizard asks for the server URL (imaps://, imap:// or https://, then smtps:// or smtp:// for sending). The account is then written to disk.
A persistent configuration is loaded from every existing path among the following, each one deep-merged on top of the ones before, so $HOME/.himalayarc overrides the others (a path naming a file already loaded, such as a symlinked rc file, is skipped):
$XDG_CONFIG_HOME/himalaya/config.toml$HOME/.config/himalaya/config.toml$HOME/.himalayarc
At most one account may be marked default = true across them: an rc file moving the default sets default = false on the account the other file marks.
These are the same paths the himalaya-tui TUI looks at: one TOML file backs both binaries. CLI-only fields and TUI-only sections coexist without errors. See config.sample.toml for a documented template.
Override those paths with -c <PATH>; multiple paths can be passed at once, separated by ; on Windows and : elsewhere, as $PATH separates them, or by repeating -c. The first one is the base and the rest are deep-merged on top.
To add an account, run himalaya configure: the account is appended to your config file, or printed when stdout is redirected. There is no in-place edit subcommand; himalaya account list and himalaya account check inspect and validate the accounts already declared.
Proton Mail
Proton does not expose IMAP/SMTP directly: run Proton Bridge, which synchronizes mail locally and serves it on a local IMAP/SMTP endpoint. The password is the one generated by the Bridge, not your Proton account password.
[accounts.proton]
imap.server = "imap://127.0.0.1:1143"
imap.sasl.plain.username = "[email protected]"
imap.sasl.plain.password.command = "pass show proton-bridge"
smtp.server = "smtp://127.0.0.1:1025"
smtp.sasl.plain.username = "[email protected]"
smtp.sasl.plain.password.command = "pass show proton-bridge"
To keep TLS on the local link, export the certificate generated by the Bridge and enable STARTTLS:
imap.starttls = true
imap.tls.cert = "/path/to/exported/cert.pem"
smtp.starttls = true
smtp.tls.cert = "/path/to/exported/cert.pem"
Fastmail
Fastmail needs an app password for IMAP/SMTP, or an API token for its native JMAP endpoint.
[accounts.fastmail]
imap.server = "imaps://imap.fastmail.com"
imap.sasl.plain.username = "[email protected]"
imap.sasl.plain.password.command = "pass show fastmail"
smtp.server = "smtps://smtp.fastmail.com"
smtp.sasl.plain.username = "[email protected]"
smtp.sasl.plain.password.command = "pass show fastmail"
To use JMAP instead, replace the imap/smtp blocks with a single jmap one:
jmap.server = "https://api.fastmail.com/jmap/session"
jmap.auth.bearer.token.command = "pass show fastmail"
Gmail
Gmail rejects the account password over SASL PLAIN: generate an app password (requires 2-step verification) and feed it through password.command or password.raw.
[accounts.gmail]
imap.server = "imaps://imap.gmail.com:993"
imap.sasl.plain.username = "[email protected]"
imap.sasl.plain.password.command = "pass show gmail"
smtp.server = "smtps://smtp.gmail.com:465"
smtp.sasl.plain.username = "[email protected]"
smtp.sasl.plain.password.command = "pass show gmail"
mailbox.alias.inbox = "INBOX"
mailbox.alias.sent = "[Gmail]/Sent Mail"
mailbox.alias.drafts = "[Gmail]/Drafts"
mailbox.alias.trash = "[Gmail]/Trash"
mailbox.alias.archive = "[Gmail]/All Mail"
Every Gmail label shows up as a top-level IMAP mailbox, and the special ones live under the [Gmail]/ prefix, so quote them in the shell or reach them through an alias.
[Gmail]/All Mail is the archive holding every message, so aliasing it puts "search everything" one flag away: himalaya envelope search -m archive ....
To use Gmail's native REST API instead of IMAP/SMTP, replace the blocks above with a single OAuth 2.0 bearer token from a helper such as ortie:
gmail.auth.token.command = ["ortie", "token", "show", "-a", "gmail"]
Labels become mailboxes here too, but they carry the API's opaque label ids; address them by name (-m Himalaya-Test) and himalaya resolves the id.
Outlook
Microsoft has retired basic authentication: use OAuth 2.0 via oauthbearer or xoauth2, with the access token supplied by an external helper such as ortie.
[accounts.outlook]
imap.server = "imaps://outlook.office365.com:993"
imap.sasl.xoauth2.username = "[email protected]"
imap.sasl.xoauth2.token.command = ["ortie", "token", "show", "-a", "outlook"]
smtp.server = "smtp://smtp-mail.outlook.com:587"
smtp.starttls = true
smtp.sasl.xoauth2.username = "[email protected]"
smtp.sasl.xoauth2.token.command = ["ortie", "token", "show", "-a", "outlook"]
To use the native Microsoft Graph API instead of IMAP/SMTP, replace the blocks above with a single OAuth 2.0 bearer token (sending goes through Graph too, so no SMTP is needed):
msgraph.auth.token.command = ["ortie", "token", "show", "-a", "msgraph"]
Mail folders carry Graph's opaque folder ids; address them by name (-m Archive) or by a well-known name (-m inbox) and himalaya resolves the id.
Posteo
Standard IMAP and SMTP with your regular account password: no app password required.
[accounts.posteo]
imap.server = "imaps://posteo.de"
imap.sasl.plain.username = "[email protected]"
imap.sasl.plain.password.command = "pass show posteo"
smtp.server = "smtps://posteo.de"
smtp.sasl.plain.username = "[email protected]"
smtp.sasl.plain.password.command = "pass show posteo"
iCloud Mail
Per the iCloud Mail support page, the IMAP login is the name of your address, johnappleseed rather than [email protected], while the SMTP login is the full address.
A dedicated app-specific password is required.
[accounts.icloud]
imap.server = "imaps://imap.mail.me.com:993"
imap.sasl.plain.username = "johnappleseed"
imap.sasl.plain.password.command = "pass show icloud"
smtp.server = "smtp://smtp.mail.me.com:587"
smtp.starttls = true
smtp.sasl.plain.username = "[email protected]"
smtp.sasl.plain.password.command = "pass show icloud"
mailbox.alias.sent = "Sent Messages"
Usage
Every command carries its own --help, the source of truth for its flags and syntax. The snippets below are a taste of the surface.
Shared API
Backend-agnostic commands run on the account's first configured backend, or the one picked with -b/--backend. -m/--mailbox takes a mailbox id, name, alias or role (inbox, sent, drafts, trash...), and defaults to the inbox.
himalaya mailbox list
himalaya envelope list --page 2
himalaya envelope search from alice and after 2026-01-01 order by date desc
himalaya flag add --flag seen 1 2 3 5
himalaya message read 42
himalaya message copy --from INBOX --to Archives 42
himalaya attachment download 42
envelope search uses himalaya's own cross-backend query DSL: date, after, before, from, to, cc, bcc, address (any of the four), subject, body, flag and attachment clauses, joined with and, or and not, grouped with parentheses, then order by. -h lists the clauses and himalaya envelope search --help gives the grammar and how each backend matches text. A clause a backend cannot search is refused with the code search-unsupported rather than dropped (every flag on Microsoft Graph, flag answered and keywords on Gmail, attachment on IMAP).
Protocol-specific APIs
Each backend also exposes its full native API under its own subgroup, always against that backend (-b/--backend is ignored here):
himalaya imap raw 'a1 SEARCH FROM "[email protected]"\r\n'
himalaya jmap mailbox query --role drafts
himalaya gmail messages list -q "from:alice is:unread"
himalaya msgraph mail-folders list
himalaya smtp send -f [email protected] -t [email protected] < message.eml
himalaya sieve list
himalaya sieve check --script-file filters.sieve
himalaya sieve put main --script-file filters.sieve
himalaya sieve activate main
himalaya pimdir search from:[email protected] has:attachment date:2026-01..2026-03
ManageSieve uses the optional sieve account block. RFC 5804 registers one port, 4190, and reaches TLS on it through STARTTLS, so a bare server address takes sieve:// with the upgrade on where the IMAP and SMTP ones take their implicit-TLS scheme.
sieves:// is accepted too, for the deployments listening for a handshake straight away. A password or a bearer token is refused on an unencrypted connection unless sieve.allow-cleartext-auth says otherwise.
capability, list, get, put, check, activate, deactivate, rename, delete and raw are available, and put and check accept a script file, inline text or stdin.
On a pimdir account, pimdir search, pimdir count, pimdir thread and pimdir tag ask the store's search index in the pimdir query language: words, phrases, field:value terms, and, or, not and parentheses, over the account's mail. The store's sync engine builds the index and refreshes it after each run; Himalaya only reads it, so a message shows once it is synced and indexed, and a line under the results says what they could not see (bodies not downloaded, mail older than what the store holds, the index lagging the store). pimdir tag queues one flag change per matched message for the sync engine to apply.
Composing messages
message compose / reply / forward cover simple cases through flags. For rich MIME (MML directives, signing, encryption, an editor-driven flow), chain a composer such as mml into message send / message add, or stage a prepared file as a draft:
himalaya message compose --to [email protected] --subject Hello --body Hi --send
mml compose >(himalaya message send)
himalaya message add -m drafts --flag draft < message.eml
See himalaya message send --help and the mml docs for the composer chaining.
Re-using sessions
Each invocation opens a fresh TCP+TLS+SASL session. To amortize the handshake, there are two options:
himalaya replkeeps one process alive for one account: each stdin line is a command without the binary name (envelope list,message read 42), run over clients that connect the first time a command needs them and stay connected for the session. Quote arguments as in a shell;quit,exitor EOF ends it. The account,--backendand--jsonare given once, on thereplcommand. Piped, every line gets one answer on stdout (one JSON value per line under--json, a failure being the error object), so a program can drive it. Stdin carries the commands, so a command takes its input as an argument or a file path, never from stdin. SMTP still connects per send, a server dropping an idle SMTP connection after a few minutes, and a command that fails drops its client so the next one connects again.- sirup serves a pre-authenticated IMAP/SMTP session over a Unix socket that
imap.server/smtp.servercan point at, shared by every process: the choice for many short invocations, or several tools at once. Do not point ahimalaya replat a sirup socket yet: sirup serves one client at a time and keeps the upstream alive only while none is attached.
MCP server
himalaya mcp runs a Model Context Protocol server over stdio for one account, exposing the shared API as tools (mailbox_list, envelope_list, envelope_search, message_read, attachment_list, attachment_download, the composers, and so on). Each tool takes the arguments of its command, spelled as on the command line (mailbox, page-size, message-ids), and answers the same JSON as --json; an argument a tool does not take is refused, never ignored. Clients connect once for the server's lifetime, as in himalaya repl.
Only the tools that read are listed by default. --allow-write adds the ones that change the store (flags, add, copy, move, delete, saving a composed message, marking a read one seen), and --allow-send the ones that send mail. An agent reading mail can be told by a message to send mail, so think twice before giving it both. Tools never read nor write local files: attachment_download always runs as attachment download --stdout, message parse and the file arguments of the composers are left out, and a message given to message_add or message_send is taken as the message itself, even when it spells a path. attachment_download returns the part's bytes as an embedded resource, and the composers take their attachments inline instead, as attach-data: a list of {filename, mimeType, data, text}, data in base64 or text as is. Either way, an attachment over --max-attachment-size <BYTES> (10 MiB by default) is refused with attachment-too-large. Under --allow-send this lets an agent send any data it holds, not only text.
{
"mcpServers": {
"himalaya": { "command": "himalaya", "args": ["mcp", "--account", "work"] }
}
}
Interfaces
Himalaya CLI is one of several front-ends to the Pimalaya libraries:
- pimalaya/himalaya-tui: official TUI (in active development)
- pimalaya/himalaya-vim: Vim plugin
- dantecatalfamo/himalaya-emacs: Emacs plugin
- jns/himalaya: Raycast extension
- openclaw/openclaw: OpenClaw SKILL
- parisni/dfzf: dfzf integration
- kezhenxu94/himalaya-fzf: interactive fzf front-end
FAQ
Aerc, mutt and alpine can be categorized as Terminal User Interfaces (TUI). When the program is executed, your terminal is locked into an event loop and you interact with your emails using keybinds.
Himalaya is a Command-Line Interface (CLI). There is no event loop: you interact with your emails using shell commands, in a stateless way.
A dedicated TUI (himalaya-tui) is in active development on top of the same Pimalaya libraries, and is definitely closer to aerc, mutt and alpine.
Every *.passwd / *.password / *.token field accepts either a raw literal or a shell command that prints the secret on stdout. The raw form is convenient for testing but should not be used in production:
imap.sasl.plain.passwd.raw = "***"
imap.sasl.plain.passwd.command = "pass show example"
imap.sasl.plain.passwd.command = ["pass", "show", "example"]
Native keyring support was removed in v2. Use a third-party keyring CLI (pass, secret-tool, gopass…) as the command.
v2 ships no OAuth flow. Use ortie, or any other token broker, to obtain an access token, then plug it in as a command printing that token on stdout.
For JMAP, point jmap.auth.bearer.token.command at the broker. For IMAP and SMTP, route the bearer through a SASL mechanism taking a command-sourced password.
The wizard probes several discovery mechanisms in parallel on the email address domain, merges their results and ranks them per domain, implicit TLS before STARTTLS, unencrypted servers dropped:
- PACC draft-ietf-mailmaint-pacc-02: well-known JSON, digest-verified against the
_ua-auto-configTXT record. - Thunderbird Autoconfiguration: ISP main / well-known / ISPDB lookups, then the autoconfig of the provider the MX records name, then the
mailconf=<URL>TXT redirect. - RFC 6186 SRV:
_imap._tcp,_imaps._tcp,_submission._tcplookups assembled into a single report. - RFC 8620 JMAP: the SRV record and the
/.well-known/jmapsession resource.
A final WWW-Authenticate probe refines the advertised auth schemes. A Google or Microsoft account, detected by its domain or its MX records, puts the Gmail API or Microsoft Graph first, IMAP and SMTP staying one change away. See io-pim-discovery for the full chain.
Use --log-level <level> (alias --log) where <level> is one of off, error, warn, info, debug, trace:
himalaya --log trace mailbox list
The RUST_LOG environment variable is consulted when --log is not passed, and supports per-target filters (see the env_logger documentation). RUST_BACKTRACE=1 enables full error backtraces.
Logs are written to stderr, so they can be redirected easily to a file:
himalaya --log trace mailbox list 2>/tmp/himalaya.log
You can also send logs straight to a file via --log-file <path>:
himalaya --log trace --log-file /tmp/himalaya.log mailbox list
Set NO_COLOR=1 in your environment.
License
This project is licensed under either of:
Social
- Chat on Matrix
- News on Mastodon or RSS
- Mail at [email protected]
Sponsoring
Special thanks to the NLnet foundation and the European Commission that have been financially supporting the project for years:
- 2022 → 2023: NGI Assure
- 2023 → 2024: NGI Zero Entrust
- 2024 → 2026: NGI Zero Core
- 2026 → 2027: NGI Zero Commons Fund
This program is part of Pimalaya, free software funded entirely by grants and donations. If you find it useful, consider sponsoring its development:
