邮件集成

Cell 内的邮箱账号管理,支持 SMTP 发送和 IMAP 收信。


邮件概念

每个 Cell 可以配置一个或多个邮箱账号,用于:

邮件是 per-cell 装配——不同部门 Cell 使用各自的邮箱(ai@legal.example.com 归 legal Cell)。


CellSpec.Email 声明

CellSpec.Email 是出生声明(INV-SEED-01):

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}/testSMTP 测试

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 轮询已停止

收信处理

新邮件到达后:

  1. 解析邮件内容
  2. 匹配绑定的 Agent
  3. 触发 Agent Run(传入邮件内容作为用户消息)
  4. 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 是并行的通信机制:

特性EmailChannel
触发方式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:    "附件是本周的合同审查报告...",
})

最佳实践

  1. 密码使用 SecretRef:不在声明中明文存放
  2. 声明只用于初始化:运行时修改通过 API
  3. IMAP 注意温度影响:有 IMAP 的 Cell 无法 Cool
  4. SMTP 测试先行:添加账号后立即用 /email/{address}/test 验证
  5. 防环意识:注意自动回复可能形成循环