IMAP email client exposed as both a CLI and an MCP (Model Context Protocol) server, built with Rust.
One binary: agentmail serve starts the MCP stdio server, all other subcommands are a direct CLI.
See also: DESIGN.md for architecture diagrams and design decisions, MCP.md for the full MCP tool & prompt reference with output schemas.
- Rust toolchain (edition 2024)
- An IMAP-enabled email account (Gmail, Outlook, Fastmail, self-hosted, etc.)
cargo build --releaseOutput binary: target/release/agentmail
agentmail reads its config from a single TOML file:
| Location | Path |
|---|---|
| Default | ~/.config/agentmail/config.toml |
| Override | Set the AGENTMAIL_CONFIG environment variable to any path |
On macOS the default expands to ~/Library/Application Support/agentmail/config.toml if dirs::config_dir() returns Library/Application Support, but ~/.config/agentmail/config.toml is more conventional and works fine — just pick one.
The fastest way to add an account is the interactive configure command:
# With a provider preset (gmail, icloud, outlook, fastmail, yahoo)
agentmail configure gmail
# Or fully custom
agentmail configureThis prompts for your username, password method, writes the config file, and tests the connection.
[accounts.personal]
host = "imap.gmail.com"
username = "you@gmail.com"
password.keyring = "you@gmail.com"Add as many [accounts.<name>] sections as you like. Each is a fully independent IMAP connection with its own credentials and settings. You do not need separate config files or multiple server instances.
[accounts.gmail]
host = "imap.gmail.com"
username = "you@gmail.com"
password.keyring = "you@gmail.com"
[accounts.icloud]
host = "imap.mail.me.com"
username = "johnappleseed"
password.cmd = "security find-internet-password -s imap.mail.me.com -a johnappleseed -w"
[accounts.work]
host = "imap.company.com"
username = "you@company.com"
password.cmd = "op read op://Work/Email/password"
trash_mailbox = "Trash"
drafts_mailbox = "Drafts"All accounts are available simultaneously — the MCP tools and CLI commands accept an account parameter to select which one to operate on.
| Field | Type | Default | Description |
|---|---|---|---|
host |
string | required | IMAP server hostname |
port |
u16 | 993 |
IMAP port |
username |
string | required | Login username / email |
password |
Secret | — | Password source (see Passwords below) |
tls |
bool | true |
Use TLS |
trash_mailbox |
string | auto-detect / "Trash" |
Trash folder name override |
drafts_mailbox |
string | auto-detect / "Drafts" |
Drafts folder name override |
agentmail uses secret-lib for flexible credential management. The password field supports three sources:
Shell command (recommended for reusing existing credentials):
# Read from Apple Mail / macOS Keychain internet passwords
password.cmd = "security find-internet-password -s imap.mail.me.com -a johnappleseed -w"
# Read from pass (Unix password manager)
password.cmd = "pass show email/gmail"
# Read from 1Password CLI
password.cmd = "op read op://Personal/Gmail/password"
# Read from Bitwarden CLI
password.cmd = "bw get password gmail-imap"The command is executed at connection time and the first line of stdout is used as the password. This is the most flexible option — it works with any password manager that has a CLI.
System keyring (recommended for standalone use):
password.keyring = "you@gmail.com"Stores and retrieves from the system credential store (macOS Keychain, Windows Credential Manager, Linux Secret Service). The value is the keyring entry key; the service name is "agentmail". Store a password with:
agentmail set-password --account gmailRaw string (not recommended — plaintext in config file):
password.raw = "hunter2"macOS Mail stores IMAP passwords as internet password items in the Keychain. You can read them directly using password.cmd:
[accounts.icloud]
host = "imap.mail.me.com"
username = "johnappleseed"
password.cmd = "security find-internet-password -s imap.mail.me.com -a johnappleseed -w"This shells out to security at connection time, which reads Apple Mail's stored password. The first time you run this, macOS may prompt you to allow keychain access.
To find the correct server and account values for your setup:
# List all internet passwords for iCloud Mail
security find-internet-password -s "imap.mail.me.com"
# List for Gmail
security find-internet-password -s "imap.gmail.com"When connecting, agentmail tries these sources in order and uses the first one found:
AGENTMAIL_PASSWORD_<ACCOUNT>environment variable (override for CI/Docker)passwordfield in config (command, keyring, or raw)- Default keyring lookup under
"agentmail"service with username as key (backward compat forset-passwordusers with nopasswordfield)
For CI, Docker, or headless servers, passwords can be passed via environment variables regardless of what's in the config file:
export AGENTMAIL_PASSWORD_GMAIL="app-specific-password"
export AGENTMAIL_PASSWORD_WORK="your-password"The variable name is AGENTMAIL_PASSWORD_ followed by the account name uppercased, with dashes and spaces replaced by underscores.
# 1. Check that the account appears in the config
agentmail list-accounts
# 2. Test IMAP connectivity and authentication
agentmail check-connection --account gmail
# 3. List mailboxes to confirm full access
agentmail list-mailboxes --account gmailGmail requires an App Password (not your regular Google account password). Generate one, then:
[accounts.gmail]
host = "imap.gmail.com"
username = "you@gmail.com"
password.keyring = "you@gmail.com"agentmail set-password --account gmail
# paste the 16-character app passwordiCloud uses your Apple ID with an app-specific password. The IMAP login is your iCloud username (not full email):
[accounts.icloud]
host = "imap.mail.me.com"
username = "johnappleseed"
password.keyring = "johnappleseed"Or reuse the password Apple Mail already stored in the Keychain:
[accounts.icloud]
host = "imap.mail.me.com"
username = "johnappleseed"
password.cmd = "security find-internet-password -s imap.mail.me.com -a johnappleseed -w"If you're upgrading from a version that used keychain_service or password = "...":
keychain_servicehas been removed. Usepassword.keyring = "your-username"instead. Passwords previously stored viaset-passwordare still found automatically (backward compat fallback).password = "plaintext"still works but is treated aspassword.raw = "plaintext"internally.
agentmail serveStarts an MCP stdio server. Logs go to stderr; JSON-RPC on stdin/stdout.
agentmail configure gmail # interactive account setup
agentmail configure # interactive setup (custom provider)
agentmail list-accounts
agentmail list-mailboxes --account gmail
agentmail create-mailbox --account gmail --name "Archive/2024"
agentmail check-connection --account gmail
agentmail list-capabilities --account gmail
agentmail set-password --account gmail
agentmail get-messages --account gmail --mailbox INBOX --limit 10
agentmail get-messages-by-uid --account gmail --uids 123 456
agentmail rank-senders --account gmail --limit 20
agentmail rank-unsubscribe --account gmail --limit 20
agentmail find-attachments --account gmail
agentmail download-attachments --account gmail --uid 123 --output-dir ./downloads
agentmail list-flags --account gmail
agentmail add-flags --account gmail --uid 123 --flags "\\Seen" --color red
agentmail create-draft --account gmail --subject "Hello" --body "Hi there" --to user@example.comFull subcommand list: agentmail --help
Add to your MCP client config (Claude Desktop, Claude Code, etc.):
{
"mcpServers": {
"agentmail": {
"command": "/path/to/agentmail",
"args": ["serve"]
}
}
}To pass passwords via environment variables instead of keychain:
{
"mcpServers": {
"agentmail": {
"command": "/path/to/agentmail",
"args": ["serve"],
"env": {
"AGENTMAIL_PASSWORD_GMAIL": "your-app-password"
}
}
}
}21 tools covering account discovery, mailbox management, message reading, search, bulk operations, flag management, and composition.
| Tool | Description |
|---|---|
list_accounts |
Return configured account names (use this first) |
list_mailboxes |
List mailboxes with message counts (total, unseen, recent) |
create_mailbox |
Create a new mailbox (folder) on the server |
check_connection |
Test IMAP connectivity for an account |
list_capabilities |
List IMAP server capabilities (IDLE, MOVE, etc.) |
get_messages |
Paginated message fetch, newest-first by UID |
search_messages |
IMAP SEARCH with text, header, sender, subject, and status filters |
list_flags |
List all flags in use with counts; resolves Apple Mail color flags |
rank_senders |
Rank senders by message count across one or all mailboxes |
rank_unsubscribe |
Rank bulk-mail senders by List-Unsubscribe presence, sorted by one-click support |
rank_list_id |
Rank mailing lists by List-Id header (RFC 2919), groups regardless of sender |
find_attachments |
Scan for messages with attachments (multipart/mixed or multipart/related) |
download_attachments |
Download attachments from a message to disk |
delete_messages |
Delete messages by UID (up to 500 per call, moves to Trash or expunges) |
delete_by_sender |
Delete all messages from a sender identified by UID, optionally across all mailboxes |
delete_list_id |
Delete all messages with a specific List-Id across all mailboxes |
move_message |
Move a message between mailboxes via IMAP MOVE |
create_draft |
Compose RFC822 draft and append to Drafts folder |
unsubscribe_message |
RFC 8058 one-click unsubscribe, optionally delete matching bulk mail across all boxes |
add_flags |
Add flags and/or set Apple Mail color on a message (union semantics) |
remove_flags |
Remove flags and/or clear Apple Mail color from a message |
accountis required for most tools. Uselist_accountsto discover valid names.mailboxdefaults toINBOXwhen omitted. Omit it onrank_senders,rank_unsubscribe,rank_list_id,list_flags, andfind_attachmentsto scan the entire account (auto-skips Trash, Junk, Spam, Drafts).limitdefaults to 25, clamped to 1..50.includeContent(default false) returns normalized markdown body text, trimmed for context window safety.- All reads use
BODY.PEEKto avoid marking messages as\Seen. - Long-running operations (
rank_senders,rank_unsubscribe,rank_list_id,find_attachments,list_flags,delete_messages) support MCP progress notifications.
6 prompts provide guided conversation starters for common email workflows:
| Prompt | Description |
|---|---|
inbox-summary |
Get a comprehensive inbox overview: folder structure, top senders, unread messages |
cleanup-sender |
Find and bulk-delete all emails from a specific sender (with preview) |
find-attachments |
Scan a mailbox for messages with attachments and list for download |
compose-email |
Guided email draft composition |
unsubscribe-cleanup |
Identify high-volume mailing lists, unsubscribe and bulk-delete |
list-id-cleanup |
Identify mailing lists by List-Id and bulk-delete entire lists |
agentmail (binary crate: agentmail-mcp)
├── serve → MCP stdio server (tokio + rmcp)
│ 21 tools + 6 prompts, progress notifications
├── list-accounts → CLI
├── list-mailboxes → CLI
├── create-mailbox → CLI
├── check-connection → CLI
├── list-capabilities → CLI
├── get-messages → CLI
├── get-messages-by-uid → CLI
├── rank-senders → CLI
├── rank-unsubscribe → CLI
├── find-attachments → CLI
├── download-attachments → CLI
├── list-flags → CLI
├── add-flags → CLI (flags + Apple Mail colors)
├── create-draft → CLI
├── set-password → CLI (keychain store)
└── configure → CLI (interactive account setup)
crates/agentmail (library)
├── config.rs → TOML config loading, default account resolution
├── credentials.rs → Password resolution (env → secret-lib → keyring fallback)
├── connection.rs → IMAP connection pool (3 idle sessions/account)
├── imap_client.rs → IMAP operations (fetch, search, delete, move, create, sync)
├── parser.rs → RFC822 → MessageInfo (via mail-parser), attachment extraction
├── draft.rs → RFC822 composition (via lettre)
├── content.rs → HTML→markdown conversion, context window trimming
├── provider.rs → Email provider presets (Gmail, iCloud, Outlook, Fastmail, Yahoo)
├── types.rs → Shared data structures (MessageInfo, MailboxInfo, etc.)
├── error.rs → Error types
└── lib.rs → Public API facade
Connection pooling: Each account maintains up to 3 idle IMAP sessions. Sessions are validated with NOOP before reuse and replaced when stale. Credentials are resolved on-demand when a new connection is needed.
Post-mutation sync: All mutating operations (delete, move, create draft, create mailbox) issue a NOOP after the operation to flush pending server-side state before releasing the session back to the pool.
- Run
agentmail check-connection --account <name>to test connectivity. - Verify your password:
agentmail set-password --account <name>to re-store it. - Gmail users: ensure you're using an App Password, not your Google account password.
- Check that your IMAP server allows external clients (some providers disable IMAP by default).
- If the MCP server appears empty in Inspector, call
initializefirst, thentools/list.