Email accounts, not agent identity.
emails is a small account/persona layer around himalaya. It knows mail config, account names, servers, defaults, and signing policy. It does not know who launched it.
config file
EMAILS_CONFIG
HIMALAYA_CONFIG
.emails/himalaya.toml # found by walking upward from $PWD
~/.config/emails/himalaya.toml
│
▼
accounts / personas
[accounts.personal] [accounts.kkl] [accounts.client]
│ │ │
│ default? │ gpg policy? │ downloads dir?
▼ ▼ ▼
read / list / send / reply through exactly one selected account
The key design decision is the boundary: account selection belongs to mail config, not to agent process state. That makes the same checkout usable for a person, an agent, a repo-local client persona, or a one-off operations mailbox without teaching the tool about any of those identities.
| emails knows | emails does not know |
|---|---|
| Config paths | AGENT_HOME, chat identity, or workspace owner |
| Account names and email addresses | Git author, GPG signing identity for commits, or org membership |
| IMAP/SMTP hosts and ports | Where your password came from |
| Optional default account | Which account you "probably meant" when config is ambiguous |
| Per-account GPG mail signing policy | Global rules like "all agents sign" or "all company mail signs" |
shiv install emailsSet up an explicit account. Passwords come from stdin, so your secret manager stays outside the tool.
# Optional: keep this repo/home on its own mail config.
export EMAILS_CONFIG="$PWD/.emails/himalaya.toml"
your-secret-manager read mail/personal/password \
| emails account setup personal \
--address you@example.com \
--display-name "Your Name" \
--imap-host imap.example.com \
--smtp-host smtp.example.com \
--password-stdinIf the config lives in a directory named .emails, setup also writes a protective .gitignore so the local password-bearing config is not accidentally committed.
Multiple accounts are fine. Ambiguous sending is not.
# Add a second persona to the same config.
your-secret-manager read mail/work/password \
| emails account setup work \
--address you@company.com \
--imap-host imap.company.com \
--smtp-host smtp.company.com \
--password-stdin
emails account list
# Be explicit when there is no default.
emails send --account personal --to friend@example.com --subject "Lunch" -b lunch.txt
emails send --account work --to client@example.com --subject "Update" -b update.html --html
# Or choose a default for this config.
emails account default work
emails account default --clear| Situation | Result | Why |
|---|---|---|
| One account | Use it | A single configured persona is unambiguous. |
--account work |
Use work |
Explicit command flags beat defaults. |
| Multiple accounts, one default | Use the default | The config says which persona is normal here. |
| Multiple accounts, no default | Fail | The tool asks for --account or emails account default. |
| Two defaults | Fail | A broken config is safer than a guessed sender. |
Mail signing is opt-in per persona. Configure it on the account that should sign mail; override per send only when you mean it.
emails account gpg enable work --local-user you@company.com
emails account gpg status work
# Require signing for one message. Fails if the selected account cannot sign.
emails send --account work --sign --to client@example.com --subject "Signed update" -b update.txt
# Suppress account-default signing for one message.
emails send --account work --no-sign --to robot@example.com --subject "Unsigned log" -b log.txt| Account policy | Send mode | Outcome |
|---|---|---|
| Account has no GPG | plain send | Unsigned |
| Account has no GPG | --sign |
Fail; enable signing first |
| Account has GPG | plain send | Signed by account policy |
| Account has GPG | --no-sign |
Unsigned for this send |
emails welcome # current config, folders, recent messages
emails list # inbox table
emails read 42 # message body, headers, signature status
emails reply 42 -b reply.txt # reply through the selected account
emails quota # storage quota
emails sizes # largest folders/messages
emails wait # block until new mail arrivesSend accepts inline text, a body file, stdin, or a JSON envelope. Prefer explicit flags in scripts.
emails send --to user@example.com --subject "Plain update" -b body.txt
cat body.html | emails send --account work --to client@example.com --subject "HTML update" --html
emails send --file email.json
emails send --to user@example.com --subject "With attachment" -b body.txt --attach report.pdfFor polished one-to-one business correspondence, start from a TSX example, edit it as the source artifact, render HTML, then send the generated file.
emails example business-letter > bob.tsx
$EDITOR bob.tsx
emails compose bob.tsx > bob.html
emails send --account work --to client@example.com --subject "Follow-up" --html -b bob.htmlThe business-letter example uses table-based layout and inline styles for email-client compatibility, including a constrained-width letterhead, date/descriptor area, emphasized recommendation block, bullets, and signature.
- Passwords arrive through explicit stdin from the caller's chosen secret manager.
- Ambiguous multi-account configs fail with the command to fix them.
- Bodies under 50 characters require
--allow-short. - If a positional subject looks like an email address, send stops and points to
--cc. - Account GPG policy decides default mail signing.
The README shows workflows, not a full command catalog. Use emails <command> --help for exact flags. The central commands are:
| Command | Purpose |
|---|---|
emails account setup |
Setup an explicit email account in the selected Himalaya config |
emails account list |
List configured email accounts |
emails account default |
Set or clear the default email account |
emails account gpg enable |
Enable GPG signing for an email account |
emails list |
List email messages |
emails read |
Read an email message |
emails send |
Send an email |
emails reply |
Reply to an email message |
emails example |
Print a starter TSX email example |
emails compose |
Compose an email from a TSX file |
emails doctor |
Check local development setup |
152 tests across two suites:
- Unit tests (116) — mock himalaya and test task logic in isolation
- Integration tests (36) — real himalaya against a local maildir backend, full round-trip, no network
mise run test
mise run test-integration
mise run doctor
codebase lint "$PWD"
mise exec -- readme build --checkgit clone https://github.com/KnickKnackLabs/emails.git
cd emails
mise trust && mise install
mise run test
mise run doctorRequires himalaya. For generated docs, edit README.tsx and run mise exec -- readme build.
Generated from [README.tsx](https://github.com/KnickKnackLabs/readme). Mail is a persona; choose it deliberately.