# 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 - Risk:AgentBus 字段变更导致映射失败。 Mitigation:保存原始 payload,字段映射失败也落 `FAILED` 记录和安全错误摘要。 - Risk:重复投递 payload 内容不同,覆盖原始事实会破坏追溯。 Mitigation:幂等命中后不覆盖原 payload,差异通过安全摘要或后续 attempt 记录表达。 - Risk:邮件原文或媒体 URL 在列表、日志、错误响应中过度暴露。 Mitigation:列表只返回安全摘要;原文接口独立权限;日志和错误响应脱敏。 - Risk:业务模块直接依赖 AgentBus DTO。 Mitigation:业务模块只依赖 SourceMessage ID 和平台接口;AgentBus DTO 只留在 `integrations.messaging.agentbus`。 - Risk:HTML 原文直接渲染带来安全问题。 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 拆成用户故事和后端实现计划。