CLI for searching, managing, drafting, and bulk-operating on emails via IMAP.
cargo install slashmailDownload a prebuilt binary from Releases, extract it, and place it on your PATH.
Requires Rust and a C compiler (for OpenSSL bindings).
git clone https://github.com/mwmdev/slashmail.git
cd slashmail
cargo build --release
cp target/release/slashmail ~/.local/bin/ # or anywhere on your PATHIf OpenSSL cannot be discovered on your system, build with
cargo build --release --features vendored-openssl.
| OS | Prerequisites |
|---|---|
| macOS | Xcode Command Line Tools (xcode-select --install) |
| Debian/Ubuntu | apt install build-essential pkg-config libssl-dev |
| Fedora/RHEL | dnf install gcc pkg-config openssl-devel |
| Arch | pacman -S base-devel openssl |
| NixOS | nix-shell (uses included shell.nix) |
| Windows | Install Rust via rustup, uses vendored OpenSSL |
slashmail [OPTIONS] <COMMAND>
Commands:
draft Save a new unsent email draft
reply Save an unsent reply draft for one message UID
attachments List or save attachments from one message UID
search Search messages by criteria
read Display the content of matching messages
delete Search + delete matching messages (move to Trash)
move Search + move matching messages to a folder
export Search + export matching messages as .eml files
mark Search + set/unset flags on matching messages
count Count matching messages (no FETCH)
quota Show mailbox quota usage
status Show per-folder message statistics
--host <HOST> IMAP host [default: 127.0.0.1]
--port <PORT> IMAP port [default: 1143 plain, 993 TLS]
--tls Use TLS (required for remote IMAP servers)
-u, --user <USER> IMAP username (or SLASHMAIL_USER env)
--account <NAME> Use a named account from config
--all-accounts Query all configured accounts (read-only commands only)
For direct/legacy connections, the password is read from SLASHMAIL_PASS or prompted interactively. Named accounts use the environment variable named by their pass_env setting.
When a config file is loaded, slashmail automatically loads .env from the same directory. Values already present in the process environment take precedence. A missing .env is ignored; an unreadable or malformed one is an error. Because .env contains plaintext secrets, keep it out of version control and readable only by your user account.
draft and reply are different because stdin is reserved for the message body: they never prompt for a password. Direct/legacy use requires a nonempty SLASHMAIL_PASS. A named account must configure pass_env, and that variable must be nonempty in the process environment or adjacent .env. Slashmail checks credentials before reading stdin, so a missing password cannot consume a piped draft body.
Connection options are global and can appear before or after the subcommand.
Settings can be stored in a config file to avoid repeating connection options:
| OS | Path |
|---|---|
| Linux | ~/.config/slashmail/config.toml |
| macOS | ~/Library/Application Support/slashmail/config.toml |
| Windows | %APPDATA%\slashmail\config.toml |
Single-account config.toml:
host = "imap.gmail.com"
port = 993
tls = true
user = "[email protected]"
sender = "User Example <[email protected]>"
drafts_folder = "[Gmail]/Drafts"
trash_folder = "[Gmail]/Trash"
default_folder = "INBOX"All single-account fields are optional. CLI arguments and environment variables take precedence over these top-level config values.
Multi-account config.toml:
default_account = "personal"
[[accounts]]
name = "personal"
host = "imap.gmail.com"
port = 993
tls = true
user = "[email protected]"
pass_env = "SLASHMAIL_PERSONAL_PASS"
sender = "Personal User <[email protected]>"
drafts_folder = "[Gmail]/Drafts"
trash_folder = "[Gmail]/Trash"
default_folder = "INBOX"
[[accounts]]
name = "work"
host = "imap.fastmail.com"
port = 993
tls = true
user = "[email protected]"
pass_env = "SLASHMAIL_WORK_PASS"
sender = "Work User <[email protected]>"
drafts_folder = "Drafts"
default_folder = "INBOX"Create .env beside config.toml with the variables named by each account's pass_env:
SLASHMAIL_PERSONAL_PASS=your-personal-password
SLASHMAIL_WORK_PASS=your-work-passwordWhen [[accounts]] is configured, slashmail uses default_account by default, or the first account if default_account is omitted. Use --account <NAME> to select one account, or --all-accounts to aggregate read-only commands across every account.
--all-accounts is supported for search, read, count, status, and quota. Mutating commands (delete, move, mark), export, drafts, replies, and received-attachment inspection require a single account.
Use --config <PATH> to specify an alternative config file location.
sender and drafts_folder are optional at both the top level and inside an [[accounts]] entry. An account value takes precedence over the top-level value. Draft composition falls back to user only when it is a valid email mailbox; configure sender when the IMAP login is not an email address.
The draft destination is resolved in this order: command --drafts-folder, the selected account's resolved drafts_folder (account value, then top-level value), then exactly one selectable server mailbox marked \Drafts. Slashmail fails without saving if the chosen override is invalid or server discovery finds zero or multiple valid Drafts mailboxes.
draft and reply save unsent messages with the IMAP \Draft flag; they do
not send mail. The body is read from stdin and is plain text unless --html
is used. Repeat --to, --cc, --bcc, or --attach to add multiple
recipients or local files.
# Create a draft with an attachment
printf '%s\n' 'Please review the attached proposal.' |
slashmail draft --account work \
--to [email protected] \
--subject "Proposal" \
--attach './documents/client proposal.pdf'
# Reply to UID 1842 without quoting the original message
printf '%s\n' 'Thanks, this looks good to me.' |
slashmail reply --account work --no-quote 1842
# Create an HTML draft
printf '%s\n' '<p>Please review the <strong>proposal</strong>.</p>' |
slashmail draft --html --to [email protected] --subject "Proposal"Replies use reply-all behavior, exclude the configured sender, preserve
available thread metadata, and quote the original by default. Use --folder
to select the source folder and --no-quote to omit the quote. The source
message remains unchanged.
Each --attach value must name a local regular file. Globs, directories, and
URLs are not supported, and attachments from the original message are not
copied into replies. Files are loaded into memory while the MIME message is
built, so large attachments may be limited by available memory or the mailbox
provider.
On success, slashmail prints the account, Drafts folder, UID, recipients, and subject. This receipt may contain Bcc addresses, so avoid copying it into public logs. If the APPEND outcome is reported as unknown, inspect the Drafts folder before retrying to avoid creating a duplicate.
attachments <UID> lists attachments from one message without marking it as
seen. The output includes MIME part IDs, filenames, content types, and decoded
sizes; add --json for machine-readable output.
# List attachments
slashmail attachments --account work 1842
# Save every attachment
slashmail attachments --account work --save \
--output-dir './received files' 1842
# Save selected MIME parts
slashmail attachments --account work --save \
--part 2.1 --part 3 --output-dir './received files' 1842Saving aborts before writing anything if a destination already exists. Add
--force to replace existing files. Filenames are sanitized and kept inside
the output directory. Only parts declared as attachments are exposed;
inline/CID parts and attachments nested inside another attached message are
not extracted.
Search, read, count, and bulk message commands share these filter options:
-f, --folder <FOLDER> Folder to search [default: INBOX]
--all-folders Search across all folders (excludes Trash, Spam)
--subject <TEXT> Subject contains
--from <TEXT> From address contains
--to <TEXT> To address contains
--cc <TEXT> CC address contains
--body <TEXT> Message body contains
--text <TEXT> Headers or body contains
--seen Only read messages
--unseen Only unread messages
--since <DATE> Messages since date (YYYY-MM-DD or 7d, 2w, 3m, 1y)
--before <DATE> Messages before date (YYYY-MM-DD or 7d, 2w, 3m, 1y)
--larger <SIZE> Messages larger than N bytes (supports K/M suffix)
--smaller <SIZE> Messages smaller than N bytes (supports K/M suffix)
--flagged Only flagged/starred messages
--unflagged Only unflagged messages
--answered Only replied-to messages
--draft Only draft messages
-n, --limit <N> Limit number of results
All filter criteria are AND'd together. Omitting all criteria matches all messages.
Commands that modify messages (delete, move, mark) support:
--yes Skip confirmation prompt
--dry-run Show what would happen without acting
delete also supports --trash-folder <NAME> (default: Trash) for servers that use a different name (e.g. Deleted Items, [Gmail]/Trash).
export supports --yes, --force (overwrite existing files), and -o, --output-dir.
mark takes one or more actions: --read, --unread, --set-flagged, --clear-flagged.
# Search INBOX (all messages, newest first)
slashmail search -u [email protected]
# Search with filters
slashmail search -u [email protected] --from "newsletter" --since 2025-01-01
slashmail search -u [email protected] --subject "invoice" --larger 1M
# Relative dates: last 7 days, 2 weeks, 3 months, 1 year
slashmail search -u [email protected] --since 7d
slashmail search -u [email protected] --since 3m --before 1m
# Show only the 10 most recent matches
slashmail search -u [email protected] --from "alerts" -n 10
# Filter by recipient or CC
slashmail search -u [email protected] --to "[email protected]"
slashmail search -u [email protected] --cc "[email protected]"
# Show only unread messages
slashmail search -u [email protected] --unseen --since 7d
# Search message body content
slashmail search -u [email protected] --body "invoice attached"
# Search everywhere (headers + body)
slashmail search -u [email protected] --text "quarterly report"
# JSON output for scripting (search and count only)
slashmail search -u [email protected] --from "alerts" --json | jq '.[].subject'
slashmail count -u [email protected] --json
# Search across all folders
slashmail search -u [email protected] --all-folders --from "noreply"
# Search across all configured accounts
slashmail search --all-accounts --from "newsletter"
slashmail read --all-accounts --subject "invoice" -n 3
# Use one named account from config
slashmail count --account work --unseen
# Delete with interactive confirmation
slashmail delete -u [email protected] --from "[email protected]"
# Batch delete (no prompt)
slashmail delete -u [email protected] --subject "unsubscribe" --yes
# Preview what would be deleted
slashmail delete -u [email protected] --from "old-list" --dry-run
# Move messages to a folder
slashmail move -u [email protected] --from "receipts" --to Archive
# Export messages as .eml files
slashmail export -u [email protected] --subject "contract" -o ./backup
# Mark messages as read
slashmail mark -u [email protected] --from "notifications" --read
# Flag important messages
slashmail mark -u [email protected] --subject "urgent" --set-flagged
# Count matching messages (fast, no FETCH)
slashmail count -u [email protected] --from "newsletter"
# Show folder statistics
slashmail status -u [email protected]
# Show mailbox quota
slashmail quota -u [email protected]
# Use with a remote IMAP server (Gmail, Fastmail, etc.)
slashmail search --tls --host imap.gmail.com -u [email protected]
# Use env vars to avoid typing credentials
export [email protected]
export SLASHMAIL_PASS=app-password
slashmail status# Bash
slashmail completions bash > ~/.local/share/bash-completion/completions/slashmail
# Zsh
slashmail completions zsh > ~/.zfunc/_slashmail
# Fish
slashmail completions fish > ~/.config/fish/completions/slashmail.fishslashmail includes a skill file (skills/slashmail/SKILL.md) that teaches AI agents how to manage your email through natural language.
Claude Code — copy the skill into your skills directory:
mkdir -p ~/.claude/skills/slashmail
cp skills/slashmail/SKILL.md ~/.claude/skills/slashmail/Codex — copy the skill into the shared agent skills directory:
mkdir -p ~/.agents/skills/slashmail
cp skills/slashmail/SKILL.md ~/.agents/skills/slashmail/Other agents — paste the contents of skills/slashmail/SKILL.md into your agent's system prompt or tool definitions.
Once installed, prompts like these just work:
> Check my latest emails
> Read the last email from Sarah
> Find emails about the quarterly report
> How many unread messages do I have?
> Show me large emails over 5MB from the last month
> Delete all newsletters from [email protected] older than 3 months
> Move flagged emails from last week to the Archive folder
> Export all invoices from 2025 to a backup folder
> Search my sent folder for emails to the finance team
> Draft a plain-text email to Sarah with subject "Project update"
> Save an HTML reply to message UID 1842 without quoting the original
Destructive operations always dry-run first and ask for confirmation.
- Gmail (via
--tls --host imap.gmail.com) - Fastmail (via
--tls --host imap.fastmail.com) - Dovecot
- Any standard IMAP4rev1 server
- All filtering runs server-side via IMAP SEARCH
- Uses IMAP SORT extension (RFC 5256) when available; falls back to client-side sort
- With SORT,
--limittruncates results before fetching (fewer bytes over the wire) search,delete,move,mark,countonly fetch headers and size -- never full messagesexportfetches full message bodies viaBODY.PEEK[]- Uses
BODY.PEEKto avoid marking messages as read - UID sets are compressed into ranges and chunked to stay within IMAP command length limits
- Passwords are securely zeroed from memory after login
0— Success1— Error (connection failure, invalid credentials, bad arguments, etc.)
All errors print to stderr. Combine --yes with cron or scripts for unattended operation.
- Verify host and port: ProtonMail Bridge uses
127.0.0.1:1143, Gmail usesimap.gmail.com:993 --tls - Check that the IMAP server is running and the port is not blocked by a firewall
- Gmail and Outlook require App Passwords, not your account password
- ProtonMail Bridge: use the bridge-generated password, not your ProtonMail account password
- Fastmail: use an app-specific password from Settings → Privacy & Security
- Run
slashmail statusto list all available folders and their names - Folder names are case-sensitive on most IMAP servers
- Gmail uses
[Gmail]/Trash,[Gmail]/All Mail, etc. — use--trash-folderwithdeleteif needed - Exchange/Outlook uses
Deleted Itemsinstead ofTrash
- Use
--tlsfor all remote (non-localhost) IMAP servers - If you get certificate errors, ensure your system CA certificates are up to date