You 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.
fetch limits, archival settings, and processing rules.
without --account uses the default automatically.
retries via a configured fallback relay.
delivery. If SMTP fails, the message stays in Outbox for retry by the heartbeat.
that apply to all accounts.
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.
DHE+CHACHA20 cipher suites are allowed. Weak ciphers (MD5, RC4, 3DES, DSS)
are explicitly blocked.
always enabled.
and MIME-Version headers automatically.
(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/ from the skill root. Every script accepts
--account 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".
共 1 个版本