Install
openclaw skills install claw-mailMulti-account email management skill for IMAP/SMTP. Fetches, reads, searches, composes, sends, replies, forwards, and organizes emails across multiple accoun...
openclaw skills install claw-mailYou are an email management agent with multi-account IMAP/SMTP support. You can fetch, read, search, process, compose, send, reply, forward, move, and manage emails, drafts, and folders across multiple email accounts.
--account uses the default automatically.archive_mail.py and the heartbeat honor per-account
archive_root/archive_frequency defaults so messages routed to the archive
action land in folders such as Archive-202603, Archive-W09, or Archive-20260315.op://vault/item/field), macOS Keychain (keychain://service/account),
and environment variables (env://VAR_NAME).All scripts are in the scripts/ directory. Run with
python3 scripts/<name>.py from the skill root. Every script accepts
--account <name> to target a specific account.
| Script | Purpose |
|---|---|
scripts/fetch_mail.py | Fetch emails from an IMAP folder |
scripts/read_mail.py | Read/render an email by Message-ID; save attachments to disk |
scripts/search_mail.py | Search emails by subject, sender, body, date, flags |
scripts/send_mail.py | Send rich HTML emails via SMTP (Outbox + fallback); attach files |
scripts/compose_mail.py | Compose rich HTML emails from templates; attach files |
scripts/reply_mail.py | Reply to an email with original-message quoting |
scripts/forward_mail.py | Forward an email inline-quoted or with attachments |
scripts/draft_mail.py | Save, list, resume, or send drafts via IMAP Drafts folder |
scripts/process_mail.py | Run emails through the rule-based processing pipeline |
scripts/manage_folders.py | List, create, delete, rename, and move IMAP folders |
scripts/move_mail.py | Move emails between IMAP folders (batch support) |
scripts/heartbeat.py | Run a full heartbeat cycle (drains Outbox, fetches, processes) |
scripts/idle_monitor.py | Monitor a mailbox via IMAP IDLE (push notifications) |
scripts/retry_send.py | Retry sending messages stuck in the IMAP Outbox |
scripts/calendar_invite.py | Compose and send iCalendar meeting invitations |
scripts/mail_merge.py | Batch personalised sends from template + CSV/JSON data |
scripts/thread_mail.py | Group messages into conversation threads |
scripts/archive_mail.py | Auto-archive old messages into dated folders (daily/weekly/monthly/yearly) |
| Module | Purpose |
|---|---|
scripts/lib/imap_client.py | IMAP client with IDLE, search, folder management, TLS 1.2+ |
scripts/lib/smtp_client.py | SMTP client with TLS 1.2+, RFC 5322, OAuth2, MIME building |
scripts/lib/composer.py | Rich HTML email composer with templates, reply, forward |
scripts/lib/processor.py | Rule-based processing pipeline with webhook actions |
scripts/lib/account_manager.py | Multi-account manager with SMTP fallback and Outbox |
scripts/lib/outbox.py | IMAP Outbox — temporary folder for reliable delivery |
scripts/lib/credential_store.py | Secure credential storage (1Password, Keychain, env) |
scripts/lib/pool.py | Connection pool for IMAP/SMTP reuse |
scripts/lib/send_queue.py | Legacy file-backed send queue (superseded by Outbox) |
scripts/lib/smime.py | S/MIME signing and encryption |
scripts/lib/oauth2.py | OAuth2 (XOAUTH2) token management |
scripts/lib/models.py | Data models (EmailMessage, EmailAddress, etc.) |
| Reference | When to read |
|---|---|
references/REFERENCE.md | API overview, all script arguments and output formats |
references/TEMPLATES.md | Available email templates and template variables |
references/RULES.md | How to configure processing rules |
ROADMAP.md | Feature roadmap and progress tracker |
python3 scripts/fetch_mail.py --config config.yaml
python3 scripts/fetch_mail.py --account personal --unread-only --format cli --config config.yaml
Messages are staged in a temporary IMAP Outbox folder, sent via SMTP (with automatic fallback), then removed from Outbox on success.
python3 scripts/send_mail.py \
--to "recipient@example.com" \
--subject "Weekly Report" \
--body "<p>Here are this week's results.</p>" \
--template default \
--attach report.pdf \
--config config.yaml
python3 scripts/reply_mail.py --message-id "<id@example.com>" --body "Thanks!" --config config.yaml
python3 scripts/forward_mail.py --message-id "<id@example.com>" --to "colleague@x.com" --config config.yaml
python3 scripts/search_mail.py --subject "invoice" --unseen --config config.yaml
python3 scripts/search_mail.py --criteria '(FROM "alice@x.com" SINCE 01-Jan-2026)' --config config.yaml
python3 scripts/draft_mail.py --action save --to "user@x.com" --subject "WIP" --body "..." --config config.yaml
python3 scripts/draft_mail.py --action list --format cli --config config.yaml
python3 scripts/draft_mail.py --action send --message-id "<draft@x.com>" --config config.yaml
python3 scripts/retry_send.py --config config.yaml
python3 scripts/retry_send.py --config config.yaml --list
The heartbeat drains each account's Outbox, then fetches and processes mail:
python3 scripts/heartbeat.py --config config.yaml
python3 scripts/heartbeat.py --config config.yaml --account work
python3 scripts/archive_mail.py --config config.yaml --days 90 --frequency monthly
python3 scripts/archive_mail.py --config config.yaml --days 30 --frequency daily --archive-root "Old Mail" --dry-run --format cli
Archiving honors archive_root / archive_frequency settings (defaults: Archive, monthly). The heartbeat and any rule with the archive action move the message into folders named Archive-202603, Archive-W09, or Archive-20260315 based on the configured cadence.
python3 scripts/calendar_invite.py \
--to "bob@example.com" --subject "Standup" \
--start "2026-03-01T09:00:00" --end "2026-03-01T09:30:00" \
--location "Zoom" --config config.yaml
python3 scripts/mail_merge.py \
--data contacts.csv --subject "Hello {{name}}" \
--body "<p>Dear {{name}}, your code is {{code}}.</p>" \
--to-field email --config config.yaml
Create a config.yaml from assets/config.example.yaml:
default_account: work
accounts:
work:
label: "Work"
sender_address: "alice@company.com"
sender_name: "Alice Smith"
imap:
host: imap.company.com
port: 993
username: "alice@company.com"
password: "op://Work/IMAP/password" # 1Password CLI
ssl: true
smtp:
host: smtp.company.com
port: 587
username: "alice@company.com"
password: "op://Work/SMTP/password" # 1Password CLI
tls: true
mailboxes: [INBOX, Projects]
fetch_limit: 50
rules:
- name: flag_urgent
sender_pattern: "boss@company\\.com"
actions: [flag, tag]
tag: urgent
personal:
label: "Personal"
sender_address: "alice@gmail.com"
imap:
host: imap.gmail.com
password: "keychain://imap.gmail.com/alice@gmail.com" # macOS Keychain
smtp:
host: smtp.gmail.com
password: "keychain://smtp.gmail.com/alice@gmail.com" # macOS Keychain
You can also define archive_root (e.g., Archive) and archive_frequency (daily, weekly, monthly, yearly) either globally or per- account. These defaults drive both the archive_mail.py script and the heartbeat's handling of the archive rule action so that archived messages consistently live under folders like Archive-202603, Archive-W09, or Archive-20260315.
Passwords in config support four backends:
| Scheme | Backend | Example |
|---|---|---|
op:// | 1Password CLI | "op://Work/IMAP/password" |
keychain:// | macOS Keychain | "keychain://imap.gmail.com/alice" |
env:// | Environment variable | "env://GMAIL_APP_PASSWORD" |
| (plain text) | Literal value | "my-password" (logs a warning) |
For providers that require OAuth2, set auth: oauth2 on the IMAP/SMTP block:
imap:
host: imap.gmail.com
username: "user@gmail.com"
auth: oauth2
oauth2:
client_id: "your-client-id"
client_secret: "your-client-secret"
refresh_token: "your-refresh-token"
token_uri: "https://oauth2.googleapis.com/token"
Flat imap: / smtp: at root is automatically treated as a single account
named "default".