← 返回 Skills 市场
psyb0t

docker-mailbox

作者 Ciprian Mandache · GitHub ↗ · v1.2.0 · MIT-0
cross-platform ⚠ pending
125
总下载
0
收藏
0
当前安装
3
版本数
在 OpenClaw 中安装
/install docker-mailbox
功能描述
Multi-mailbox IMAP/SMTP control plane exposed as a REST API + MCP server (streamable HTTP) on a single port. Read, search, send, mark-seen, and delete mail a...
使用说明 (SKILL.md)

docker-mailbox

REST + MCP shim over IMAP/SMTP. Point it at one or more mail accounts via a YAML config, get back one HTTP API + one MCP server on the same port (MCP rides a streamable-HTTP endpoint at /mcp). No webmail. No DB. No message store. Stateless — restart it and nothing's lost because nothing was ever kept.

The killer endpoint is GET /inbox — it hits every IMAP account in parallel, runs the same structured search on each, merges newest-first, and tags every result with which mailbox it came from. "Show me everything from [email protected]," "what's unread right now," "what came in this morning" — one call, no fanout dance on the client side.

For installation and setup, see references/setup.md.

Setup

The API should already be running. Set the base URL and (if configured) the bearer token:

export MAILBOX_URL=http://localhost:8000
export MAILBOX_TOKEN=your_token_here   # omit if auth.tokens is empty in config

Verify:

curl -s $MAILBOX_URL/health
# {"ok": true, "version": "0.1.0"}

curl -s -H "Authorization: Bearer $MAILBOX_TOKEN" $MAILBOX_URL/mailboxes | jq

/health is always open — point liveness probes at it without worrying about auth.

Auth is optional. If auth.tokens is empty/missing in the server config, all endpoints are open. If it's set, every non-/health request needs Authorization: Bearer \x3Cone of auth.tokens> and returns 401 (with WWW-Authenticate: Bearer) on miss. Tokens are constant-time compared. The same gate covers /mcp.

How It Works

GET to read, POST to send/mark/create, DELETE to delete. All bodies are JSON. All responses are JSON.

Every error response:

{"detail": "description of what went wrong"}

Status codes:

Status When
401 Missing or invalid bearer (when auth is on).
404 Unknown mailbox name in the URL.
409 Mailbox doesn't have the requested protocol (IMAP endpoint on an SMTP-only mailbox).
422 Request body validation failed (pydantic).
502 The IMAP / SMTP server upstream rejected the operation.

UIDs (not sequence numbers) are used for every message identifier so IDs stay stable across server-side mutations.

API Reference

Health

curl -s $MAILBOX_URL/health
# {"ok": true, "version": "0.1.0"}

Mailboxes

curl -s -H "Authorization: Bearer $MAILBOX_TOKEN" $MAILBOX_URL/mailboxes
{
  "mailboxes": [
    { "name": "personal", "description": "Gmail", "imap": true, "smtp": true },
    { "name": "work",     "description": "",       "imap": true, "smtp": true }
  ]
}

name is the URL-safe handle (matches [a-zA-Z0-9_-]+, unique) used in every other path. The imap / smtp booleans tell you which protocols the server has configured for that mailbox — if imap: false, you can't list/fetch/delete; if smtp: false, you can't send.

Unified inbox (the main read endpoint)

GET /inbox fans out across every IMAP-configured mailbox in parallel, runs the same structured search against each one, merges newest-first, and tags each message with which account it came from. Per-mailbox failures land in errors instead of aborting the whole call.

Query param What it does
mailbox CSV filter by mailbox name (personal) or email address ([email protected]). Omit to search all IMAP mailboxes.
from, to, subject, body, text IMAP SEARCH predicates. text is full-text across headers + body.
since, before IMAP date filters, e.g. 1-Jan-2026.
unseen, seen, flagged, answered Boolean flag filters.
larger_than, smaller_than Size filters in bytes.
folder IMAP folder name (default INBOX).
limit Max merged results, ≤ 500 (default 50).
# everything from one sender, all accounts
curl -s -H "Authorization: Bearer $MAILBOX_TOKEN" \
  "$MAILBOX_URL/[email protected]&limit=20" | jq

