8.7 KiB
内网邮箱 Agent Skill MVP
这是一个本地运行的邮箱 Skill Adapter,面向 Agent 暴露结构化 HTTP 接口,底层使用 POP3 收信、SMTP 发信、SQLite 保存本地索引。
重要边界
- 本项目是
POP3 + SMTP模式,不依赖 IMAP、网页自动化或邮箱数据库。 - POP3 不能可靠同步服务器侧已读、未读、文件夹、标签、归档、草稿状态。
- 已读、已处理、归档、删除、已回复、标签、草稿、已发送记录都是本地 SQLite 状态。
- SMTP 发出的邮件不一定会出现在 Webmail 的“已发送”里。
- 如果将来需要和 Webmail 已发送一致,可以改成调用 Webmail HTTP API,或者发送时 BCC 自己。
- 默认绑定
127.0.0.1,不要把服务直接暴露到公网或共享网段。 - 附件真实路径不会返回给 Agent,接口只返回
attachment_id和附件元数据。
配置
复制示例配置:
cp .env.example .env
按内网邮箱配置 POP3 和 SMTP:
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。
启动
go run ./cmd/server
默认监听:
http://127.0.0.1:8765
也可以指定配置文件:
CONFIG_PATH=/path/to/.env go run ./cmd/server
常用接口示例
健康检查:
curl http://127.0.0.1:8765/health
从 POP3 同步邮件。同步使用 UIDL 去重,不默认删除服务器邮件;单封邮件失败会进入 errors,不会中断整次同步:
curl -X POST http://127.0.0.1:8765/sync
搜索本地邮件,只返回摘要:
curl "http://127.0.0.1:8765/emails/search?q=合同&from=alice@example.local&has_attachment=true&limit=20"
读取邮件全文:
curl http://127.0.0.1:8765/emails/1
读取本地线程:
curl http://127.0.0.1:8765/emails/1/thread
修改本地状态,不会同步回 POP3 服务器:
curl -X POST http://127.0.0.1:8765/emails/1/mark \
-H 'Content-Type: application/json' \
-d '{"read":true,"processed":true}'
创建标签并应用到邮件:
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"}'
创建草稿:
curl -X POST http://127.0.0.1:8765/drafts \
-H 'Content-Type: application/json' \
-d '{"to":["bob@example.local"],"cc":[],"bcc":[],"subject":"测试","body":"你好"}'
列出草稿:
curl http://127.0.0.1:8765/drafts
修改草稿:
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:
curl -X POST http://127.0.0.1:8765/drafts/1/send
直接发送新邮件,内部等价于创建草稿并发送:
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":"正文"}'
转发邮件。MVP 暂不转发附件,include_attachments=true 会返回结构化错误:
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。
构建与测试
运行测试:
go test ./...
只跑本地端到端测试:
go test ./internal/e2e -count=1 -v
internal/e2e 会在测试进程里启动最小 fake POP3 和 fake SMTP 服务,然后通过 HTTP API 验证:
GET /healthPOST /sync- POP3
UIDL去重 - MIME 正文和附件解析
- 附件只暴露
attachment_id,不暴露真实路径 GET /emails/searchGET /emails/{id}- 标签创建与应用
- 本地已读、已处理、已回复状态
- 草稿回复发送
POST /send直接发送- SMTP envelope 包含 To、Cc、Bcc
- Bcc 不写入邮件头
- 发送成功写入
sent_messages - 发送失败写入
sent_messages(status=failed),不误标记草稿为sent
本机编译:
go build ./cmd/server
交叉编译 Windows:
mkdir -p dist
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -o dist/internal-mail-skill.exe ./cmd/server
交叉编译 Linux:
mkdir -p dist
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o dist/internal-mail-skill-linux-amd64 ./cmd/server
也可以使用 Makefile:
make test
make e2e
make build
make cross-build
本地验收建议
外部邮箱注册经常受风控影响,因此交接前建议先跑本地可重复验收:
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 账号做最终验证。
内网接手测试清单
-
复制
.env.example为.env,填写内网 POP3/SMTP 参数。 -
确认
HTTP_HOST=127.0.0.1,不要直接暴露到公网或共享网段。 -
启动服务:
go run ./cmd/server -
健康检查:
curl http://127.0.0.1:8765/health -
同步邮件:
curl -X POST http://127.0.0.1:8765/sync -
再次同步,确认重复邮件进入
skipped,不会重复入库。 -
搜索一封已知邮件:
curl "http://127.0.0.1:8765/emails/search?q=关键字&limit=10" -
读取邮件全文,确认正文、附件元数据和本地状态正常。
-
创建草稿并发送到测试收件人。
-
如果是回复邮件,确认
In-Reply-To、References尽量写入,且原邮件本地replied=true。 -
如果 SMTP 发出后 Webmail 的“已发送”里看不到,这是 POP3+SMTP 模式的正常限制;需要一致性时可 BCC 自己或后续接 Webmail HTTP API。
常见问题
SMTP 25 端口能连但发不出去
确认内网服务器是否需要 SMTP AUTH。如果需要明文 AUTH,必须显式设置:
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、文件名、类型和大小。