Files
sgmail-skill/README.md
2026-06-06 22:28:36 +08:00

8.7 KiB
Raw Blame History

内网邮箱 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=trueSMTP_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 /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

本机编译:

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 的主链路处理能力。

已完成的公网邮箱试测记录

以下记录用于交接背景,不包含任何密码。

  • VFEmailPOP3 同步成功SMTP 发送成功,自发自收闭环成功,邮件可通过搜索接口查到。
  • RunboxPOP3 同步成功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. 启动服务:

    go run ./cmd/server
    
  4. 健康检查:

    curl http://127.0.0.1:8765/health
    
  5. 同步邮件:

    curl -X POST http://127.0.0.1:8765/sync
    
  6. 再次同步,确认重复邮件进入 skipped,不会重复入库。

  7. 搜索一封已知邮件:

    curl "http://127.0.0.1:8765/emails/search?q=关键字&limit=10"
    
  8. 读取邮件全文,确认正文、附件元数据和本地状态正常。

  9. 创建草稿并发送到测试收件人。

  10. 如果是回复邮件,确认 In-Reply-ToReferences 尽量写入,且原邮件本地 replied=true

  11. 如果 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=trueSMTP_USE_STARTTLS=true

同步后 Webmail 里还是未读

这是预期行为。POP3 无法可靠同步服务器侧已读、标签、归档、文件夹等状态;本项目只维护本地状态。

SMTP 发出的邮件没有出现在 Webmail 已发送

这是预期行为。SMTP 只负责投递,不一定写入 Webmail 已发送文件夹。需要一致性时,可让系统 BCC 自己,或者后续接入 Webmail HTTP API。

附件在哪里

附件真实路径只保存在 SQLite 中,不通过 API 返回给 Agent。API 只返回 attachment_id、文件名、类型和大小。