# unread mail in just two accounts
curl -s -H "Authorization: Bearer $MAILBOX_TOKEN" \
  "$MAILBOX_URL/inbox?mailbox=personal,work&unseen=true" | jq

# everything since yesterday, full-text "invoice"
curl -s -H "Authorization: Bearer $MAILBOX_TOKEN" \
  "$MAILBOX_URL/inbox?since=$(date -d 'yesterday' +%-d-%b-%Y)&text=invoice" | jq

# search a specific folder (e.g. Spam)
curl -s -H "Authorization: Bearer $MAILBOX_TOKEN" \
  "$MAILBOX_URL/inbox?folder=Spam&limit=10" | jq

Response:

{
  "messages": [
    {
      "uid": "1234",
      "mailbox": "personal",
      "mailbox_address": "[email protected]",
      "from": "[email protected]",
      "to": "[email protected]",
      "subject": "weekly sync",
      "date": "Mon, 18 May 2026 09:15:00 +0000",
      "message_id": "\[email protected]>",
      "flags": ["\\Seen"]
    }
  ],
  "errors": [
    { "mailbox": "work", "error": "login failed: ..." }
  ]
}

Per-mailbox IMAP

When you want to target one account directly:

# Folders
curl -s -H "Authorization: Bearer $MAILBOX_TOKEN" \
  $MAILBOX_URL/mailboxes/personal/folders

# List newest-first headers — raw IMAP SEARCH criteria
curl -s -H "Authorization: Bearer $MAILBOX_TOKEN" \
  "$MAILBOX_URL/mailboxes/personal/messages?folder=INBOX&limit=20&search=UNSEEN"

# Structured single-mailbox search — same query params as /inbox minus `mailbox`
curl -s -H "Authorization: Bearer $MAILBOX_TOKEN" \
  "$MAILBOX_URL/mailboxes/personal/[email protected]&since=1-May-2026"

# Fetch one full message (decoded body_text + body_html + attachment metadata)
curl -s -H "Authorization: Bearer $MAILBOX_TOKEN" \
  "$MAILBOX_URL/mailboxes/personal/messages/1234?folder=INBOX"

# Same but also get `body_reader` — HTML stripped to clean text/markdown
# (perfect for feeding into an LLM without all the table/style chrome)
curl -s -H "Authorization: Bearer $MAILBOX_TOKEN" \
  "$MAILBOX_URL/mailboxes/personal/messages/1234?folder=INBOX&reader=true"

# Mark seen / unseen
curl -s -X POST -H "Authorization: Bearer $MAILBOX_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"seen": true}' \
  "$MAILBOX_URL/mailboxes/personal/messages/1234/seen?folder=INBOX"

# Delete (flag \Deleted + EXPUNGE — gone, really gone)
curl -s -X DELETE -H "Authorization: Bearer $MAILBOX_TOKEN" \
  "$MAILBOX_URL/mailboxes/personal/messages/1234?folder=INBOX"

/messages search is raw IMAP SEARCH (e.g. ALL, UNSEEN, FROM foo@bar, (UNSEEN FROM foo@bar)). /search is the structured query DSL — same params as /inbox minus mailbox. Use whichever's easier.

Full-message fetch returns:

{
  "uid": "1234",
  "from": "[email protected]",
  "to": "[email protected]",
  "cc": "",
  "subject": "weekly sync",
  "date": "Mon, 18 May 2026 09:15:00 +0000",
  "message_id": "\[email protected]>",
  "body_text": "plain text body",
  "body_html": "\x3Cp>html body\x3C/p>",
  "body_reader": null,
  "attachments": [
    {"filename": "agenda.pdf", "content_type": "application/pdf", "size": 12345}
  ]
}

body_reader is null unless you pass reader=true. When enabled it falls back to body_text if no HTML body exists, otherwise it's the HTML body stripped to readable markdown (links inline, images dropped, tables flattened, no styles/scripts).

How reader mode works

Runs the HTML body through html2text configured for LLM consumption: body_width=0 (no wrap), ignore_images=True (kills \x3Cimg> tracking pixels), unicode_snob=True (real unicode, no smart-quote mangling). \x3Cstyle>, \x3Cscript>, \x3Chead>, comments and all inline-style chrome get dropped. Headings → #, bold/italic preserved, \x3Ca href="x">text\x3C/a>[text](x) inline, lists/tables converted to markdown equivalents.

