Files
th-hotel-simple/docs/project/requirements/M001-source-message-inbox-prd.md
2026-07-12 23:53:50 +08:00

398 lines
20 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.

# M001 SourceMessage Inbox 邮件来源入口 PRD
## 文档信息
| 项目 | 内容 |
| --- | --- |
| 文档版本 | 0.1 |
| 日期 | 2026-07-06 |
| 状态 | 草稿 |
| 适用范围 | TH Hotel 项目邮件来源入口、邮件历史查询、邮件原文读取能力 |
| 主要读者 | 产品、后端、前端、测试、后续协作 agent |
## 已确认结论
- 历史邮件范围:第一阶段只查询“本项目已经接收并落库过的邮件”,不主动拉取上线前邮箱历史邮件。
- 邮件唯一标识AgentBus 返回 `source.external_message_id`,对应单封邮件外部唯一 ID。
- 邮件链标识AgentBus 返回 `source.external_conversation_id`,对应邮件会话或邮件链 ID。
- AgentBus frame `id``session_id` 只表示 AgentBus 推送帧和连接会话,不能等同于邮件 ID 或邮件链 ID。
- 正文图片和附件AgentBus 返回的是已经处理过的新 URL且 URL 长期有效。
- 本项目第一阶段不下载附件、不转存文件、不重新生成媒体 URL、不做 URL 刷新机制。
- 邮件原文读取能力是平台通用能力,不按“内部人员 / 业务人员”拆成两套能力,而是按权限、场景、审计和数据最小化原则管控。
- 邮件原文后续在哪个功能或哪个前端页面展示暂时待定,但平台侧应先把能力和数据边界设计清楚。
## 1. Executive Summary
本项目需要建设 `SourceMessage Inbox` 邮件来源入口,用于接收 AgentBus 推送的邮件 JSON并把它沉淀为本项目可查询、可去重、可追溯、可扩展的来源事实。该能力为后续 AI 识别、人工处理、Case / Task 流程提供稳定输入,但第一阶段不直接做 AI 抽取、不自动创建业务任务、不自动回复客户,也不直接调用外部业务系统。平台同时需要提供受控的邮件原文读取能力,后续具体页面由业务功能决定。
## 2. Problem Statement
### 谁有这个问题
- 业务处理人员:后续需要确认某封邮件是否已经进入系统,并在具体业务流程中查看邮件原文或上下文。
- 系统运维 / 管理人员:需要排查 AgentBus 是否成功推送、是否重复投递、payload 是否解析失败。
- 后续 AI / 业务流程:需要一个稳定、可审计的来源输入,而不是直接依赖 AgentBus 原始 DTO。
### 当前问题
如果 AgentBus 邮件 JSON 只被临时消费,或者直接进入 AI / Case / Task 业务流程,会带来以下问题:
- 历史邮件不可查,无法确认邮件是否已经进入系统。
- 重复投递难以幂等处理,可能导致重复任务或重复业务动作。
- AgentBus 协议细节会污染业务模块,后续更换来源方式成本高。
- 邮件原文、HTML、正文图片、附件 URL 如果缺少统一管控,容易在列表、日志、错误响应或前端页面里过度暴露。
- 后续 AI 识别和人工处理缺少统一来源事实,审计链路不完整。
### 为什么痛
- 业务影响:邮件是后续业务流程的起点,入口不稳定会影响 Case 匹配、任务生成、人工复核和追溯。
- 工程影响:入口层和业务层耦合过深,会让后续接入 Microsoft Graph、IMAP、Webhook、手工导入等来源变困难。
- 安全影响原文、HTML、图片和附件 URL 都可能包含客户信息,需要统一权限、审计和最小化返回策略。
### 证据
- 已有 AgentBus 对接文档确认 Outlook 邮件 payload 包含 `source.external_message_id``source.external_conversation_id``body.html``body.text``inline_images[]``attachments[]` 等字段。
- 已有后端规范要求 AgentBus 实时入口必须先写 SourceMessage Inbox后续如需调用 SuperAgent必须通过入库后的受控异步 dispatch / outbox 链路完成,不直接生成 MessageEvent、Evidence、Case、Task、Operation 或 Receipt。
- 🔶 Assumption业务人员后续会在某些业务功能中查看邮件原文但具体功能和页面还未确定。
## 3. Target Users & Personas
### Primary Persona业务处理人员
- 角色:酒店业务流程中的处理人员,例如预订、客服或后续明确的部门角色。
- 目标:在处理具体业务时,能够查看相关邮件原文和附件信息,理解客户真实意图。
- 痛点:如果只能看到摘要,遇到复杂邮件、转发链、附件说明时可能无法做准确判断。
- 当前行为:业务人员可能通过其他方式直接查看邮箱原文;本项目后续需要把原文读取能力接入业务流程。
### Secondary Persona系统运维 / 管理人员
- 角色:负责排查 AgentBus 接入、消息落库、重复投递、失败 payload 的技术或管理人员。
- 目标:确认系统是否收到邮件、是否重复、是否解析失败、失败原因是否安全可见。
- 痛点:如果没有入站记录和幂等状态,排查只能依赖外部日志或临时样本。
### System Persona后续 AI / 业务流程
- 角色:后续的 AI 识别流程、人工复核流程、Case / Task 编排流程。
- 目标:使用稳定的 SourceMessage ID 和受控内容读取能力,而不是直接依赖 AgentBus 原始 payload。
- 痛点:如果来源事实不稳定,后续业务对象无法可靠追溯到原始邮件。
## 4. Strategic Context
### 业务目标
- 建立邮件作为业务流程起点的稳定入口。
- 为后续 AI 识别、人工确认、Case / Task 流程提供可追溯来源。
- 保持低耦合和高可维护性,避免把 AgentBus 协议绑定到业务流程里。
### 为什么现在做
当前项目还处于需求和规范阶段,尚未大量生成前后端代码。此时先确定 SourceMessage Inbox 的产品边界、数据边界和安全边界,可以降低后续重构成本。
## 5. Solution Overview
### 方案概述
建设平台级 `SourceMessage Inbox` 能力。AgentBus Adapter 接收邮件 JSON 后先通过统一捕获端口保存为来源事实。后续所有查询、原文读取、AI 处理、业务处理都围绕本项目内部 SourceMessage ID 展开。
```text
AgentBus 邮件 JSON
// 外部消息入口,负责把邮件 payload 推送给本项目
→ integrations.messaging.agentbus
// AgentBus 适配层:解析 AgentBus frame提取稳定字段不做业务判断
→ platform.message SourceMessage Capture
// 平台消息捕获能力:做幂等、落库、状态记录、安全摘要生成
→ SourceMessage Inbox
// 邮件来源事实:可查询、可追溯、不可直接等同于业务任务
→ 后续受控读取 / AI 识别 / 人工处理
// 后续流程只依赖内部 SourceMessage ID 和平台接口
```
### 核心能力
- 邮件入站捕获:接收 AgentBus 邮件 JSON 并保存来源事实。
- 幂等去重:同一酒店、同一 provider、同一 channel、同一外部邮件 ID 不重复建记录。
- 历史查询:查询本项目已经接收并落库过的邮件。
- 邮件链查询:按 `externalConversationId` 查询同一邮件链下的记录。
- 安全摘要:列表只返回主题摘要、发件人展示值、时间、状态、短摘要等受控展示信息。
- 原文读取:提供统一受控接口读取 text、HTML、正文图片 URL、附件 URL。
- 媒体引用保存:保存 AgentBus 已处理好的长期有效媒体 URL不下载、不转存、不刷新。
- 扩展来源:后续可以接入 Microsoft Graph、IMAP、Webhook、手工导入等来源。
### 数据模型边界
```text
source_message_inbox
// 邮件来源索引表:保存单封邮件的外部 ID、邮件链 ID、来源、状态、摘要和幂等信息
source_message_payload
// 原始载荷表:保存 AgentBus 推来的规范 JSON用于追溯、排查和后续重新处理
source_message_body
// 邮件正文表:保存 text/html 正文。列表不直接返回正文,原文读取接口按权限返回
source_message_media
// 邮件媒体引用表:保存 AgentBus 已处理好的长期有效 URL包括正文图片和附件不保存二进制文件
```
#### `source_message_inbox` 字段说明
| 字段 | 中文说明 |
| --- | --- |
| `id` | 本项目内部 SourceMessage ID后续业务流程使用该 ID 关联来源邮件 |
| `hotelId` | 酒店或业务上下文 ID避免不同酒店邮件 ID 冲突 |
| `provider` | 来源提供方,例如 `AGENTBUS` |
| `channel` | 来源渠道,例如 `EMAIL` |
| `externalMessageId` | 外部邮件系统中的单封邮件唯一 ID对应 AgentBus `source.external_message_id` |
| `externalConversationId` | 外部邮件系统中的邮件链 / 会话 ID对应 AgentBus `source.external_conversation_id` |
| `providerFrameId` | AgentBus frame ID用于排查推送帧不作为邮件唯一 ID |
| `providerSessionId` | AgentBus session ID用于排查连接会话不作为邮件链 ID |
| `payloadSha256` | 规范 JSON 的哈希值,用于排查 payload 是否变化 |
| `captureStatus` | 捕获状态,例如 `RECEIVED``FAILED` |
| `receivedAt` | 邮件来源接收时间,优先取 AgentBus payload `received_at`;缺失时使用本项目接收时间 |
| `sourceSentAt` | 邮件来源发送时间,如果 payload 提供则保存 |
| `senderSummary` | 发件人展示值,业务人员需要识别真实发件人,当前只清理首尾空白不打码 |
| `subject` | 邮件主题 |
| `safeSnippet` | 邮件安全摘要不能包含完整正文、HTML、附件 URL 或敏感凭证 |
推荐幂等键:
```text
hotelId + provider + channel + externalMessageId
// 同一业务上下文、同一来源、同一渠道、同一封外部邮件只保存一次
```
#### `source_message_payload` 字段说明
| 字段 | 中文说明 |
| --- | --- |
| `inboxId` | 所属 SourceMessage Inbox 记录 |
| `payloadJson` | AgentBus 规范 JSON 原始载荷 |
| `payloadSha256` | payload 哈希,用于一致性检查 |
| `schemaVersion` | payload 结构版本AgentBus 有提供则保存,否则使用本项目捕获版本 |
#### `source_message_body` 字段说明
| 字段 | 中文说明 |
| --- | --- |
| `inboxId` | 所属 SourceMessage Inbox 记录 |
| `contentType` | 正文类型,例如 `TEXT``HTML``MIXED` |
| `textBody` | 纯文本正文,用于摘要、搜索和 AI 处理更安全 |
| `htmlBody` | HTML 原文,前端展示前必须做 sanitize |
| `bodySha256` | 正文哈希,用于排查正文是否变化 |
#### `source_message_media` 字段说明
| 字段 | 中文说明 |
| --- | --- |
| `inboxId` | 所属 SourceMessage Inbox 记录 |
| `mediaType` | 媒体类型:`INLINE_IMAGE` 表示正文内图片,`ATTACHMENT` 表示附件 |
| `fileName` | 文件名,附件一般有,正文图片可能为空 |
| `contentType` | 文件 MIME 类型,例如 `image/png``application/pdf` |
| `sizeBytes` | 文件大小AgentBus 有返回则保存,没有则为空 |
| `externalUrl` | AgentBus 返回的长期有效访问 URL只能通过受控详情接口返回 |
| `externalMediaId` | AgentBus 或邮件系统中的媒体 ID有则保存便于排查和追溯 |
### 接口能力边界
```text
GET /api/source-messages
// 邮件历史列表只返回安全摘要不返回完整正文、HTML、附件 URL
GET /api/source-messages/{id}
// 邮件详情摘要:返回来源字段、状态、主题、摘要、邮件链 ID 等,不默认返回原文
GET /api/source-messages/{id}/original
// 邮件原文读取:按权限返回 textBody、htmlBody、正文图片 URL 和附件 URL
GET /api/source-messages?externalConversationId=...
// 邮件链查询:按外部邮件链 ID 查询本项目已接收的同链邮件
```
权限建议:
```text
SOURCE_MESSAGE_READ
// 邮件来源记录读取权限:允许查看列表和摘要详情
SOURCE_MESSAGE_ORIGINAL_READ
// 邮件原文读取权限允许读取正文、HTML、正文图片 URL 和附件 URL
```
## 6. Success Metrics
### Primary Metric
- 邮件入库可靠性:合法 AgentBus 邮件 payload 能稳定保存为 `RECEIVED` SourceMessage Inbox。
- 🔵 Open Question第一阶段是否需要量化目标例如测试环境 100% 合法样本入库成功。
### Secondary Metrics
- 重复投递去重成功率:重复外部邮件不会重复创建 SourceMessage。
- 查询可用性:可以按时间、状态、外部邮件 ID、外部邮件链 ID 查询本项目已接收邮件。
- 原文读取可控性:只有具备 `SOURCE_MESSAGE_ORIGINAL_READ` 权限的调用方可以读取原文和媒体 URL。
### Guardrail Metrics
- 列表接口、普通详情接口、日志、错误响应不得暴露完整正文、HTML、附件 URL、Token 或 Secret。
- AgentBus 实时入口不得在 WebSocket 回调内直接创建 Case、Task、Operation、Receipt 或客户回复M007 SuperAgent 自动分发也必须以 SourceMessage 入库后的异步 dispatch 为边界。
## 7. User Stories & Requirements
### Epic Hypothesis
我们相信建设 SourceMessage Inbox 邮件来源入口,可以让本项目稳定保存、查询和追溯 AgentBus 邮件来源事实,因为业务流程需要一个不依赖 AgentBus DTO 的内部起点。成功标准是合法邮件可入库、重复邮件可幂等、已入库邮件可查询、邮件原文可按权限读取。
### Story 1接收并保存 AgentBus 邮件 JSON
As a 系统
I want to 把 AgentBus 推送的邮件 JSON 保存为 SourceMessage Inbox
so that 后续业务流程可以基于本项目内部来源记录继续处理
Acceptance Criteria:
- Given AgentBus 推送合法邮件 JSON
When 系统处理该 JSON
Then 系统保存一条 `RECEIVED` SourceMessage Inbox 记录
- Given payload 包含 `source.external_message_id`
When 系统保存记录
Then `externalMessageId` 保存该单封邮件外部唯一 ID
- Given payload 包含 `source.external_conversation_id`
When 系统保存记录
Then `externalConversationId` 保存该邮件链 ID
- Given payload 包含正文、正文图片和附件 URL
When 系统保存记录
Then 系统保存正文和媒体引用,但不下载、不转存、不刷新 URL
### Story 2重复投递保持幂等
As a 系统运维 / 管理人员
I want to 系统识别重复投递的同一封邮件
so that 不会产生重复来源记录和后续重复业务处理
Acceptance Criteria:
- Given 已存在相同 `hotelId + provider + channel + externalMessageId` 的记录
When 系统再次收到该邮件 payload
Then 系统不创建新的 SourceMessage Inbox
- Given 重复投递 payload 与原 payload 内容不同
When 系统识别到同一外部邮件 ID
Then 系统不覆盖原始 payload并保留可排查的安全状态或摘要
### Story 3查询已接收邮件历史
As a 具备邮件来源读取权限的用户或系统
I want to 查询本项目已经接收并落库的邮件历史
so that 我可以确认邮件是否已经进入系统
Acceptance Criteria:
- Given 系统中已有 SourceMessage Inbox
When 调用邮件历史列表接口
Then 返回分页列表和安全摘要
- Given 调用方按 `externalMessageId` 查询
When 邮件已存在
Then 返回对应邮件摘要记录
- Given 调用方按 `externalConversationId` 查询
When 同一邮件链已有多封邮件
Then 返回本项目已接收的同链邮件记录
- Given 列表返回结果
Then 不包含完整正文、HTML、附件 URL、Token 或 Secret
### Story 4按权限读取邮件原文
As a 具备邮件原文读取权限的调用方
I want to 通过统一接口读取邮件原文、正文图片和附件 URL
so that 后续业务功能可以在需要时展示完整邮件上下文
Acceptance Criteria:
- Given 调用方具备 `SOURCE_MESSAGE_ORIGINAL_READ` 权限
When 调用邮件原文读取接口
Then 返回 `textBody``htmlBody``inlineImages[]``attachments[]`
- Given 调用方不具备 `SOURCE_MESSAGE_ORIGINAL_READ` 权限
When 调用邮件原文读取接口
Then 系统拒绝访问
- Given 原文接口返回 `htmlBody`
Then 接口说明必须标注前端展示前需要 sanitize
- Given 原文接口返回媒体 URL
Then 只返回 AgentBus 已处理的长期有效 URL不返回 Token 或后端 Secret
- Given 发生原文读取
Then 系统应记录访问审计至少包含调用方、SourceMessage ID、访问时间和访问场景
## 8. Out of Scope
第一阶段不包含:
- 主动拉取上线前邮箱历史邮件。
- 下载附件、转存对象存储、重新生成图片或附件 URL。
- URL 过期刷新机制,因为当前已确认 AgentBus 返回 URL 长期有效。
- AI 抽取、意图识别、Case 匹配、Task 创建、Operation、Receipt。
- 自动回复客户、自动发送 ACK 或 `task.result`
- 具体业务前端页面设计。原文读取能力先作为平台接口能力沉淀,页面位置后续再定。
- 真实客户邮件样本入库测试。测试夹具必须使用合成数据。
未来可考虑:
- 接入 Microsoft Graph、IMAP、Webhook、手工导入等其他来源。
- 全文检索和更复杂的邮件链展示。
- 原文访问审批、敏感字段遮罩、附件预览策略。
- SourceMessage Replay 到 MessageEvent / Evidence。
## 9. Dependencies & Risks
### Dependencies
- AgentBus payload 字段稳定性:依赖 `source.external_message_id``source.external_conversation_id``body.text``body.html``inline_images[]``attachments[]`
- 权限体系:需要后续确认 `SOURCE_MESSAGE_READ``SOURCE_MESSAGE_ORIGINAL_READ` 如何落到角色。
- 审计体系:需要记录原文读取行为,具体表结构可与平台审计能力统一设计。
- HTML 安全展示:前端展示 HTML 前必须 sanitize后端接口文档也要明确该约束。
### Risks & Mitigations
- RiskAgentBus 字段变更导致映射失败。
Mitigation保存原始 payload字段映射失败也落 `FAILED` 记录和安全错误摘要。
- Risk重复投递 payload 内容不同,覆盖原始事实会破坏追溯。
Mitigation幂等命中后不覆盖原 payload差异通过安全摘要或后续 attempt 记录表达。
- Risk邮件原文或媒体 URL 在列表、日志、错误响应中过度暴露。
Mitigation列表只返回安全摘要原文接口独立权限日志和错误响应脱敏。
- Risk业务模块直接依赖 AgentBus DTO。
Mitigation业务模块只依赖 SourceMessage ID 和平台接口AgentBus DTO 只留在 `integrations.messaging.agentbus`
- RiskHTML 原文直接渲染带来安全问题。
Mitigation前端展示前 sanitize必要时使用受控容器和链接安全策略。
## 10. Open Questions
| 问题 | 负责人 | 状态 |
| --- | --- | --- |
| 第一个 checkpoint 是否直接实现 `/api/source-messages/{id}/original` 原文读取接口,还是只写接口契约并后置实现? | 产品 / 后端 | Open |
| `SOURCE_MESSAGE_READ``SOURCE_MESSAGE_ORIGINAL_READ` 第一阶段如何映射到角色? | 产品 / 后端 | Open |
| 原文读取审计是否使用独立表,还是复用后续统一审计能力? | 后端 | Open |
| HTML sanitize 主要在前端完成,还是后端也提供清洗后的安全 HTML | 前端 / 后端 | Open |
| 是否需要第一阶段支持按正文摘要关键字搜索,还是只按 ID、时间、状态、邮件链查询 | 产品 | Open |
## PRD Self-Assessment
### Strongest Section
数据边界和安全边界较清晰:已经明确 AgentBus 字段、邮件唯一 ID、邮件链 ID、媒体 URL 长期有效、原文读取权限和列表最小化返回策略。
### Weakest Section
成功指标和角色权限仍需细化。当前只有定性验收标准,缺少真实环境中的量化目标和角色映射。
### Top Assumptions to Validate
| # | Assumption | Section | Risk if Wrong | Proposed Validation |
| --- | --- | --- | --- | --- |
| 1 | 业务人员后续会在某些功能中查看邮件原文,但页面位置未定 | Section 2 / 8 | 如果不需要展示原文,原文接口优先级可能过高 | 与业务流程设计一起复核 |
| 2 | AgentBus 媒体 URL 长期有效 | Section 5 / 8 | 如果 URL 后续变为临时 URL需要增加刷新或代理机制 | 与 AgentBus 提供方确认并记录 |
| 3 | 第一阶段不需要全文搜索 | Section 10 | 如果业务排查强依赖搜索,查询体验不足 | 需求评审时确认查询条件 |
### Recommended Next Step
先确认第一个 checkpoint 范围:是否把原文读取接口纳入第一阶段实现。确认后,再把本 PRD 拆成用户故事和后端实现计划。