Mail administrative proxy — RPC CLI for IMAP/SMTP accounts, messages, folders and labels.
Status: 🟢 IMPLEMENTED — 24 actions + OAuth2 + App Password. See
CONTRACT.mdfor the authoritative action, HITL, verification, and transport contracts.
Refonte of mail-mcp (MCP server, 25 tools) into a non-MCP
CLI built on the exact model of tick-proxy, itself built
on the ADN of tg-proxy.
Single binary with two namespaces:
mail-proxy admin doctor|status|auth login|status|logout|default|reset|purge # Admin (always JSON)
mail-proxy do <action> [payload|file] # 24 RPC actions| Command | Description |
|---|---|
doctor |
Scan config directory, auto-fix permission problems |
status |
Complete status: accounts, auth state, permissions, probes, issues |
auth login |
Add one account via smart HITL form (type selector → email → password/OAuth2) → writes accounts.json + .env atomically |
auth status |
Show auth state per account (configured / missing password) |
auth logout |
Remove password for one account (HITL-confirmed) |
auth default -a <account> |
Set the default account by ID, alias, or email prefix (HITL-confirmed) |
reset |
Clear all passwords (HITL-confirmed) |
purge |
Delete the config directory (HITL-confirmed) |
| Domain | Actions |
|---|---|
| Inbox | inbox-check · inbox-digest |
| Messages | message-list · message-info · message-search · message-thread · message-mark · message-move · message-archive · message-trash · message-spam · message-delete |
| Compose | message-send · message-reply · message-forward · message-draft |
| Folders | folder-list · folder-create · folder-rename · folder-delete |
| Attachments | attachment-download |
| Labels | label-list · label-set |
| Zimbra | zimbra-tag-list · zimbra-tag-items · zimbra-tag-create · zimbra-tag-delete · zimbra-tag-apply · zimbra-tag-remove |
| Escape hatch | raw |
Full catalog with source-tool mapping and HITL requirement: CONTRACT.md.
uv tool install . # production
uv tool install --editable . # developmentmail-proxy admin doctorBefore doing anything else, you MUST enable IMAP on your email account. Without this, no authentication method will work for reading emails.
- Go to: https://outlook.live.com/mail/0/options/mail/accounts
- Scroll to "POP and IMAP" section
- Set "Let devices and apps use IMAP" to Enabled
- Save
Or try: Settings (⚙️) → Mail → Accounts → POP and IMAP → Enable IMAP.
⚠️ If you don't see the IMAP option, your account may be managed by an organization (Microsoft 365). In that case, the admin IT must enable IMAP in the admin center: https://admin.microsoft.com → Settings → Org settings → Modern authentication → ensure IMAP is allowed.
IMAP is enabled by default on Gmail. No action needed.
IMAP is enabled by default on most Zimbra installations. No action needed.
-
Enable 2-Step Verification (required first): → https://myaccount.google.com/signinoptions/two-step-verification
-
Generate an App Password: → https://myaccount.google.com/apppasswords → Select app: Mail → Select device: Other → type a name → Generate → Copy the 16-character password (e.g.
abcd efgh ijkl mnop) -
Use it in mail-proxy: →
mail-proxy admin auth login→ Type: Gmail → Email:you@gmail.com→ Password: the app password
⚠️ If the apppasswords page says "No app passwords available", make sure 2-Step Verification is active (not just set up). The app passwords option only appears after 2FA is on.
-
Go to security proofs page: → https://account.live.com/proofs/manage/additional?mkt=fr-FR&refd=account.microsoft.com&refp=security
-
Create an app password: → Scroll to "Mots de passe d'application" / "App passwords" → Click "Créer un mot de passe d'application" / "Create a new app password" → Copy the generated password
-
Use it in mail-proxy: →
mail-proxy admin auth login→ Type: Outlook → Email:you@outlook.com→ Password: the app password
⚠️ 2-Step Verification must be enabled for app passwords to appear. If the section is missing, enable 2FA first at the same URL.
No app password needed — use your regular webmail password directly.
mail-proxy admin auth loginOpens a smart HITL form in your browser:
- Select provider type (Gmail / Outlook / Zimbra / Custom)
- Choose auth method:
- App Password — simple, works for Gmail + Zimbra + Outlook (if IMAP enabled)
- OAuth2 — modern, works for Gmail + Outlook (no app password needed, browser consent)
- Fill in your email — IMAP/SMTP hosts are auto-detected from the domain
- Set an account ID (auto-generated from email, or custom)
- Add aliases (comma-separated, optional)
- Enter your password (for App Password method) OR authorize in browser (for OAuth2)
The form writes both accounts.json + .env atomically — no partial state.
mail-proxy admin status # see all accounts, permissions, IMAP/SMTP probes
mail-proxy admin doctor # fix any permission issuesmail-proxy do inbox-check # default account
mail-proxy do inbox-check -a work # specific account by alias
mail-proxy do message-send '{...}' -a gmail # specific account by ID
mail-proxy admin auth default -a work # select the default account explicitlyTwo methods, both fully supported for Gmail and Outlook:
- How it works: Generate a one-time password on the provider's website, use it like a regular password
- Best for: Quick setup, no browser consent needed
- Works on: Gmail ✅, Outlook ✅, Zimbra ✅
- Requires: 2FA enabled + IMAP enabled on the account
- Secret storage:
MAIL_<ID>_PASSin~/.config/mail-proxy/.env
- How it works: Browser-based consent flow, get an access token + refresh token
- Best for: Modern standard, no password exchange, scope control
- Works on: Gmail ✅, Outlook ✅
- Requires: IMAP enabled on the account (for Microsoft: well-known Thunderbird client ID)
- Secret storage:
~/.config/mail-proxy/tokens/<id>.json(chmod 600) - Token refresh: Automatic — when the access token expires, the refresh token gets a new one
1. mail-proxy admin auth login → select Outlook → OAuth2
2. Terminal shows: "Go to https://microsoft.com/devicelogin"
3. Terminal shows: "Enter code: ABCD-EFGH"
4. Open browser → enter code → consent → done
5. Tokens stored automatically → IMAP + SMTP work
No Azure AD app registration needed — uses Thunderbird's well-known client ID.
1. mail-proxy admin auth login → select Gmail → OAuth2
2. Browser opens → Google consent screen → approve
3. Redirected to localhost → tokens captured automatically
4. IMAP + SMTP work with XOAUTH2
Requires a Google Cloud project with OAuth 2.0 credentials:
- Set
MAIL_OAUTH2_GOOGLE_CLIENT_IDandMAIL_OAUTH2_GOOGLE_CLIENT_SECRETin.env
| App Password | OAuth2 | |
|---|---|---|
| Gmail | ✅ Simple | ✅ Modern |
| Outlook | ✅ Works (if IMAP enabled) | ✅ Works (device code flow) |
| Zimbra | ✅ Direct | ❌ Not supported |
| Setup complexity | Low — generate password | Medium — browser consent |
| Token refresh | Never (permanent password) | Automatic (refresh token) |
| Secret storage | .env (password) |
tokens/<id>.json (OAuth tokens) |
| Scope control | Full access | Configurable |
Three files, one purpose each:
| File | Purpose |
|---|---|
~/.config/mail-proxy/accounts.json |
Account definitions (email, aliases, hosts, auth_method) — NOT secrets |
~/.config/mail-proxy/.env |
Passwords ONLY (MAIL_<ID>_PASS) — chmod 600 (for App Password auth) |
~/.config/mail-proxy/tokens/<id>.json |
OAuth2 tokens (access_token, refresh_token, expires_at) — chmod 600 (for OAuth2 auth) |
The login IS the email address from accounts.json — no separate MAIL_*_LOGIN needed.
When an account's email matches a known domain, IMAP/SMTP hosts are resolved automatically:
| Domain | IMAP | SMTP |
|---|---|---|
gmail.com |
imap.gmail.com:993 |
smtp.gmail.com:587 |
outlook.com / hotmail.com / live.com |
outlook.office365.com:993 |
smtp.office365.com:587 |
polytechnique.edu |
webmail.polytechnique.fr:993 |
webmail.polytechnique.fr:587 |
For custom servers, the auth login form lets you specify imap_host and smtp_host manually.
chmod 700 ~/.config/mail-proxy
chmod 600 ~/.config/mail-proxy/.env
chmod 600 ~/.config/mail-proxy/accounts.json
chmod 600 ~/.config/mail-proxy/tokens/Run mail-proxy admin doctor to auto-fix any permission issues.
Most common cause: IMAP is not enabled on your account.
- Check your provider's settings (see "Enable IMAP" section above)
- For Outlook: https://outlook.live.com/mail/0/options/mail/accounts → POP and IMAP → Enable
- For Gmail: IMAP is enabled by default
This error can be misleading. It often means IMAP is disabled, not that Basic Auth is blocked. Enable IMAP first, then retry with app password.
The device code expires after ~15 minutes. Run mail-proxy admin auth login again to get a
new code.
If the refresh token is expired or revoked, run mail-proxy admin auth login again to
re-authorize.
Gmail maps its star to IMAP \\Flagged. message-mark verifies its write by reading back
UID FETCH FLAGS; message-search with flagged_only:true independently checks Gmail with
SEARCH FLAGGED. Both are normal mail-proxy do actions — no raw IMAP command is needed.
On 2026-09-03, Gmail Web retained a yellow star in [Gmail]/Spam after repeated page refreshes
while both checks reported the exact same message unstarred (FLAGS: [], absent from
SEARCH FLAGGED). Another visually starred message in that folder correctly returned
\\Flagged. This is a Gmail Web/IMAP state divergence, not a wrong account, UID, folder, or
generic flags-parser result.
When this happens:
- Do not repeat a flag mutation blindly.
- Use
message-infofor the target UID andmessage-searchwithflagged_only:truein the same folder. If both agree,message-markhas verified the IMAP state correctly. - Treat
data.verification.ok:trueas proof of IMAP state only, not proof that an already loaded Gmail Web row has rendered the same state. - If Gmail Web parity is required, a future provider-specific Gmail API path must read/write
the Gmail
STARREDlabel explicitly; generic IMAP must not silently claim that guarantee.
Human-in-the-Loop via a local web UI. The auth login form has a dedicated template with a
provider type selector, auth method selector (App Password / OAuth2), auto-fill, and existing
accounts display. Other HITL operations (compose, drafts, irreversible deletes) use the
standard review form.
HITL-required: auth login · auth logout · message-send · message-reply ·
message-forward · message-draft · message-delete · folder-delete · raw ·
admin reset · admin purge.
# Discover everything — the docstrings ARE the documentation (≥3 examples each)
mail-proxy do --help # compact overview of all 24 actions
mail-proxy do message-send --help # full docstring + exact payload schema
# Payload: inline JSON or a file path
mail-proxy do inbox-check
mail-proxy do message-list '{"folder":"Archive","limit":5}'
mail-proxy do message-search ./filter.json
mail-proxy do folder-delete '{"names":["Projects/2025","Projects/2026"]}'
mail-proxy do attachment-download '{"uid":42,"filename":"diagram.png"}'
# → ~/Downloads/Mail-Proxy/<account-id>/diagram.png (created automatically)
mail-proxy do attachment-download '{"uid":42,"filename":"diagram.png","save_path":"~/Downloads/Mail-Proxy/"}'
# → ~/Downloads/Mail-Proxy/diagram.png
mail-proxy do message-reply '{"uid":42,"body_text":"See attached","attachments":["/absolute/path/diagram.png"]}'
# Compose actions always open the HITL review page before sending
mail-proxy do message-send '{"to":["x@y.fr"],"subject":"Rendez-vous","body_text":"Dispo demain ?"}'
# Meta options
mail-proxy do folder-list -f table # table instead of JSON
mail-proxy do message-search ./f.json -o /tmp/result.jsonraw is always browser-HITL-approved. It can express every normal action through one or more
protocol primitives and more, but is not an unbounded code-execution surface:
| Protocol | Coverage |
|---|---|
imap (default) |
Any IMAP command, including flags, folders, searches, copies, moves and extensions; isolated connection |
smtp |
Any prebuilt RFC822/MIME message through the account SMTP transport |
gmail-api |
Any Gmail REST endpoint, including canonical STARRED label operations; Google OAuth2 only |
zimbra-soap |
Any authenticated Zimbra SOAP operation XML; provider-native tags and item actions |
mail-proxy do raw '{"protocol":"imap","method":"UID","args":["FETCH","42","(FLAGS)"],"select":"INBOX"}'
mail-proxy do raw '{"protocol":"smtp","method":"send-rfc822","params":{"recipients":["a@b.fr"],"rfc822_base64":"..."}}'
mail-proxy do raw '{"protocol":"gmail-api","method":"post","endpoint":"/users/me/messages/ID/modify","payload":{"addLabelIds":["STARRED"]}}'
mail-proxy do raw '{"protocol":"zimbra-soap","method":"GetTagRequest","payload":"<GetTagRequest xmlns=\"urn:zimbraMail\"/>","account_id":"poly"}'Native Zimbra tags accept batches: zimbra-tag-create accepts names; delete accepts tag_ids;
apply/remove accept both tag_ids and item_ids. Delete, apply, and remove require HITL.
Raw returns the provider response and carries no automatic verification. It never silently changes protocol, and never executes shell, filesystem, Python, or arbitrary runtime code.
| Option | Role |
|---|---|
--output-file <path> / -o |
Write the full envelope to a file |
--format json|table / -f |
Display format (default: json) |
--help / -h |
Full docstring + payload schema |
Attachment downloads default to ~/Downloads/Mail-Proxy/<account-id>/<filename> and create
the account directory as needed. save_path accepts either an explicit file path or a directory
path ending in /; a directory retains the attachment filename.
Every response carries a meta section:
{
"meta": {
"status": "ok",
"comment": "",
"edited": false
},
"data": { }
}stdout is pure JSON — logs and HITL prompts go to stderr, so mail-proxy do … | jq always
works. Every execution also autosaves to /tmp/mail-proxy-autosave/{action}_{timestamp}.json.
make check # smoke + ruff + py_compile + pyright + pytest
make smoke # CLI + registry integrity (24 actions, 0 duplicates)
make uv-link # editable installSee Makefile for the full target list, AGENTS.md for the agent working context, and
CONTRACT.md for the architecture contract.