The original body_text and body_html are still returned — body_reader is additive. UI clients can render HTML, agents can read markdown, attachments stay as metadata.

Useful when the text/plain part is missing or an auto-generated "view in HTML client" stub (which is true for most marketing/transactional mail). Limitations: reply-quote chains aren't stripped, table-layout emails come through as pipe-tables (faithful but visually noisy).

SMTP

curl -s -X POST -H "Authorization: Bearer $MAILBOX_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{
    "to":           ["[email protected]"],
    "cc":           ["[email protected]"],
    "bcc":          ["[email protected]"],
    "subject":      "hi",
    "body_text":    "plain text body",
    "body_html":    "\x3Cp>optional html body\x3C/p>",
    "from_address": "Me \[email protected]>",
    "reply_to":     "[email protected]"
  }' \
  $MAILBOX_URL/mailboxes/personal/send

Required: to (non-empty), subject, and at least one of body_text / body_html. Both bodies = multipart/alternative.

The SMTP client automatically sets Date, a domain-aligned Message-ID, and a Thunderbird-shaped User-Agent — provider spam filters get hostile when those are missing or sloppy, so we play the game. Response:

{
  "from":       "Me \[email protected]>",
  "to":         "[email protected]",
  "subject":    "hi",
  "message_id": "\[email protected]>"
}

MCP server

Same operations exposed as MCP tools over streamable HTTP at POST /mcp (same port, same bearer). One flat tool set — every per-mailbox op takes mailbox as a parameter (the configured name OR the email address), so the catalog stays constant-sized no matter how many accounts you configure:

mailboxes                   # discovery: list configured mailboxes + capabilities
inbox                       # unified read across all IMAP mailboxes (mailbox= filter)
list_folders                # (mailbox)
list_messages               # (mailbox, folder, limit, search)
search                      # (mailbox, from, subject, since, ...)
get_message                 # (mailbox, uid, reader=true → +body_reader)
delete_message              # (mailbox, uid)
mark_seen                   # (mailbox, uid, seen)
send                        # (mailbox, to, subject, body_text/html, ...)

Discovery flow for an agent: call mailboxes to see what's available, then pass the chosen name ("personal") or address ("[email protected]") as the mailbox argument. For cross-account reads use inboxinbox(from="[email protected]") fans out across every IMAP-enabled mailbox in one call. IMAP-only tools only appear if at least one mailbox has IMAP; same for SMTP. No dead buttons.

There is no stdio transport. Point MCP clients at $MAILBOX_URL/mcp. The endpoint speaks the full streamable-HTTP protocol (GET opens SSE, POST sends requests, DELETE terminates the session). .mcp.json snippet:

{
  "mcpServers": {
    "mailbox": {
      "transport": "streamable-http",
      "url": "http://localhost:8000/mcp",
      "headers": {
        "Authorization": "Bearer YOUR_TOKEN_HERE"
      }
    }
  }
}

Drop the headers block if you're running without auth.tokens.

Common Workflows

Find and delete

# 1. Find UIDs matching the criteria
HITS=$(curl -s -H "Authorization: Bearer $MAILBOX_TOKEN" \
  "$MAILBOX_URL/[email protected]&limit=500" | jq -r '.messages[] | "\(.mailbox) \(.uid)"')

# 2. Delete each (per-mailbox endpoint since DELETE is single-mailbox)
echo "$HITS" | while read -r mailbox uid; do
  curl -s -X DELETE -H "Authorization: Bearer $MAILBOX_TOKEN" \
    "$MAILBOX_URL/mailboxes/$mailbox/messages/$uid"
done

Send-to-self e2e sanity check

MARKER="e2e-$(uuidgen | cut -c1-8)"

# 1. Send marker to self
curl -s -X POST -H "Authorization: Bearer $MAILBOX_TOKEN" \
  -H 'Content-Type: application/json' \
  -d "{\"to\": [\"[email protected]\"], \"subject\": \"ping $MARKER\", \"body_text\": \"$MARKER\"}" \
  "$MAILBOX_URL/mailboxes/personal/send"

