cd /news/developer-tools/himalaya-cli-to-manage-emails · home topics developer-tools article
[ARTICLE · art-80881] src=github.com ↗ pub= topic=developer-tools verified=true sentiment=· neutral

Himalaya – CLI to Manage Emails

Himalaya, a Rust-based CLI tool for managing emails, supports IMAP, SMTP, JMAP, Gmail REST API, and Microsoft Graph, with features including multi-account TOML configuration, TLS via Rustls or Native TLS, and proxy support. The tool can be installed via shell script, cargo, or package managers like pacman, brew, and scoop, and offers a wizard for automatic provider discovery.

read10 min views2 publishedJul 30, 2026
Himalaya – CLI to Manage Emails
Image: source
  • Features
  • Installation
  • Configuration
  • Usage
  • Interfaces
  • FAQ
  • AI disclosure
  • License
  • Social
  • Contributing
  • Sponsoring

Shared API formailboxes

,envelopes

,flags

,messages

andattachments

Protocol-specific APIs exposing each backend's full surfaceIMAP,** SMTP**,** JMAP**,** Gmail**(REST API),** Microsoft Graph**(Outlook / Microsoft 365) support** Maildir**,specs** m2dirsupportspecs Simple authsupport for IMAP/SMTP: anonymous, login, plain, oauthbearer, xoauth2, scram-sha-256 HTTP authsupport for JMAP: basic, bearer TLS**support:Rustlswith ring cryptoRustlswith aws crypto (requiresrustls-aws

feature)Native TLS(requiresnative-tls

feature)

Discovery support:SOCKS5,** HTTP**proxy support via$ALL_PROXY

and$HTTP_PROXY

TOML configuration with multi-account supportJSON output via--json

Tip

Himalaya is written in Rust and uses cargo features to gate backend support. The default feature set is declared in Cargo.toml.

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 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

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
brew install himalaya

Note

Cargo features are not compatible with brew. If you need a different feature set, please use another installation method.

scoop install himalaya

From the COPR repo:

dnf copr enable atim/himalaya
dnf install himalaya

If you have the Flakes feature enabled:

nix profile install github:pimalaya/himalaya

Or run without installing:

nix run github:pimalaya/himalaya
git clone https://github.com/pimalaya/himalaya
cd himalaya
nix run

Run himalaya

. With no configuration file on disk the wizard prompts for an account name and an email address, runs provider discovery (PACC, Thunderbird Autoconfiguration, RFC 6186 SRV and RFC 8620 JMAP resolution, all probed in parallel and merged), fills the IMAP/SMTP (or JMAP) prompts with the discovered defaults, then writes the result to disk.

A persistent configuration is loaded from the first valid path among:

$XDG_CONFIG_HOME/himalaya/config.toml

$HOME/.config/himalaya/config.toml

$HOME/.himalayarc

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 the path with -c <PATH>

; multiple paths can be passed at once, separated by :

. The first one is the base and the rest are deep-merged on top.

To add or reconfigure an account, run the bare himalaya

wizard again and redirect its printed TOML into (or merge it with) your config file. There is no in-place edit subcommand; himalaya account list

and himalaya account check

inspect and validate the accounts already declared.

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 = "example@proton.me"
imap.sasl.plain.password.command = "pass show proton-bridge"

smtp.server = "smtp://127.0.0.1:1025"
smtp.sasl.plain.username = "example@proton.me"
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 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 = "example@fastmail.com"
imap.sasl.plain.password.command = "pass show fastmail"

smtp.server = "smtps://smtp.fastmail.com"
smtp.sasl.plain.username = "example@fastmail.com"
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 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 = "example@gmail.com"
imap.sasl.plain.password.command = "pass show gmail"

smtp.server = "smtps://smtp.gmail.com:465"
smtp.sasl.plain.username = "example@gmail.com"
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 mailboxes live under the [Gmail]/

prefix — quote them in the shell (-m "[Gmail]/Drafts"

) or reach them through an alias. [Gmail]/All Mail

is the archive containing every message: aliasing it makes "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.

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 = "example@outlook.com"
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 = "example@outlook.com"
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.

Standard IMAP/SMTP with your regular account password — no app password required.

[accounts.posteo]

imap.server = "imaps://posteo.de"
imap.sasl.plain.username = "example@posteo.net"
imap.sasl.plain.password.command = "pass show posteo"

