Blocked-by: #5177 (headers 半边先行;本单收窄为只剩附件 )
维护者已拍板(2026-08-04,PM 会话内) :按「先 3 后 1」拆分执行 ——
headers_json 先行(终态方案,不是过渡) = plugin-email/platform-objects: sys_email 加 headers_json + 有界 attachments_json —— 小附件与自定义 header 取得队列投递资格(#5172 一期) #5177 ;
附件走 storage 引用,保留期解耦 :内容经 storage capability 存储、行里存引用 + 永久元数据 (filename / size / hash / contentType,审计用);附件内容的生命周期 = 行到达终态后可回收 (留宽限窗)—— 内容是投递工件,元数据才是审计工件,这一刀把 append-only 审计日志与二进制无限增长解耦。
明确否决:内容 base64 内联进 sys_email(append-only 表无界增长、MySQL 行大小/包大小风险、N 收件人 N 份拷贝)、内联但设大小上限的折中(一个功能两条路径 + 一个阈值,作者最容易在边界上出错)。
响亮性约束:队列模式 + 附件 + storage capability 缺失时不得静默 —— 维持退回内联并说明原因的现行为。
本单实现时需要回答的剩余细节(实现方判断,PR 里说明):storage key 的命名/前缀方案、宽限窗长度与回收的触发机制(终态时同步删 vs 清扫式)、上传失败时的语义(退回内联,响亮)。
原文如下(现状与选项分析,选项部分已由上述拍板取代):
由 #5160 (邮件队列投递)落地时暴露,PR 里已按"不丢数据"的方向处理,但限制本身 需要单独跟踪。
事实
sys_email(packages/platform-objects/src/audit/sys-email.object.ts)有 to_addresses / from_address / cc_addresses / bcc_addresses / reply_to / subject / body_text / body_html,没有附件列,也没有 header 列 。rowToNormalized() 因此只能重建这些字段。
队列投递的载荷是 { rowId } —— worker 靠行重建消息。于是 send({ attachments }) 若真的入队,投递出去的邮件没有附件 :静默丢数据。
#5160 的处置(不是本单要改的)
带 attachments 或 headers 的消息在队列模式下退回内联投递 ,并在 info 级说明原因;有用例钉住。选这条是因为另一个选项是"入队并投出剥掉附件的邮件",那属于 AGENTS.md 反复点名的 declared ≠ delivered。
代价说清楚:这类邮件拿不到本次新增的持久化保证 —— 进程死在投递中途,它们和 #5160 之前一样丢。对"发合同 PDF""发导出报表"这种恰恰最不该丢的邮件,这个洞的形状不太好看。
相关
Blocked-by: #5177(headers 半边先行;本单收窄为只剩附件)
原文如下(现状与选项分析,选项部分已由上述拍板取代):
由 #5160(邮件队列投递)落地时暴露,PR 里已按"不丢数据"的方向处理,但限制本身需要单独跟踪。
事实
sys_email(packages/platform-objects/src/audit/sys-email.object.ts)有to_addresses/from_address/cc_addresses/bcc_addresses/reply_to/subject/body_text/body_html,没有附件列,也没有 header 列。rowToNormalized()因此只能重建这些字段。队列投递的载荷是
{ rowId }—— worker 靠行重建消息。于是send({ attachments })若真的入队,投递出去的邮件没有附件:静默丢数据。#5160 的处置(不是本单要改的)
带
attachments或headers的消息在队列模式下退回内联投递,并在 info 级说明原因;有用例钉住。选这条是因为另一个选项是"入队并投出剥掉附件的邮件",那属于 AGENTS.md 反复点名的 declared ≠ delivered。代价说清楚:这类邮件拿不到本次新增的持久化保证 —— 进程死在投递中途,它们和 #5160 之前一样丢。对"发合同 PDF""发导出报表"这种恰恰最不该丢的邮件,这个洞的形状不太好看。
相关