# 2. Search for it (may take a few seconds to land)
for i in 1 2 3 4 5; do
  sleep 2
  FOUND=$(curl -s -H "Authorization: Bearer $MAILBOX_TOKEN" \
    "$MAILBOX_URL/inbox?subject=$MARKER" | jq -r '.messages | length')
  [ "$FOUND" -gt 0 ] && break
done

Pull unread across everything, format for a digest

curl -s -H "Authorization: Bearer $MAILBOX_TOKEN" \
  "$MAILBOX_URL/inbox?unseen=true&limit=100" \
  | jq -r '.messages[] | "\(.mailbox)	\(.from)	\(.subject)"' \
  | column -t -s $'	'

Tips

  • Date filters (since, before) use IMAP date format (1-Jan-2026), not ISO — date -d ... +%-d-%b-%Y is your friend.
  • larger_than / smaller_than are in bytes.
  • folder defaults to the mailbox's default_folder (usually INBOX). Provider-specific folder names: Gmail = [Gmail]/Spam, GMX/Yahoo = Spam, Outlook = Junk Email. Use GET /mailboxes/\x3Cname>/folders to discover.
  • A self-send may land in Spam on some providers (GMX especially) due to provider-side self-send heuristics even with proper headers — search folder=Spam if you don't see it in INBOX.
  • Gmail / Yahoo / etc. need app passwords, not your account password. Generate one in the provider's security settings.
  • delete is a real EXPUNGE — there is no trash bin equivalent unless the server moves to a Trash folder first. If you want soft delete, MOVE first then delete; mailboxd doesn't expose move yet.
  • Per-mailbox blowups in /inbox come back in the errors array — always check it, one dead account shouldn't blind you to the rest.
  • Bearer tokens live in the server's config.yaml under auth.tokens — list with multiple tokens to rotate without downtime.
能力标签
cryptorequires-oauth-tokenrequires-sensitive-credentials
如何使用
  1. 确保已安装 OpenClaw(本地或 Docker 部署)
  2. 在对话框中输入安装命令:/install docker-mailbox
  3. 安装完成后,直接呼叫该 Skill 的名称或使用 /docker-mailbox 触发
  4. 根据 Skill 的参数说明提供必要输入,即可获得结构化输出
版本历史
v1.2.0
No visible file changes detected for version 1.2.0. No changes to functionality or documentation.
v1.1.0
No file changes detected for this release. - No user-facing changes or updates in this version. - Functionality and documentation remain the same as the previous release.
v1.0.0
docker-mailbox v1.0.0 - Initial release providing a unified REST API and MCP server for controlling multiple IMAP/SMTP mailboxes on a single port. - Supports reading, searching, sending, marking as seen, and deleting mail across multiple inboxes in one call. - Unified `GET /inbox` endpoint fans out searches across all configured mailboxes and merges results newest-first. - Optional bearer-token authentication; `/health` endpoint is always open for liveness checks. - No state, message store, or per-provider client library required—uses Python stdlib and FastAPI. - Supports granular mailbox operations, full structured search, and simple per-mailbox requests.
元数据
Slug docker-mailbox
版本 1.2.0
许可证 MIT-0
累计安装 0
当前安装数 0
历史版本数 3
常见问题

docker-mailbox 是什么?

Multi-mailbox IMAP/SMTP control plane exposed as a REST API + MCP server (streamable HTTP) on a single port. Read, search, send, mark-seen, and delete mail a... 它是一个面向 Claude Code / OpenClaw 的 AI Agent Skill 插件,目前累计下载 125 次。

如何安装 docker-mailbox?

在 OpenClaw 或 Claude Code 对话框中运行命令「/install docker-mailbox」即可一键安装,无需额外配置。

docker-mailbox 是免费的吗?

是的,docker-mailbox 完全免费,采用 MIT-0 许可证,可自由下载、安装和使用。

docker-mailbox 支持哪些平台?

docker-mailbox 跨平台运行,可在任意部署了 OpenClaw / Claude Code 的环境中使用(cross-platform)。

谁开发了 docker-mailbox?

由 Ciprian Mandache(@psyb0t)开发并维护,当前版本 v1.2.0。

💬 留言讨论