Implement internal mail skill MVP
This commit is contained in:
317
README.md
Normal file
317
README.md
Normal file
@@ -0,0 +1,317 @@
|
||||
# 内网邮箱 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 的主链路处理能力。
|
||||
|
||||
## 已完成的公网邮箱试测记录
|
||||
|
||||
以下记录用于交接背景,不包含任何密码。
|
||||
|
||||
- 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`、文件名、类型和大小。
|
||||
Reference in New Issue
Block a user