邮件集成
Cell 内的邮箱账号管理,支持 SMTP 发送和 IMAP 收信。
邮件概念
每个 Cell 可以配置一个或多个邮箱账号,用于:
- SMTP 发送:Agent 主动发送邮件
- IMAP 收信:轮询收取新邮件并触发 Agent Run
邮件是 per-cell 装配——不同部门 Cell 使用各自的邮箱(ai@legal.example.com 归 legal Cell)。
CellSpec.Email 声明
CellSpec.Email 是出生声明(INV-SEED-01):
- 只在 Cell 首次 boot 播种进
wes_email_configs表 - 此后存储是唯一权威
- 声明不再被读
spec := wesgine.CellSpec{
Email: []email.Config{
{
Address: "ai@legal.example.com",
SMTPHost: "smtp.example.com",
SMTPPort: 587,
IMAPHost: "imap.example.com",
IMAPPort: 993,
Username: "ai@legal.example.com",
PasswordRef: config.SecretRef{Source: "env", Name: "EMAIL_PASSWORD"},
},
},
}
原缺陷:email upsert 播种,运行时改的密码被声明覆盖回去且哪里都不报错。声明标记在探测成功后无条件写,含声明为空。
Email API
| 方法 | 路径 | 说明 |
|---|---|---|
GET | /email | 邮箱账号列表 |
POST | /email | 添加邮箱账号 |
DELETE | /email/{address} | 删除邮箱账号 |
POST | /email/{address}/test | SMTP 测试 |
SDK 调用
// 列出
accounts, err := cell.Email().List(ctx)
// 添加
err := cell.Email().Add(ctx, email.Config{
Address: "ai@legal.example.com",
SMTPHost: "smtp.example.com",
// ...
})
// 删除
err := cell.Email().Delete(ctx, "ai@legal.example.com")
// 测试
result, err := cell.Email().Test(ctx, "ai@legal.example.com")
IMAP 收信
轮询机制
Cell 启动后,为每个配置了 IMAP 的邮箱账号注册轮询 goroutine。
关键规则:IMAP 收信注册与温度管理一律读存储不读声明。
| 场景 | 行为 |
|---|---|
| 存储有账号 | 注册 IMAP 轮询 |
| 存储无账号 + 声明有 | 首次 boot 播种后注册 |
| 声明为空 | 不注册 |
温度影响
IMAP 轮询使 Cell 保持 Warm——Cell 无法降到 Cool。
imapWarmnessCount 读存储不读声明。仅探测失败时退回声明作估计(偏多 = 钉住 Warm,安全一侧)。
Cell Warm 时:IMAP 轮询活跃
Cell Cool 时:IMAP 轮询已停止
收信处理
新邮件到达后:
- 解析邮件内容
- 匹配绑定的 Agent
- 触发 Agent Run(传入邮件内容作为用户消息)
- Agent 回复可通过 SMTP 发送
配额(INV-QUOTA-05)
MaxEmailAccounts 遵循外设配额统一规则:
| 规则 | 说明 |
|---|---|
| 唯一增长点 | email.Store.PutAccount 是唯一增长点 |
| delta 按行存不存在取 0/1 | 覆盖已有账号 delta=0 |
| 卡在上限能轮换密码 | upsert 是编辑手势 |
| 问与长同临界区 | 防止并发越界 |
// 新增账号
quotaFn(current, 1) // delta=1
// 更新已有账号(换密码)
quotaFn(current, 0) // delta=0,不占配额
邮件安全
凭据管理
邮箱密码必须通过 SecretRef 管理:
email.Config{
PasswordRef: config.SecretRef{
Source: "env",
Name: "LEGAL_EMAIL_PASSWORD",
},
}
防环机制
邮件头解析检查 Auto-Submitted 字段,防止自动回复循环。
已知风险:长 References: 头可能超过行扫描上限。使用 internal/infra/lineio 替代 bufio.Scanner(INV-LINE-01)。
与 Channel 的关系
Email 与 IM Channel 是并行的通信机制:
| 特性 | Channel | |
|---|---|---|
| 触发方式 | IMAP 轮询 | Webhook 推送 |
| 响应方式 | SMTP 发送 | 平台 API |
| 存储权威 | wes_email_configs 表 | CellSpec(无运行时存储) |
| 播种行为 | 首次 boot 播种 | 首次 boot 播种 |
SMTP 发送示例
Agent 可通过邮件工具主动发送:
// Agent 的 send_email 工具调用
result := sendEmail(SendEmailRequest{
From: "ai@legal.example.com",
To: []string{"user@example.com"},
Subject: "合同审查报告",
Body: "附件是本周的合同审查报告...",
})
最佳实践
- 密码使用 SecretRef:不在声明中明文存放
- 声明只用于初始化:运行时修改通过 API
- IMAP 注意温度影响:有 IMAP 的 Cell 无法 Cool
- SMTP 测试先行:添加账号后立即用
/email/{address}/test验证 - 防环意识:注意自动回复可能形成循环