Install
Ratmail is distributed as a Rust binary and is easiest to install directly from the GitHub repository. The install command builds and locks dependency versions for reproducible output.
Install from GitHub with Cargo:
cargo install --git https://github.com/peter-fm/ratmail.git --locked After install, verify your binary is reachable in $PATH and check command help if needed:
ratmail --help Run:
ratmail First run auto-creates:
~/.config/ratmail/ratmail.toml(or$XDG_CONFIG_HOME/ratmail/ratmail.toml)~/.config/ratmail/.setup-completeafter setup completion
Keep ratmail.toml private. It can contain IMAP/SMTP credentials and should not be committed
to source control.
Run from source in the app repo:
cargo run -p ratmail From-source runs are useful when iterating on Ratmail internals, trying unreleased behavior, or validating changes to local ACL/CLI flows before publishing docs.
Onboarding Setup
The setup wizard is the fastest way to get a valid account config and safe baseline ACL policy in one pass. It is designed for first-run and for incrementally adding additional accounts later.
Run interactive setup:
ratmail setup The wizard flow includes:
- Provider pick: Gmail, Proton Bridge, Yahoo Mail, or custom IMAP/SMTP
- Account form (name, email/username, password, display name, provider hosts/ports)
- Optional multi-account add
- Optional AI CLI enablement
- ACL scope selection: one/many/all accounts
- ACL options: body read, attachment download, send, message changes
Generated defaults for each account:
db_path = "ratmail-<slug>.db"imap.initial_sync_days = 90imap.fetch_chunk_size = 10
If AI CLI is enabled, setup writes:
[cli]defaults and policy public key path intoratmail.toml- policy draft to
~/.config/ratmail/cli-policy.toml
Setup intentionally keeps AI access conservative by default. Even if CLI is enabled, your signed policy controls what is actually allowed at runtime.
Apply policy with admin privileges:
sudo ratmail lock That final admin step signs and applies policy to the system path, updates anti-rollback state, and ensures normal users or agents cannot silently replace policy.
For automated environments, ratmail setup --apply-system-policy can trigger the policy apply
step directly, but still requires admin privileges.
Config Reference
Ratmail merges behavior from TOML config plus runtime defaults. If a key is omitted, Ratmail falls back to built-in safe values for that section.
Ratmail checks config paths in this order:
./ratmail.toml$XDG_CONFIG_HOME/ratmail/ratmail.toml(or~/.config/ratmail/ratmail.toml)
Accounts
Each [[accounts]] entry is an independently addressable mailbox with its own database file.
Multi-account tabs in the TUI map directly to this list.
[[accounts]]
name = "Personal"
db_path = "ratmail-personal.db"
[accounts.imap]
host = "imap.example.com"
port = 993
username = "user@example.com"
password = "app-password"
skip_tls_verify = false
initial_sync_days = 90
fetch_chunk_size = 10
[accounts.smtp]
host = "smtp.example.com"
port = 587
username = "user@example.com"
password = "app-password"
from = "Your Name <user@example.com>"
skip_tls_verify = false Notes:
- Relative
db_pathresolves under$XDG_STATE_HOME/ratmail(or~/.local/state/ratmail) - If
db_pathis omitted, default isratmail-<slug-account-name>.db imap.fetch_chunk_sizeis clamped to1..50
Operational guidance: keep initial_sync_days moderate for first sync speed, then use backfill
from the TUI (o) as needed for older history.
Render
Render settings control HTML-to-terminal image behavior. In rendered mode, Ratmail chooses geometry based on terminal dimensions and these values.
render.remote_images: bool, defaulttruerender.width_px: default800(runtime geometry can override)render.render_scale: default1.5, clamped0.25..4.0render.tile_height_px_side: default1000render.tile_height_px_focus: default60
If your terminal does not support image protocols (Kitty/Sixel), Ratmail still works in text mode and falls back gracefully.
Send
Send settings affect outgoing message formatting and generated HTML alternatives when sending from compose or CLI.
send.html: defaulttruesend.font_family: defaultArial, sans-serifsend.font_size_px: default14, clamped8..72
If you want plainest output for compatibility-sensitive recipients, set send.html = false.
UI
UI values tune layout density and interaction style. Theme normalization is strict; unknown names fall back
to default.
ui.folder_width_cols: default25, clamped8..40ui.compose_vim: defaultfalseui.themepresets:default,ratmail,nord,gruvbox,solarized-dark,solarized-light,dracula,catppuccin-mocha,catppuccin-latte,custom
Custom palette keys ([ui.palette]):
base_fg, base_bg, border, bar_fg, bar_bg, accent, warn, error, selection_bg, selection_fg, link, muted
Custom palette values must be hex colors (for example #88c0d0). Invalid color strings are
ignored for the specific palette key.
Spell
Spellcheck is backed by Hunspell dictionaries. You can control language and custom dictionary path here.
spell.lang: defaulten_USspell.dir: optional dictionary directoryspell.ignore: list of words, normalized to lowercase
Use spell.ignore for recurring names, project terms, or acronyms that should not generate
review noise during compose.
CLI
CLI configuration is split between enablement in ratmail.toml and signed policy content in the
blob file. Both are required for active CLI operations.
cli.enabled: defaultfalsecli.default_account: optional default CLI accountcli.policy.public_key_path: required when CLI enabledcli.policy.path: signed blob path overridecli.policy.state_path: anti-rollback state path override
If cli.enabled = true but policy verification fails, Ratmail disables CLI execution and
returns a JSON error explaining why.
TUI Guide
The TUI is optimized around quick keyboard workflows: browse folders, triage messages, open focused view, and compose without leaving the terminal.
Main modes:
- List/View panes
- Focus mode for full message scrolling
- Compose mode
- Overlays: search, links, attachments, spellcheck, bulk actions, confirmations
Primary keys
The help bar in-app shows compact bindings by default and expands with ?. Most list navigation
works in both Vim-style and arrow-key style.
Tab,h,l: move focus panesj/k: move selectionSpace: select message and advanceEnter: open message / open selected bulk actionsv: toggle rendered/text viewp: toggle preview pane/: search overlays: sync selected foldero: backfill older messages[/]: switch account tab?: toggle expanded helpq: quit
Behavior note: selecting with Space enables bulk actions. Press Enter with a
selection set to open bulk action overlay.
Compose
Compose supports attachments, spell tools, reply/forward helpers, and draft confirmation. Sending can be triggered from keyboard without leaving the compose overlay.
Ctrl+SorF5: sendCtrl+A: attach file pickerCtrl+R: remove last attachmentF7: spellcheck overlayCtrl+QorEsc: close compose (draft confirm if content exists)
Vim compose mode (ui.compose_vim = true):
- Body starts in Normal mode,
Esctoggles Insert/Normal - Normal mode supports movement/edit ops (
h j k l w b 0 $ x dd,i a A I o O)
When Vim compose mode is off, compose behaves as a standard text-input workflow with tab-cycle focus.
Search syntax
Search accepts mixed plain text and structured field filters in one query. Filters narrow results before rendering message lists.
- Text terms: match
from,subject, and preview from:,subject:,to:,date:since:,before:(dateparse-compatible dates)att:,file:,filename:for attachment namestype:,mime:for attachment MIME or extension
Examples:
from:alice subject:invoice since:2026-01-01
to:team@example.com att:report type:pdf
urgent deploy rollbackCLI Guide
The CLI is designed for agent/tool automation. Every response is machine-readable JSON with explicit success or failure fields.
CLI output is JSON, with schema ratmail.cli.v1.
{
"schema": "ratmail.cli.v1",
"ok": true,
"result": { ... }
} Errors:
{
"schema": "ratmail.cli.v1",
"ok": false,
"error": "..."
} Command tree
Read-only commands and mutation commands share one command tree. Actual permission to execute a command is determined by policy mode plus ACL allow/deny rules.
ratmail setup [--apply-system-policy]
ratmail lock
ratmail accounts list
ratmail folders list [--account <name>]
ratmail messages list [filters...]
ratmail message get --id <n> [--body] [--raw] [--attachments] [--fetch]
ratmail message body --id <n> [--fetch]
ratmail message raw --id <n> [--fetch]
ratmail message attachment-save --id <n> --index <i> --path <file> [--fetch]
ratmail message move --id <n> --folder <name>
ratmail message delete --id <n>
ratmail message mark --id <n> (--read|--unread)
ratmail sync [--account <name>] [--folder <name>] [--backfill] [--days <n>] [--wait] [--timeout-secs <n>]
ratmail send --to ... --subject ... --body ... [--cc ...] [--bcc ...] [--attach ...] [--wait] [--timeout-secs <n>]
ratmail acl edit|sign|apply ... --wait on send and sync blocks for completion events up to --timeout-secs. Without --wait, commands queue work and return quickly.
Message list filters
--unread,--limit,--folder,--account--from,--subject,--to,--date--querywith search syntax above--since/--beforeor--since-ts/--before-ts--attand--att-typeattachment filters
For best performance in automation, pass folder/account explicitly and keep --limit small on
polling loops.
Automation shortcut
Use string commands with --cmd, for example:
ratmail --cmd "messages list --account Personal --unread --limit 20" --cmd is parsed by Ratmail and then re-routed through normal command handling, so ACL checks
are identical to direct subcommands.
Agent Skills (Claude and Codex)
Ratmail ships skill assets under skills/ in the app repo. These are prompt/workflow packs
meant for agent tooling, not binary plugins loaded by Ratmail itself.
Direct GitHub sources:
Install for Codex/OpenAI-style skills
If your Codex environment reads skills from $CODEX_HOME/skills, fetch directly from GitHub.
For default local setups, use ~/.codex/skills:
mkdir -p "$HOME/.codex/skills/openai/ratmail-cli/agents"
curl -L "https://raw.githubusercontent.com/peter-fm/ratmail/main/skills/openai/ratmail-cli/SKILL.md" \
-o "$HOME/.codex/skills/openai/ratmail-cli/SKILL.md"
curl -L "https://raw.githubusercontent.com/peter-fm/ratmail/main/skills/openai/ratmail-cli/agents/openai.yaml" \
-o "$HOME/.codex/skills/openai/ratmail-cli/agents/openai.yaml" Verify:
ls -R "$HOME/.codex/skills/openai/ratmail-cli" Install for Claude-style skills
Claude clients vary by host/editor. If your setup supports a local filesystem skill folder, fetch the same way:
mkdir -p "$HOME/.claude/skills"
mkdir -p "$HOME/.claude/skills/ratmail-cli"
curl -L "https://raw.githubusercontent.com/peter-fm/ratmail/main/skills/claude/ratmail-cli/SKILL.md" \
-o "$HOME/.claude/skills/ratmail-cli/SKILL.md" If your Claude client uses a different path, copy the same ratmail-cli folder into that
client-specific skills directory.
Usage note
These skills describe how an agent should operate the Ratmail CLI. They do not bypass Ratmail ACL policy. All real command permissions are still enforced by your signed CLI policy at runtime.
The published skill files are aligned with the signed-policy CLI model.
ACL and Policy
When [cli].enabled = true, Ratmail does not trust ACL values inline in ratmail.toml.
It requires a signed policy blob verified against [cli.policy].public_key_path.
This separation is intentional: local user config can remain easy to edit, while enforcement-critical policy is signed and promoted through an explicit privileged workflow.
Policy mode
readonly: no mutations; restricted fields unless explicitly allowedreadonly-full-access: read all fields including body/raw/attachmentsfull-access: allows mutations unless ACL flags/command rules deny
ACL fields
accounts, folders, from_allow, fields, allow_body, allow_raw, allow_attachments, allow_commands, deny_commands, allow_move, allow_delete, allow_mark, allow_send.
Pattern matching supports wildcard * (case-insensitive).
Rule precedence is deny-first for commands: if a command matches a deny pattern, it is blocked even if an allow pattern is broad.
In readonly, mutation commands are blocked regardless of allow lists. In full-access, mutation still depends on ACL toggles like allow_send and allow_delete.
Command IDs for allow/deny
accounts.list, folders.list, messages.list, message.get, message.body, message.raw, message.attachment.save, message.move, message.delete, message.mark, sync, send.
Signed policy workflow
- Draft/edit TOML policy (
ratmail acl editor setup-generated draft) - Sign policy:
ratmail acl sign --policy-toml ... --secret-key ... --out ... - Apply policy:
ratmail acl apply --policy ... - Or use
sudo ratmail lockto auto-sign and apply from draft
Policy versions should increase monotonically. Applying an older version is rejected by anti-rollback state unless you explicitly allow downgrade in manual apply flow.
ratmail lock also prints a human-readable review comparing current and incoming risky
permissions before confirmation.
Default system paths:
- Linux policy blob:
/etc/ratmail/config.blob - macOS policy blob:
/Library/Application Support/ratmail/config.blob - Linux public key:
/etc/ratmail/policy-public.pem - macOS public key:
/Library/Application Support/ratmail/policy-public.pem - Linux private key:
/var/lib/ratmail/keys/policy-private.pem - macOS private key:
/Library/Application Support/ratmail/keys/policy-private.pem - Linux state file:
/var/lib/ratmail/cli-policy-state.json - macOS state file:
/Library/Application Support/ratmail/cli-policy-state.json
These defaults can be overridden in [cli.policy] for custom deployment layouts.
Security Model
Ratmail CLI hardening is local-policy enforcement for agent workflows on a single machine. It reduces risk, but does not replace endpoint hardening or server-side controls.
- Policy signatures are verified with OpenSSL Ed25519.
- Policy versions are monotonic; rollback is rejected when version decreases.
ratmail lockrequires root/admin privileges.- ACL is meaningful for non-privileged agents; root-level compromise can bypass local controls.
Operationally, keep agent processes unprivileged and treat any request for privileged escalation as a manual review checkpoint.
Troubleshooting
Most CLI failures are configuration or policy state issues. Start by checking that config paths, key paths, and policy version are all aligned.
- CLI disabled: set
[cli].enabled = trueand configure signed policy + public key path. - Missing public key: ensure
[cli.policy].public_key_pathexists and is readable. - Policy verification failed: re-sign policy with matching private/public key pair.
- Rollback detected: increase policy
versionbefore applying. - Attachment/body unavailable in CLI: allow the related ACL flags and command IDs.
- No rendered HTML: terminal image protocol support is required for rendered mode.
If needed, rerun setup to regenerate defaults, then re-apply policy with sudo ratmail lock to
restore a known-good baseline.