Skip to content

Repository files navigation

slashmail

CI Crates.io MSRV Crate Size License

CLI for searching, managing, drafting, and bulk-operating on emails via IMAP.

Install

From crates.io

cargo install slashmail

From GitHub Releases

Download a prebuilt binary from Releases, extract it, and place it on your PATH.

From source

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 PATH

If OpenSSL cannot be discovered on your system, build with cargo build --release --features vendored-openssl.

Platform notes

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

Usage

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

Connection options

--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.

Config file

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-password

When [[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.

Email drafts

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.

Received attachments

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' 1842

Saving 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.

Filter options

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.

Action options

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.

Examples

# 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

Shell completions

# Bash
slashmail completions bash > ~/.local/share/bash-completion/completions/slashmail

# Zsh
slashmail completions zsh > ~/.zfunc/_slashmail

# Fish
slashmail completions fish > ~/.config/fish/completions/slashmail.fish

AI Agent Skill

slashmail 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.

Tested with

  • Gmail (via --tls --host imap.gmail.com)
  • Fastmail (via --tls --host imap.fastmail.com)
  • Dovecot
  • Any standard IMAP4rev1 server

How it works

  • All filtering runs server-side via IMAP SEARCH
  • Uses IMAP SORT extension (RFC 5256) when available; falls back to client-side sort
  • With SORT, --limit truncates results before fetching (fewer bytes over the wire)
  • search, delete, move, mark, count only fetch headers and size -- never full messages
  • export fetches full message bodies via BODY.PEEK[]
  • Uses BODY.PEEK to 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

Exit codes

  • 0 — Success
  • 1 — Error (connection failure, invalid credentials, bad arguments, etc.)

All errors print to stderr. Combine --yes with cron or scripts for unattended operation.

Troubleshooting

Connection refused

  • Verify host and port: ProtonMail Bridge uses 127.0.0.1:1143, Gmail uses imap.gmail.com:993 --tls
  • Check that the IMAP server is running and the port is not blocked by a firewall

Login failed

  • 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

Folder not found

  • Run slashmail status to 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-folder with delete if needed
  • Exchange/Outlook uses Deleted Items instead of Trash

TLS errors

  • Use --tls for all remote (non-localhost) IMAP servers
  • If you get certificate errors, ensure your system CA certificates are up to date

About

CLI for reading, writing, searching, managing, and bulk-operating on emails via IMAP.

Topics

Resources

Contributing

Security policy

Stars

3 stars

Watchers

0 watching

Forks

Releases

Used by

Contributors

Languages