Skip to main content
IMAP/SMTP mail client following the pimalaya/himalaya vocabulary. Install it on the workspace and the whole tree is discoverable with himalaya --help.

Install

Two installs under different names are two accounts. In YAML, the same install rides the clis: section; see the CLI overview.

Sent copies

Sending is SMTP and keeps no record of itself, so the copy in your own Sent mailbox is a second, separate IMAP APPEND that mail clients make on your behalf. mirage makes it too, \Seen, on every --send. Which mailbox it lands in is asked, not guessed: a server that implements RFC 6154 tags one of its mailboxes \Sent in its folder listing, which is [Gmail]/Sent Mail on Gmail and Sent Items on Exchange. Set sentFolder to pin a name and skip the probe, or saveCopy=False to file nothing.
--save <MAILBOX> overrides both for one line, and on its own (without --send) it files the message without sending it, which is how a draft is written. The two failure modes differ on purpose: a copy that fails after a successful send is a warning on stderr and exit 0, because the mail is already gone and a non-zero exit invites a retry that would send it twice; a --save that sends nothing fails loudly, because nothing happened yet.

Verbs

The verbs follow the himalaya CLI structure: himalaya envelope list|search to triage, himalaya message read|compose|send|reply|forward to act. Messages are addressed by positional id, the mailbox by -m/--mailbox, and reads return JSON rather than a rendered table. Upstream aliases resolve too: envelope ls, envelope sr, message write, message new, message fwd.

himalaya envelope list

List a mailbox, most recent first.
Only the newest page * page_size messages are fetched, so listing the first page costs one page of header fetches rather than a scan of the whole mailbox. The account’s max_messages (default 200) bounds how far back paging can reach; an order by is unrelated to arrival order, so a sorted search considers that whole window. Filter and sort with himalaya’s own query DSL. The query is the trailing operand, so it is words rather than flags.
Conditions: date <yyyy-mm-dd>, before <yyyy-mm-dd>, after <yyyy-mm-dd>, from <pattern>, to <pattern>, subject <pattern>, body <pattern>, and flag <seen|answered|flagged|draft|deleted>. Combine them with and, or and not, group with parentheses, and sort with order by <date|from|to|subject> [asc|desc]. Three things to know about the grammar. The date conditions read the message’s own Date: header, not the mailbox’s received-at timestamp, so imported or delayed mail lands on the day it was sent. after is strictly greater than the given day, unlike IMAP’s inclusive SENTSINCE. And a pattern containing spaces needs literal double quotes inside the shell’s quoting ('"quarterly review"'), because the shell’s own quotes are gone by the time the query reaches the parser, exactly as upstream behaves. The same paging flags as envelope list apply. A query that does not parse exits 1 without contacting the server.

himalaya message read

himalaya message compose

The built-in flag composer. Without --send it writes the assembled RFC 5322 message to stdout, so it can be piped into message send or into another composer.

himalaya message send

Sends a raw RFC 5322 message taken from the operand or from stdin. This is the sink a composer chain feeds.

himalaya message reply

Fetches the source message, prefills Re: on the subject plus In-Reply-To / References, derives the recipient from the source’s Reply-To (else its From), and quotes the source body. Like compose, it writes MIME to stdout unless --send is passed.
It carries every composer flag above, plus the mailbox and: There is no --all flag, matching upstream: reply-all is spelled by naming the other recipients with --cc, which message read reports.

himalaya message forward

Same flags as reply. The subject gains Fwd:, References carries over, and In-Reply-To does not.

Divergences from upstream

Deliberate gaps, all of which fail loudly rather than silently:
  • message add, copy, move, delete, flag add, attachment download, mailbox and the protocol-specific subgroups (imap, jmap, gmail, msgraph, smtp) are not implemented. An unknown verb exits 1 with git’s wording.
  • message read --seen is absent because the mount is read-only and never flips \Seen.
  • The account-level sent copy is mirage’s own, and defaults on. Upstream v2 files a copy only when --save <MAILBOX> names one; a mirage agent that never learned the flag still leaves the record a human sender would. Turn it off with saveCopy per account.
  • Resolving the sent mailbox from the server’s RFC 6154 \Sent tag is ahead of upstream, whose own IMAP backend still pins INBOX alone while it waits on LIST RETURN (SPECIAL-USE) support in io-imap.
  • envelope list renders JSON, not a table, so its table flags (--max-width, --recipient, --has-attachment) do not exist.
  • Body and signature files (--body-file, --signature-file) are not wired up; --attach is, reading each path through the workspace, with the content type guessed from a fixed extension table rather than a full mime database.
Server behavior can differ too: whether from alice matches alice@example.com as a substring is up to the IMAP server, not mirage.