--- name: himalaya description: "Himalaya CLI: IMAP/SMTP email from terminal." version: 1.1.0 author: community license: MIT platforms: [linux, macos, windows] metadata: hermes: tags: [Email, IMAP, SMTP, CLI, Communication] homepage: https://github.com/pimalaya/himalaya prerequisites: commands: [himalaya] --- # Himalaya Email CLI Himalaya is a CLI email client that lets you manage emails from the terminal using IMAP, SMTP, Notmuch, or Sendmail backends. This skill is separate from the Hermes Email gateway adapter. The gateway adapter lets people email the agent and uses Hermes' built-in IMAP/SMTP adapter; this skill lets the agent operate a mailbox from terminal tools and requires the external `himalaya` CLI. ## References - `references/configuration.md` (config file setup + IMAP/SMTP authentication) - `references/message-composition.md` (MML syntax for composing emails) - `references/attachment-extraction.md` (Python recipe for extracting attachments from .eml files) ## Scripts - `scripts/bulk-export.py` (multi-folder bulk export with folder-relative ID handling) ## Prerequisites 1. Himalaya CLI installed (`himalaya --version` to verify) 2. A configuration file at `~/.config/himalaya/config.toml` 3. IMAP/SMTP credentials configured (password stored securely) ### Installation ```bash # Pre-built binary (Linux/macOS — recommended) curl -sSL https://raw.githubusercontent.com/pimalaya/himalaya/master/install.sh | PREFIX=~/.local sh # macOS via Homebrew brew install himalaya # Or via cargo (any platform with Rust) cargo install himalaya --locked ``` ## Configuration Setup Run the interactive wizard to set up an account: ```bash himalaya account configure ``` Or create `~/.config/himalaya/config.toml` manually: ```toml [accounts.personal] email = "you@example.com" display-name = "Your Name" default = true backend.type = "imap" backend.host = "imap.example.com" backend.port = 993 backend.encryption.type = "tls" backend.login = "you@example.com" backend.auth.type = "password" backend.auth.cmd = "pass show email/imap" # or use keyring message.send.backend.type = "smtp" message.send.backend.host = "smtp.example.com" message.send.backend.port = 587 message.send.backend.encryption.type = "start-tls" message.send.backend.login = "you@example.com" message.send.backend.auth.type = "password" message.send.backend.auth.cmd = "pass show email/smtp" # Folder aliases (himalaya v1.2.0+ syntax). Required whenever the # server's folder names don't match himalaya's canonical names # (inbox/sent/drafts/trash). Gmail is the common case — see # `references/configuration.md` for the `[Gmail]/Sent Mail` mapping. folder.aliases.inbox = "INBOX" folder.aliases.sent = "Sent" folder.aliases.drafts = "Drafts" folder.aliases.trash = "Trash" ``` > **Heads up on the alias syntax.** Pre-v1.2.0 docs used a > `[accounts.NAME.folder.alias]` sub-section (singular `alias`). > v1.2.0 silently ignores that form — TOML parses fine, but the > alias resolver never reads it, so every lookup falls through to > the canonical name. On Gmail this means save-to-Sent fails *after* > SMTP delivery succeeds, and `himalaya message send` exits non-zero. > Any caller (agent, script, user) that retries on that exit code > will re-run the entire send — including SMTP — producing duplicate > emails to recipients. Always use `folder.aliases.X` (plural, dotted > keys, directly under `[accounts.NAME]`). ## Hermes Integration Notes - **Reading, listing, searching, moving, deleting** all work directly through the terminal tool - **Composing/replying/forwarding** — piped input (`cat << EOF | himalaya template send`) is recommended for reliability. Interactive `$EDITOR` mode works with `pty=true` + background + process tool, but requires knowing the editor and its commands - Use `--output json` for structured output that's easier to parse programmatically - The `himalaya account configure` wizard requires interactive input — use PTY mode: `terminal(command="himalaya account configure", pty=true)` ## Common Operations ### List Folders ```bash himalaya folder list ``` ### List Emails List emails in INBOX (default): ```bash himalaya envelope list ``` List emails in a specific folder: ```bash himalaya envelope list --folder "Sent" ``` List with pagination: ```bash himalaya envelope list --page 1 --page-size 20 ``` ### Search Emails ```bash himalaya envelope list from john@example.com subject meeting ``` ### Read an Email Read email by ID (shows plain text): ```bash himalaya message read 42 ``` Export raw MIME: ```bash himalaya message export 42 --full ``` ### Reply to an Email To reply non-interactively from Hermes, read the original message, compose a reply, and pipe it: ```bash # Get the reply template, edit it, and send himalaya template reply 42 | sed 's/^$/\nYour reply text here\n/' | himalaya template send ``` Or build the reply manually: ```bash cat << 'EOF' | himalaya template send From: you@example.com To: sender@example.com Subject: Re: Original Subject In-Reply-To: Your reply here. EOF ``` Reply-all (interactive — needs $EDITOR, use template approach above instead): ```bash himalaya message reply 42 --all ``` ### Forward an Email ```bash # Get forward template and pipe with modifications himalaya template forward 42 | sed 's/^To:.*/To: newrecipient@example.com/' | himalaya template send ``` ### Write a New Email **Non-interactive (use this from Hermes)** — pipe the message via stdin: ```bash cat << 'EOF' | himalaya template send From: you@example.com To: recipient@example.com Subject: Test Message Hello from Himalaya! EOF ``` Or with headers flag: ```bash himalaya message write -H "To:recipient@example.com" -H "Subject:Test" "Message body here" ``` Note: `himalaya message write` without piped input opens `$EDITOR`. This works with `pty=true` + background mode, but piping is simpler and more reliable. ### Move/Copy Emails Move to folder (target folder comes first, then the message ID): ```bash himalaya message move "Archive" 42 ``` Copy to folder (target folder comes first, then the message ID): ```bash himalaya message copy "Important" 42 ``` ### Delete an Email ```bash himalaya message delete 42 ``` ### Manage Flags Add flag: ```bash himalaya flag add 42 --flag seen ``` Remove flag: ```bash himalaya flag remove 42 --flag seen ``` ## Multiple Accounts List accounts: ```bash himalaya account list ``` Use a specific account: ```bash himalaya --account work envelope list ``` ## Attachments Save attachments from a message: ```bash himalaya attachment download 42 ``` Save to specific directory: ```bash himalaya attachment download 42 --downloads-dir ~/Downloads ``` ## Output Formats Most commands support `--output` for structured output: ```bash himalaya envelope list --output json himalaya envelope list --output plain ``` ## Debugging Enable debug logging: ```bash RUST_LOG=debug himalaya envelope list ``` Full trace with backtrace: ```bash RUST_LOG=trace RUST_BACKTRACE=1 himalaya envelope list ``` ## Bulk Export (Multi-Folder) When exporting emails from multiple folders into a single directory, use a shell script that pairs each ID with its source folder. **Message IDs are folder-relative** — an ID from `--folder "GirlsMom"` will fail with "cannot find message" unless you also pass `--folder "GirlsMom"` to `message export`. ```bash #!/bin/bash OUTDIR=/tmp/export mkdir -p "$OUTDIR" # For each folder, list then export with matching --folder himalaya envelope list --folder "GirlsMom" --page-size 200 2>/dev/null \ | grep '^|' | grep -v '^|---' | grep -v '^| ID' \ | while read line; do id=$(echo "$line" | awk -F'|' '{gsub(/ /,"",$2); print $2}') himalaya message export "$id" --full --folder "GirlsMom" 2>/dev/null \ > "$OUTDIR/${id}.eml" done ``` See `scripts/bulk-export.py` for a complete multi-folder Python template that handles folder-relative IDs, pagination, and empty-file verification. ## Read-Only Gmail Setup For read-only access (no SMTP), omit the `message.send` block entirely: ```toml [accounts.personal] email = "you@gmail.com" display-name = "Your Name" default = true backend.type = "imap" backend.host = "imap.gmail.com" backend.port = 993 backend.encryption.type = "tls" backend.login = "you@gmail.com" backend.auth.type = "password" backend.auth.cmd = "echo YOUR_APP_PASSWORD" # inline for quick setup folder.aliases.inbox = "INBOX" folder.aliases.sent = "[Gmail]/Sent Mail" folder.aliases.drafts = "[Gmail]/Drafts" folder.aliases.trash = "[Gmail]/Trash" ``` Generate an App Password at https://myaccount.google.com/apppasswords (select "Mail", name it "Himalaya"). ## Pitfalls - **Folder-relative IDs**: Message IDs from `envelope list --folder "X"` only work with `message export --folder "X"`. Without `--folder`, you get "cannot find message". This is the #1 cause of silent empty exports. - **Search query ordering**: Flags like `--page-size` must come BEFORE search terms. `himalaya envelope list from foo@bar.com --page-size 5` fails; use `himalaya envelope list --page-size 5 "from foo@bar.com"` instead. - **`--output json` breaks with search queries**: When a search query returns no results or an error, `--output json` produces empty output that fails `json.load()`. Always test with plain-text output first, or wrap JSON parsing in try/except. - **`[Gmail]/All Mail` search syntax differs**: The All Mail folder uses a different search parser than regular folders. `himalaya envelope list --folder "[Gmail]/All Mail" "pandaneuro"` fails with a parse error. Use specific folders (INBOX, Sent Mail) with explicit `from`/`to` queries instead. - **Gmail folder names**: Gmail uses `[Gmail]/Sent Mail`, `[Gmail]/Drafts`, `[Gmail]/Trash` — not plain `Sent`/`Drafts`/`Trash`. The folder aliases handle this mapping. - **Empty exports are silent**: `himalaya message export` writes nothing and exits 0 when it can't find a message. Always verify with `wc -c` or `find -empty` after bulk exports. ## Tips - Use `himalaya --help` or `himalaya --help` for detailed usage. - Message IDs are relative to the current folder; re-list after folder changes. - For composing rich emails with attachments, use MML syntax (see `references/message-composition.md`). - Store passwords securely using `pass`, system keyring, or a command that outputs the password.