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

318 lines
8.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 内网邮箱 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` 和附件元数据。
## 配置
复制示例配置:
```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":"正文"}'
```
转发邮件。MVP 暂不转发附件,`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 的主链路处理能力。
## 已完成的公网邮箱试测记录
以下记录用于交接背景,不包含任何密码。
- 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. 启动服务:
```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`、文件名、类型和大小。