Features • Quick Start • Navigation • Commands • Configuration • Architecture • License
Postero (pstr) is a modern open-source terminal email client designed for productivity. Built from the ground up for developers, engineers, and command-line aficionados, Postero combines the power of TUI with the simplicity and clarity you expect from modern workflows.
Features include:
- Intuitive text-based interface - Reading, replying, forwarding, and organizing email from the terminal
- Modern authentication - IMAP/SMTP accounts with app passwords,
password_cmd, native OS keychain storage, and built-in OAuth2 login - Fast search and MIME filters - Keyboard-centric navigation with configurable HTML-to-text rendering
- Interactive TUI - Bubble Tea based workflow for inboxes, drafts, attachments, focused reading, and vim-style navigation
- Keyboard-first workflow - Built-in shortcuts for triage, search, compose, and pane navigation
- Account-aware mail flow - Per-account sync, compose, reply, and send flows from CLI and TUI
- Composable drafts and attachments - Save drafts locally, inspect attachments, and download them from the TUI
go install github.com/kriuchkov/postero/cmd/pstr@latestgo build -o pstr ./cmd/pstrDownload the latest release from the Releases page.
Start Postero:
pstrRunning pstr without subcommands opens the interactive TUI.
Sync mailbox:
pstr syncSearch emails:
pstr search "subject:golang"Compose new email:
pstr composeGenerate a compose or reply draft with AI using configured templates:
pstr compose ai --account Gmail --to user@example.com --instruction "Draft a short project kickoff email"
pstr reply ai msg-001 --template reply-default --instruction "Politely accept and ask for the agenda" --allThe TUI is keyboard-first and now supports vim-style movement across the sidebar, message list, reader pane, and composer.
h/lor←/→- Move focus between sidebar, message list, and reader panej/kor↓/↑- Move within the active pane- Prefix motions with counts such as
5j,3gg, or2Gto move multiple rows or jump to a specific visible item ggorHome- Jump to the top of the active paneGorEnd- Jump to the bottom of the active pane0- Jump to the start of the active pane$- Jump to the end of the active paneCtrl+u/Ctrl+d- Half-page up or downPgUp/PgDnorCtrl+b/Ctrl+f- Full-page movement
/- Start live search in the current mailboxEnter- Keep the current filtered result or open the selected draftEsc- Clear the active search or clear account scoping from the sidebar
c- Compose a new messager- ReplyR- Reply allf- Forwardd- Move to trash, or permanently delete in Trasha- Archive!- Mark as spam.- Repeat the last archive, trash, spam, or permanent delete action on the current selectionu- Undo the last delete, archive, or spam action while the undo window is actives- Save attachments from the selected message to~/Downloads
:- Open the command palette- Supported commands:
compose,compose-ai,reply-ai,reply-all-ai,inbox,sent,drafts,archive,trash,spam,refresh,help,quit - AI commands open the normal composer with generated content, for example
:compose-ai Draft a short kickoff email,:compose-ai --template compose-default Draft a short kickoff email, or:reply-ai --template reply-default Politely accept and confirm - While AI generation is in flight, the header and footer show a dedicated loading badge so network-backed draft generation is visible
- While a compose draft has unsaved changes, commands that would abandon it are blocked until you save, send, or cancel it
Compose has a normal mode for navigation and a writing mode for text entry.
j/k- Move between Account, To, Subject, and Body while in normal modeh/lor←/→on the Account field - Switch the sending account- Counts also work in compose normal mode, for example
2j,3gg, orG gg,G,0,$- Jump to the first or last compose fieldiorEnter- Enter writing mode for the selected field:- Open the command palette from compose, including AI drafting commands such ascompose-ai --template compose-default ...Esc- Leave writing mode; pressEscagain to cancel composeCtrl+o- Save draftCtrl+x- Send message
Postero supports a flexible configuration system using YAML files and environment variables.
Priority Order:
- Command-line flags
- Environment variables
- Configuration file (
~/.config/postero/config.yamlor./config.yaml) - Default values
Example ~/.config/postero/config.yaml:
accounts:
- name: "personal"
provider: "gmail"
email: "user@example.com"
username: "user@example.com"
# For common providers, Postero fills IMAP/SMTP defaults automatically.
imap:
# password: "imap-app-password"
# password_cmd: ["pass", "show", "email/personal-imap"]
smtp:
# password: "smtp-app-password"
# password_cmd: ["pass", "show", "email/personal-smtp"]
# Optional shared fallback if IMAP/SMTP passwords are the same
# password: "shared-app-password"
# password_cmd: ["pass", "show", "email/personal"]
oauth2:
client_id: "your-client-id"
client_secret: "your-client-secret"
filters:
# Render HTML emails using w3m
text/html: "w3m -T text/html -dump"
# Optional plain text post-processing
# text/plain: "sed -e 's/\\r$//'"
tui:
# Messages fetched per page in the interactive list and search results.
list_page_size: 30
# How close the cursor gets to the bottom before the next page is fetched.
list_prefetch_ahead: 5
# Spinner frame interval for loading indicators, in milliseconds.
loading_tick_ms: 120If username is omitted, Postero uses email as the login.
For common public providers, provider: "gmail" and provider: "outlook" prefill the standard IMAP/SMTP hosts, ports, TLS, and OAuth2 defaults.
For real IMAP and SMTP access, Postero resolves credentials in this order:
- Refreshed OAuth2 tokens for accounts configured with
auth_type: oauth2 password_cmdat protocol or account level- Native OS keychain entries saved with
pstr auth setorpstr auth login - Environment variables
- Inline config passwords
Environment variable fallbacks:
POSTERO_<ACCOUNT_NAME>_IMAP_PASSWORD, for examplePOSTERO_OUTLOOK_IMAP_PASSWORDPOSTERO_<ACCOUNT_NAME>_SMTP_PASSWORD, for examplePOSTERO_OUTLOOK_SMTP_PASSWORDPOSTERO_IMAP_PASSWORDandPOSTERO_SMTP_PASSWORDas protocol-wide fallbacksPOSTERO_<ACCOUNT_NAME>_PASSWORDorPOSTERO_PASSWORDas shared fallbacks for both protocols
The same env override convention applies to TUI settings. Examples:
POSTERO_TUI_LIST_PAGE_SIZE=50POSTERO_TUI_LIST_PREFETCH_AHEAD=8POSTERO_TUI_LOADING_TICK_MS=90
These override the corresponding YAML keys under tui:.
If imap.username or smtp.username is omitted, Postero falls back to username, then to email.
sync and compose --send now use the configured IMAP/SMTP servers directly and return a clear error if credentials are missing.
Useful auth and config commands:
pstr config init gmail
pstr config validate
pstr auth add personal --provider gmail --email user@example.com
pstr auth set personal
pstr auth login personal
pstr auth delete personalpstr auth add saves or updates an account entry in config.yaml. pstr auth login performs the OAuth2 code exchange inside Postero, stores the resulting token in the OS keychain, and can also bootstrap missing OAuth client settings from CLI flags.
pstr- Launch the interactive terminal UIsync- Synchronize emails with IMAP serversearch- Search emails by subject, sender, or contentshow- Print one message with headers, labels, attachments, and bodycompose- Create and send new emailreply- Reply to selected emailforward- Forward emaillist- Print a mailbox snapshot to stdoutread- Mark a message as readstar- Toggle the starred state of a messagearchive- Move a message out of Inbox into Archivetrash- Mark a message as trashed without deleting it permanentlydelete- Permanently remove a message from the local storespam- Mark a message as spam
auth subcommands manage saved credentials and OAuth2 logins:
auth set <account>- Save a password in the OS keychainauth add <provider>- Create or update a provider-backed account inconfig.yamlauth login <account>- Run the built-in OAuth2 login flow and save the token in keychainauth delete <account>- Remove stored credentials for the account
config subcommands help initialize and validate YAML configuration:
config init <provider>- Print a starter config snippet for a known providerconfig validate- Check the loaded config and print actionable validation hints
compose, reply, forward, and sync support --account so you can explicitly choose the configured account by name or email.
compose ai and reply ai use ai.providers and ai.templates from the config file. Templates are rendered with compose/reply context and must return JSON with subject and body. Use --instruction for the high-level request and --var key=value for extra template data.
list supports mailbox and output filters such as --mailbox, --label, --limit, and --format. search supports --account, --label, --limit, --unread, and --format for scripting-friendly usage.
Message action commands such as read, star, archive, trash, spam, and delete accept multiple IDs and can read IDs from stdin with --stdin-ids for shell pipelines. trash is reversible mailbox state, while delete permanently removes messages from the local store.
Examples:
pstr sync --account Outlook
pstr list --mailbox archive --limit 10
pstr search invoice --account Gmail --unread
pstr show msg-001
pstr compose --account Gmail --to user@example.com --subject "Hello" --attach ./invoice.pdf
pstr reply msg-001 --account Gmail --all --send
pstr trash msg-001 msg-002
pstr search invoice --format json | jq -r '.[].id' | pstr archive --stdin-ids
pstr delete msg-999
pstr archive msg-001Postero follows Clean Architecture principles with a clear separation of concerns:
- Entities/Models - Core email message types
- Use Cases/Services - Business logic for email operations
- Interface Adapters - Grouped by responsibility: commands, mail, storage, and UI
- Frameworks - Cobra for CLI, Bubble Tea for TUI
postero/
├── cmd/
│ └── pstr/
│ └── main.go
├── internal/
│ ├── adapters/
│ │ ├── commands/
│ │ │ └── cli/ # Cobra commands and CLI entrypoints
│ │ ├── mail/
│ │ │ ├── imap/ # IMAP transport adapter
│ │ │ └── smtp/ # SMTP transport adapter
│ │ ├── storage/
│ │ │ ├── file/ # JSON file-backed storage adapter
│ │ │ └── sqlite/ # SQLite-backed storage adapter
│ │ └── ui/
│ │ └── tui/ # Bubble Tea terminal UI
│ ├── app/ # Runtime wiring and factories
│ ├── config/ # Configuration management
│ ├── core/
│ │ ├── models/ # Domain models plus service request/response types
│ │ ├── errors/ # Domain errors
│ │ └── ports/ # Interfaces
│ └── services/
│ └── message/ # Email operations service
└── go.mod
Runtime wiring supports both SQLite and file-backed storage through storage.backend. Use sqlite for the default database-backed mode or file for JSON-on-disk storage.
GPL-3.0-or-later