Email ↔ AI Agent bidirectional remote command bridge. Control AI coding agents (Claude Code, OpenCode) via email — no dashboards, no webhooks, no chat apps.
Inbox ──> IMAP Listener ──> claude -p subprocess ──> SMTP reply
MailCode's core idea is lightweight, direct human-to-agent connection.
Most AI toolchains rely on heavy collaboration platforms — Slack/Discord bots, webhook configurations, chat interfaces. MailCode does the opposite: it uses email, something you already have.
Direct connection, not a chatbot. Replying to an email sends a command. Your inbox is your console. No third-party app required.
Lightweight async. No persistent services, no database, no message queue. One Python script + email protocol, runs on any machine with network access. The agent works in the background; you do other things; the result arrives in your inbox.
MailCode doesn't aim to be a platform. It does one thing: let you talk to an AI agent the way you already talk to people — through email.
MailCode requires two email accounts — one Bot, one User:
- Bot mailbox (e.g.,
mailcode_bot@example.com) — MailCode monitors its inbox. After the AI processes a task, results are sent back through this mailbox. - User mailbox (your personal email, e.g.,
you@example.com) — You send command emails from this address to the Bot mailbox.
Flow:
[User Mailbox] ──send command──▶ [Bot Mailbox Inbox]
you@example.com mailcode_bot@example.com
│
▼
IMAP Listener
│
▼
AI Processing
│
▼
[User Mailbox] ◀──reply email── [Bot Mailbox Outbox]
Why not use your own mailbox? MailCode needs to log into a mailbox to read and send emails, so a dedicated Bot mailbox is required. It stays separate from your daily mailbox, and the
allowed_sendersconfig ensures only your personal email can send it commands.
- python3 (≥3.9)
- Claude Code or OpenCode (
claude/opencodecommand must be inPATH)
Zero third-party Python dependencies — all standard library (imaplib, smtplib, email, subprocess, json, secrets, etc.).
pip install mailcodegit clone <repo-url> && cd MailCode
bash install.shinstall.sh automatically: installs the mailcode package, initializes config, creates ~/.mailcode symlink, and adds to PATH.
Install from local wheel: bash install.sh --local dist/mailcode-*.whl
Edit ~/.config/mailcode/config.json with required fields. Keep the two mailboxes straight — mailcode_bot.email is the Bot mailbox, security.allowed_senders lists the addresses allowed to send it commands (typically your personal email):
SMTP and IMAP settings are auto-detected from the Bot mailbox domain. Supported providers: QQ Mail, 163/126 Mail, Gmail, Outlook/Hotmail.
To override SMTP/IMAP (e.g., self-hosted email), add smtp / imap sections — manual values take precedence over auto-detection.
Getting an app password: QQ Mail → Settings → Account → POP3/IMAP → Generate authorization code. Gmail → Google Account → Security → App passwords.
| Command | Description |
|---|---|
mailcode serve |
Start IMAP listener relay (includes scheduler, real-time console events) |
mailcode chat |
Terminal interactive mode — talk to AI directly (no email) |
mailcode schedule <action> |
Scheduled task management (list, show, add, enable, disable, delete, run-now, validate) |
mailcode config <action> |
Configuration management (show, init, init-test, path, validate) |
mailcode health [--send] |
Mail connectivity check (SMTP/IMAP; --send sends a test email) |
mailcode session <action> |
Session management (list, show, delete, cleanup, stats) |
mailcode --version |
Show version |
# Foreground (default: IMAP IDLE persistent connection, real-time email delivery)
# Console output shows live events: 📬 email received → 🤖 invoking AI → ✅ reply sent
mailcode serve
# Dry-run mode (print emails only, don't invoke AI)
mailcode serve --dry-run
# Force polling (disable IDLE; some legacy providers require this)
mailcode serve --no-idle
# Single poll then exit
mailcode serve --onceIMAP IDLE support varies by provider — MailCode detects IMAP CAPABILITY on connect and falls back to polling if IDLE is unavailable:
| Provider | IDLE | Behavior | Recommended check_interval |
|---|---|---|---|
QQ Mail (imap.qq.com) |
✅ | Real-time push, sub-second response | 60s (when polling) |
163/126 Mail (imap.163.com / imap.126.com) |
❌ | Auto-fallback to polling, warning log | 60-120s (excessive polling triggers anti-abuse rate limits, potentially IP ban) |
| Gmail / Outlook | ✅ | Real-time push | 60s (when polling) |
NetEase (163/126) mailboxes do not support IDLE, and frequent IMAP logins trigger anti-abuse measures. If your Bot mailbox uses 163/126, set mailcode_bot.check_interval to 60–120 seconds to avoid temporary bans.
View logs:
tail -f ~/.config/mailcode/relay.logmailcode config show # View current config (passwords masked)
mailcode config path # Show config file path
mailcode config init # Initialize config (skip if exists)
mailcode config init --force # Force re-initialize
mailcode config validate # Validate config integrityTalk to AI directly from the terminal without going through email:
mailcode chat # Start a new conversation
mailcode chat --session-id <id> # Resume an existing session
mailcode chat --cwd ~/my-project # Set working directoryUseful for quick debugging or when you don't want to use the email channel. Sessions created in serve mode can be resumed in chat mode and vice versa.
MailCode maintains multi-turn conversations grouped by email subject by default. Set session.enabled = false for single-reply mode.
mailcode session list # List all sessions
mailcode session list --wide # Full display (no truncation)
mailcode session list --filter "keyword" # Filter by sender or subject
mailcode session show <session_id> # View full message history
mailcode session delete <session_id> # Delete a session
mailcode session stats # Statistics (total / active / expired)
mailcode session cleanup # Clean up expired sessions by TTL
mailcode session cleanup --dry-run # Preview only, no deletionPut cwd: <path> on the first line of your email body to start the AI subprocess in that directory — ideal for "AI, work on this project." In session mode, the cwd is sticky: subsequent emails in the same session reuse the directory until a new one is specified.
cwd: ~/Projects/my-app
Take a look at the JWT validation logic in src/auth.py
Path resolution rules:
~/~/fooexpands to the user's home directory- Relative paths (
./foo,foo) resolve fromPath.cwd() - The path must exist and be a directory (
is_dir()check); otherwise it falls back to$HOME - Case-insensitive:
Cwd:/CWD:work the same way
Mode differences:
- Session mode (
session.enabled = true, default): cwd is sticky across the entire session; check withmailcode session show <id> - Single-reply mode (
session.enabled = false): cwd is not sticky, each email parses independently
The cwd line is stripped from the body before invoking the AI, so it never pollutes the prompt.
mailcode health # Check SMTP/IMAP config and connectivity
mailcode health --send # Also send a test email to verify the send channelChecks: SMTP connection / login / send, IMAP connection / login / inbox, sender whitelist is not empty (an empty whitelist rejects all incoming emails in serve mode).
MailCode includes a lightweight scheduling engine — no external cron or systemd timer required. It runs inside mailcode serve, with persistence to ~/.config/mailcode/schedules.json.
Four schedule types:
| Type | Parameters | Example |
|---|---|---|
interval |
--interval-seconds <N> |
Every 3600 seconds |
daily |
--time <HH:MM> |
Every day at 09:00 |
weekly |
--time <HH:MM> --day-of-week <0-6> |
Every Monday at 09:00 (0=Sunday) |
monthly |
--time <HH:MM> --day-of-month <1-31> |
1st of each month at 09:00 |
Scheduled tasks invoke claude -p <prompt> and email the response to the configured recipient. All schedules are based on local time. Missed trigger windows are skipped (no catch-up).
# Create a scheduled task
mailcode schedule add morning-digest --type daily --time 09:00 \
--prompt "Summarize GitHub notifications, list today's TODOs" \
--to-email you@example.com \
--subject-prefix "[Morning Digest]"
# List all tasks
mailcode schedule list
# View task details
mailcode schedule show morning-digest
# Execute immediately (doesn't affect schedule stats)
mailcode schedule run-now morning-digest
# Enable / Disable
mailcode schedule enable morning-digest
mailcode schedule disable morning-digest
# Delete
mailcode schedule delete morning-digest
# Validate all task configs
mailcode schedule validateConfiguration (~/.config/mailcode/config.json, optional):
{
"schedule": {
"enabled": true, // Global toggle
"tick_seconds": 30 // Scheduler poll interval
}
}Features:
- Hot-reload — tasks created/modified/deleted via CLI take effect immediately in a running
serveprocess (no restart needed) - Concurrency guard — a task won't re-trigger while its previous run is still in progress
- Error notification — if AI invocation or email sending fails, the configured recipient is notified automatically
- Standalone execution —
mailcode schedule run-nowworks without a runningserveprocess - Dry-run compatible —
mailcode serve --dry-runmarks tasks as executed without actually running them
{ "mailcode_bot": { "email": "mailcode_bot@example.com", // ← Bot mailbox: MailCode logs into this "password": "Bot mailbox app password", // ← App password, not your login password "check_interval": 60 // ← Poll interval (seconds); 163/126 recommend 60-120 }, "security": { "allowed_senders": ["you@example.com"] // ← Allowed command senders (your personal email) } }