# 内网邮箱 Agent Skill Adapter 这是一个本地运行的邮箱 Skill Adapter,面向 Agent 暴露结构化 HTTP 接口,底层使用 POP3 收信、SMTP 发信、SQLite 保存本地索引。仓库同时包含一个可交给 Agent 使用的 Skill 说明包。 ## Skill 包 Skill 包位于: ```text skills/internal-mail-agent/ ``` 其中: - `skills/internal-mail-agent/SKILL.md`:Agent 使用本地邮箱 Adapter 的核心流程和安全规则。 - `skills/internal-mail-agent/references/http-api.md`:HTTP API 请求形态、查询参数和示例。 如果接手环境支持 Codex Skill,可以把 `skills/internal-mail-agent` 复制或安装到对应的 skills 目录;如果不支持,也可以直接把 `SKILL.md` 作为 Agent 工具说明使用。 ## 重要边界 - 本项目是 `POP3 + SMTP` 模式,不依赖 IMAP、网页自动化或邮箱数据库。 - POP3 不能可靠同步服务器侧已读、未读、文件夹、标签、归档、草稿状态。 - 已读、已处理、归档、删除、已回复、标签、草稿、已发送记录都是本地 SQLite 状态。 - SMTP 发出的邮件不一定会出现在 Webmail 的“已发送”里。 - 如果将来需要和 Webmail 已发送一致,可以改成调用 Webmail HTTP API,或者发送时 BCC 自己。 - 默认绑定 `127.0.0.1`,不要把服务直接暴露到公网或共享网段。 - 附件真实路径不会返回给 Agent,接口只返回 `attachment_id` 和附件元数据。 ## 配置 复制示例配置: ```bash cp .env.example .env ``` 按内网邮箱配置 POP3 和 SMTP: ```dotenv MAIL_ACCOUNT_ID=default POP3_HOST=mail.example.local POP3_PORT=110 POP3_USERNAME=your-name POP3_PASSWORD=your-password POP3_USE_TLS=false POP3_TIMEOUT_SECONDS=30 SMTP_HOST=mail.example.local SMTP_PORT=25 SMTP_USERNAME=your-name SMTP_PASSWORD=your-password SMTP_USE_TLS=false SMTP_USE_STARTTLS=false SMTP_AUTH_REQUIRED=false SMTP_ALLOW_INSECURE_AUTH=false SMTP_FROM_ADDRESS=your-name@example.local SMTP_FROM_NAME=Your Name SMTP_TIMEOUT_SECONDS=30 ``` 不要把真实账号密码提交到 Git。服务日志不会打印密码。 如果内网 SMTP 服务器要求在 25 端口上使用明文 SMTP AUTH,需要显式设置 `SMTP_ALLOW_INSECURE_AUTH=true`。这会把认证信息发送在未加密连接上,只应在可信内网里使用;公网服务商通常应使用 `SMTP_USE_TLS=true` 或 `SMTP_USE_STARTTLS=true`。 ## 启动 ```bash go run ./cmd/server ``` 默认监听: ```text http://127.0.0.1:8765 ``` 也可以指定配置文件: ```bash CONFIG_PATH=/path/to/.env go run ./cmd/server ``` ## 常用接口示例 健康检查: ```bash curl http://127.0.0.1:8765/health ``` 从 POP3 同步邮件。同步使用 `UIDL` 去重,不默认删除服务器邮件;单封邮件失败会进入 `errors`,不会中断整次同步: ```bash curl -X POST http://127.0.0.1:8765/sync ``` 搜索本地邮件,只返回摘要: ```bash curl "http://127.0.0.1:8765/emails/search?q=合同&from=alice@example.local&has_attachment=true&limit=20" ``` 读取邮件全文: ```bash curl http://127.0.0.1:8765/emails/1 ``` 读取本地线程: ```bash curl http://127.0.0.1:8765/emails/1/thread ``` 修改本地状态,不会同步回 POP3 服务器: ```bash curl -X POST http://127.0.0.1:8765/emails/1/mark \ -H 'Content-Type: application/json' \ -d '{"read":true,"processed":true}' ``` 创建标签并应用到邮件: ```bash curl -X POST http://127.0.0.1:8765/labels \ -H 'Content-Type: application/json' \ -d '{"name":"待处理","color":"#336699"}' curl -X POST http://127.0.0.1:8765/emails/labels/apply \ -H 'Content-Type: application/json' \ -d '{"email_ids":[1],"label_ids":[1],"op":"add"}' ``` 创建草稿: ```bash curl -X POST http://127.0.0.1:8765/drafts \ -H 'Content-Type: application/json' \ -d '{"to":["bob@example.local"],"cc":[],"bcc":[],"subject":"测试","body":"你好"}' ``` 列出草稿: ```bash curl http://127.0.0.1:8765/drafts ``` 修改草稿: ```bash curl -X PATCH http://127.0.0.1:8765/drafts/1 \ -H 'Content-Type: application/json' \ -d '{"to":["bob@example.local"],"subject":"更新后的主题","body":"更新后的正文"}' ``` 发送草稿。Bcc 不写入邮件头,但会参与 SMTP 实际发送;发送成功写入 `sent_messages`,回复邮件会把原邮件本地标记为 `replied=true`: ```bash curl -X POST http://127.0.0.1:8765/drafts/1/send ``` 直接发送新邮件,内部等价于创建草稿并发送: ```bash curl -X POST http://127.0.0.1:8765/send \ -H 'Content-Type: application/json' \ -d '{"to":["bob@example.local"],"bcc":["audit@example.local"],"subject":"直接发送","body":"正文"}' ``` 转发邮件。当前版本暂不转发附件,`include_attachments=true` 会返回结构化错误: ```bash curl -X POST http://127.0.0.1:8765/emails/1/forward \ -H 'Content-Type: application/json' \ -d '{"to":["bob@example.local"],"subject":"Fwd: 原邮件","body":"请看下面原文","include_attachments":false}' ``` ## 数据目录 默认路径: - SQLite: `./data/internal_mail.db` - 原始邮件: `./data/raw` - 附件文件: `./data/attachments` 这些运行时数据默认不提交到 Git。 ## 构建与测试 运行测试: ```bash go test ./... ``` 只跑本地端到端测试: ```bash go test ./internal/e2e -count=1 -v ``` `internal/e2e` 会在测试进程里启动最小 fake POP3 和 fake SMTP 服务,然后通过 HTTP API 验证: - `GET /health` - `POST /sync` - POP3 `UIDL` 去重 - MIME 正文和附件解析 - 附件只暴露 `attachment_id`,不暴露真实路径 - `GET /emails/search` - `GET /emails/{id}` - 标签创建与应用 - 本地已读、已处理、已回复状态 - 草稿回复发送 - `POST /send` 直接发送 - SMTP envelope 包含 To、Cc、Bcc - Bcc 不写入邮件头 - 发送成功写入 `sent_messages` - 发送失败写入 `sent_messages(status=failed)`,不误标记草稿为 `sent` 本机编译: ```bash go build ./cmd/server ``` 交叉编译 Windows: ```bash mkdir -p dist CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -o dist/internal-mail-skill.exe ./cmd/server ``` 交叉编译 Linux: ```bash mkdir -p dist CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o dist/internal-mail-skill-linux-amd64 ./cmd/server ``` 也可以使用 Makefile: ```bash make test make e2e make build make cross-build ``` ## 本地验收建议 外部邮箱注册经常受风控影响,因此交接前建议先跑本地可重复验收: ```bash go test ./internal/e2e -count=1 -v go test ./... go build ./cmd/server CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -o dist/internal-mail-skill.exe ./cmd/server ``` 本地 E2E 不依赖真实邮箱账号,不会发出公网邮件;它验证的是本项目对 POP3、SMTP、MIME、SQLite 和 HTTP API 的主链路处理能力。 ## 已完成的公网邮箱试测记录 以下记录用于交接背景,不包含任何密码。 - VFEmail:POP3 同步成功,SMTP 发送成功,自发自收闭环成功,邮件可通过搜索接口查到。 - Runbox:POP3 同步成功;SMTP 587 STARTTLS 和 465 TLS 均返回 `535 Incorrect authentication data`;外部投递到该账号返回 `550 Unknown account`。 - GMX、eclipso、Fastmail、Purelymail、mailbox.org:公开 POP3/SMTP 端口握手可达,但注册或试用受风控、手机号、试用限制影响,未形成账号级完整闭环。 结论:代码主链路已用本地 E2E 和 VFEmail 真实账号闭环验证;内网交接时应重点用公司 POP3/SMTP 账号做最终验证。 ## 内网接手测试清单 1. 复制 `.env.example` 为 `.env`,填写内网 POP3/SMTP 参数。 2. 确认 `HTTP_HOST=127.0.0.1`,不要直接暴露到公网或共享网段。 3. 启动服务: ```bash go run ./cmd/server ``` 4. 健康检查: ```bash curl http://127.0.0.1:8765/health ``` 5. 同步邮件: ```bash curl -X POST http://127.0.0.1:8765/sync ``` 6. 再次同步,确认重复邮件进入 `skipped`,不会重复入库。 7. 搜索一封已知邮件: ```bash curl "http://127.0.0.1:8765/emails/search?q=关键字&limit=10" ``` 8. 读取邮件全文,确认正文、附件元数据和本地状态正常。 9. 创建草稿并发送到测试收件人。 10. 如果是回复邮件,确认 `In-Reply-To`、`References` 尽量写入,且原邮件本地 `replied=true`。 11. 如果 SMTP 发出后 Webmail 的“已发送”里看不到,这是 POP3+SMTP 模式的正常限制;需要一致性时可 BCC 自己或后续接 Webmail HTTP API。 ## 常见问题 ### SMTP 25 端口能连但发不出去 确认内网服务器是否需要 SMTP AUTH。如果需要明文 AUTH,必须显式设置: ```dotenv SMTP_AUTH_REQUIRED=true SMTP_ALLOW_INSECURE_AUTH=true ``` 公网邮箱通常不应使用明文 AUTH,应使用 `SMTP_USE_TLS=true` 或 `SMTP_USE_STARTTLS=true`。 ### 同步后 Webmail 里还是未读 这是预期行为。POP3 无法可靠同步服务器侧已读、标签、归档、文件夹等状态;本项目只维护本地状态。 ### SMTP 发出的邮件没有出现在 Webmail 已发送 这是预期行为。SMTP 只负责投递,不一定写入 Webmail 已发送文件夹。需要一致性时,可让系统 BCC 自己,或者后续接入 Webmail HTTP API。 ### 附件在哪里 附件真实路径只保存在 SQLite 中,不通过 API 返回给 Agent。API 只返回 `attachment_id`、文件名、类型和大小。