posta
posta is a tiny, generic IMAP/SMTP mail client for your terminal. One binary, JSON output by default, and named accounts in a single TOML file — so your mailbox is as scriptable as any other API.
The name comes from the postas: the relay stations of the colonial mail routes of South America. In Argentine slang, la posta also means "the real thing".
$ posta list --unread --max 3 --format table
UID DATE FROM SUBJECT
7121 ● Fri, 17 Jul 2026 17:0… "Example ISP" <billing@isp.ex… Your invoice is ready
7120 ● Fri, 17 Jul 2026 15:0… Meetup <events@meetup.examp… 🚀 Registration open
7119 ● Fri, 17 Jul 2026 10:1… "bank@example.com" Card notice
$ posta search 'FROM bank UNSEEN' | jq .uids
[7119, 7118, 7117]
Features
- Works with any provider — plain IMAP/SMTP with STARTTLS (
imap://,smtp://) or implicit TLS (imaps://,smtps://). Your own server, Gmail, Fastmail, anything. - Multiple accounts — named accounts in one config file, switched with
--account,$POSTA_ACCOUNT, or adefault_account. - Script-friendly — every command prints pretty JSON (pipe it to
jq), with--format tablefor humans. - Secrets your way — literal passwords (the file is checked for loose
permissions and posta warns you) or
password_cmdto fetch them frompass, a keyring, or any command. - Safe by default — reading never marks messages as seen
(
BODY.PEEKeverywhere); opt in with--mark-readorposta mark read. - No config file needed — everything can come from
POSTA_*environment variables, handy for CI and one-off scripts.
Install
git clone https://git.all.ar/pub/posta.git
cd posta
cargo install --path .
Configure
~/.config/posta/config.toml (or $POSTA_CONFIG, or --config FILE):
default_account = "personal"
[accounts.personal]
email = "alice@example.com"
display_name = "Alice Example"
imap_url = "imap://alice@example.com@mail.example.com:143"
smtp_url = "smtp://alice@example.com@mail.example.com:587"
password_cmd = "pass show mail/personal" # or: password = "..." (chmod 600!)
[accounts.work]
email = "alice@work.example"
imap_url = "imaps://alice@imap.work.example" # implicit TLS, port 993
smtp_url = "smtps://alice@smtp.work.example" # implicit TLS, port 465
password_cmd = "secret-tool lookup mail work"
sent_folder = "Sent Items"
trash_folder = "Deleted"
The user part of a URL may itself contain @
(imap://alice@example.com@mail.example.com); the host starts after the
last one. imap_password[_cmd] / smtp_password[_cmd] override the shared
password per protocol when they differ.
Environment variables override any field of the selected account:
POSTA_IMAP_URL, POSTA_SMTP_URL, POSTA_PASSWORD, POSTA_PASSWORD_CMD,
POSTA_IMAP_PASSWORD, POSTA_SMTP_PASSWORD, POSTA_EMAIL,
POSTA_DISPLAY_NAME, POSTA_SENT_FOLDER, POSTA_TRASH_FOLDER,
plus POSTA_ACCOUNT and POSTA_CONFIG.
Use
posta accounts # configured accounts
posta status # message/unread counts
posta folders # list mailboxes
posta list --unread # newest first
posta list --query 'FROM bank SINCE 1-Jul-2026'
posta get 7119 # headers + text body, as JSON
posta get 7119 --html # prefer the HTML part
posta get 7119 --raw > msg.eml # raw RFC 822 source
posta send --to bob@example.com --subject 'Hi' --body 'Hello!'
git log | posta send --to bob@example.com --subject 'Changelog'
posta mark read 7119 7120 # also: unread, flagged, unflagged
posta move 7119 --to Archives
posta delete 7119 # moves to the trash folder
posta search 'FROM bank UNSEEN' # raw IMAP SEARCH, prints UIDs
Global flags: --account/-a NAME, --config FILE,
--format json|table, and --folder on folder-scoped commands.
Every command opens a fresh session and logs out; there is no daemon, cache, or local state.
Notes
sendappends a copy to the sent folder over IMAP (skip with--no-record).move/deleteuseUID MOVEwhen the server supports it, falling back toCOPY+\Deleted+EXPUNGE.- TLS is always required; plain-text connections are not supported.
Development
cargo test # unit + CLI integration tests, no server needed
cargo clippy --all-targets -- -D warnings
cargo fmt --check