# 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，不直接生成 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 实时链路不得直接创建 Case、Task、Operation、Receipt 或客户回复。

## 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 拆成用户故事和后端实现计划。