smtp.server = "smtps://posteo.de"
smtp.sasl.plain.username = "example@posteo.net"
smtp.sasl.plain.password.command = "pass show posteo"

From the iCloud Mail support page: the IMAP login is the name of your address (johnappleseed

, not johnappleseed@icloud.com

) while the SMTP login is the full address, and 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 = "johnappleseed@icloud.com"
smtp.sasl.plain.password.command = "pass show icloud"

mailbox.alias.sent = "Sent Messages"

Every command carries its own --help

, the source of truth for its flags and syntax. The snippets below are a taste of the surface.

Backend-agnostic commands run on the account's first configured backend, or the one picked with -b/--backend

. When an inbox

alias is set under [mailbox.alias]

, -m/--mailbox

defaults to it.

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: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; its grammar lives in himalaya envelope search --help

.

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 "alice@example.com"\r\n'
himalaya jmap mailbox query --role drafts
himalaya gmail messages list -q "from:alice is:unread"
himalaya msgraph mail-folder list
himalaya smtp send -f me@example.com -t you@example.com < message.eml

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 you@example.org --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.

Each invocation opens a fresh TCP+TLS+SASL session. To amortize the handshake, pair himalaya with sirup: it serves a pre-authenticated IMAP/SMTP session over a Unix socket that imap.server

/ smtp.server

can point at.

Himalaya CLI is one of several front-ends to the Pimalaya libraries:

pimalaya/himalaya-tui: official TUI (in active development)pimalaya/himalaya-vim: Vim plugindantecatalfamo/himalaya-emacs: Emacs pluginjns/himalaya: Raycast extensionopenclaw/openclaw: OpenClaw SKILLparisni/dfzf: dfzf integration

How different is it from aerc, mutt or alpine? #

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.

How are secrets resolved? #

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

.

How is OAuth 2.0 handled? #

v2 does not ship OAuth flows. Use pimalaya/ortie (or any other token broker) to obtain an access token, then plug it as a command

returning the token on stdout. For JMAP, point jmap.auth.bearer.token.command

at the broker; for IMAP/SMTP, route the bearer through a SASL mechanism that consumes a command-sourced password.

How does the wizard discover IMAP/SMTP/JMAP configs? #

The wizard probes several discovery mechanisms in parallel on the email address domain, merges their results and keeps the most secure endpoint per service:

PACC: well-known JSON, digest-verified against thedraft-ietf-mailmaint-pacc-02_ua-auto-config

TXT record.Thunderbird Autoconfiguration: ISP main / well-known / ISPDB lookups, then MX-based retry, then themailconf=<URL>

TXT redirect.RFC 6186 SRV:_imap._tcp

,_imaps._tcp

,_submission._tcp

lookups assembled into a single report.RFC 8620 JMAP: the SRV record and the/.well-known/jmap

session resource.

A final WWW-Authenticate probe refines the advertised auth schemes, and a detected Google or Microsoft account short-circuits to its dedicated configurations. See io-pim-discovery for the full chain.

How to debug Himalaya? #

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

How to disable color output? #

Set NO_COLOR=1

in your environment.

This project is developed with AI assistance. This section documents how, so users and downstream packagers can make informed decisions.

Tools: Claude Code (Anthropic), invoked locally with a persistent project-scoped memory and a small set of repo-specific rules.Used for: Refactors, mechanical multi-file edits, boilerplate (feature gates, error enums, derive macros, trait impls), test scaffolding, doc polish, exploratory design conversations.Not used for: Engineering, critical code, git manipulation (commit, merge, rebase…), real-world tests.** Verification**: Every AI-assisted change is read, compiled, tested, and formatted before commit. Behavioural correctness is verified against the relevant RFC or upstream spec, not assumed from the model output. Tests are never adjusted to fit AI-generated code; the code is adjusted to fit correct behaviour.Limitations: AI models occasionally produce code that compiles and passes tests but is subtly wrong. The verification workflow catches most of this; it does not catch all of it. Bug reports are welcome and taken seriously.Last reviewed: 17/07/2026

This project is licensed under either of:

at your option.

Contributions are welcome: start with CONTRIBUTING.md, which opens with the Pimalaya-wide guides to read first.

Special thanks to the NLnet foundation and the European Commission that have been financially supporting the project for years:

If you appreciate the project, feel free to donate using one of the following providers:

── more in #developer-tools 4 stories · sorted by recency
── more on @himalaya 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/himalaya-cli-to-mana…] indexed:0 read:10min 2026-07-30 ·