返回文章列表
/ 功能设计

邮件模板「草稿 / 发布」设计

把单记录模型改成草稿态与发布态,避免编辑中的模板直接影响线上发送。

邮件模板草稿

状态:设计中(未实现)。本文记录 backoffice 邮件模板页(/email/template/index)“保存草稿 / 保存并生效”的需求讨论与已定方向,供后续落地对齐。 交互原型:doc/prototypes/marketing/email-template-prototype.html(已按本方案改造,可交互预览)。

1. 背景与问题

现状(单记录模型):每条邮件模板一条 EmailTemplate 记录。

  • 「保存草稿」= 写这条记录、不改 enabled;
  • 「保存并生效」= 写这条记录 + enabled = true。

二者操作的是同一条记录,唯一差别是 enabled 标志。带来两个问题:

  1. 对”已启用”的模板点「保存草稿」会立即改动线上真正发出去的内容——“草稿”名不副实,没有”改到一半不影响线上”的安全感。

  2. 曾设想复用 enabled 当”草稿 / 线上”开关(下称 A’),但这会把两个正交概念压成一个标志:

    • 启用 / 禁用——这类邮件到底发不发(业务开关);
    • 草稿 / 已发布——当前内容是编辑中的草稿,还是已发布的线上版。

    商户”禁用某类邮件”和”改了但还没发布”是两回事,混在一个标志里必然混淆。故 A’ 否决。

2. 核心模型(方案 B:草稿 / 线上分离)

两个维度相互独立、互不影响:

维度含义存储
启用 / 禁用这类邮件发 / 不发(业务开关)enabled(bool,保持不变)
草稿 / 已发布内容版本独立的草稿列

数据模型(同表加草稿列,不新建表):EmailTemplate 上把”内容”分成两套列——

  • 线上(已发布)列:subject / content(htmlContent) / senderName / senderEmail … —— 发送时只读这套。
  • 草稿列:draftSubject / draftContent / draftHtmlContent / draftSenderName / draftSenderEmail + draftUpdatedAt(有值即表示”有未发布草稿”)。
  • enabled 原样保留,只表示”这类邮件启不启用”,与草稿无关。

选”镜像列”而非独立草稿表:模板是 8 类固定小集合,镜像列省一张表和一个 join。

发送邮件:始终读线上列。(只要保证”发布”才写线上列,发送链路无需改动。)

3. 交互

操作行为
打开编辑有草稿 → 载入草稿列 + 顶部横幅”正在编辑未发布草稿(保存于 {draftUpdatedAt}),线上版本不受影响”;无草稿 → 载入线上版起手(此时草稿副本 = 线上)
预览预览面板加「草稿 / 线上版」切换:草稿 = 当前编辑内容;线上版 = 已发布内容(客户实际收到的),并提示”编辑不影响它,除非点保存并生效”。用于回答”编辑看草稿时如何看原本内容”
保存草稿只写 draft* 列 + draftUpdatedAt;不动线上列、不动 enabled
保存并生效把 draft* 覆盖到线上列(= 发布),清空草稿标记
放弃草稿(可选)清 draft*,下次以线上版起手
列表 / 卡片启用 / 禁用 Tag 之外,对有草稿的模板加独立的「草稿」徽标(两者并存不混淆)

四种状态组合(禁用与草稿可并存,证明两维度不混淆):启用+草稿 / 启用无草稿 / 禁用+草稿 / 禁用无草稿。

4. 待决策(open questions)

Q1 草稿门控范围:哪些字段进草稿?

  • A(推荐):模板名称 / 描述即时更新(后台标签,客户看不到,改了不影响任何一封邮件);主题 / 正文 / 发件人走草稿。
    • 好处:改名不用”发布”;列表卡片显示的名字始终最新(不涉及”草稿名 vs 线上名”的显示难题)。
  • B:名称、描述、主题、正文全部草稿化,“保存草稿”一律不动线上,“保存并生效”才整体发布。
    • 好处:语义最一致(“草稿 = 我未发布的全部改动”)。
    • 代价:改名也要发布;列表要决定显示”线上名”还是”草稿名”,更复杂。

建议 A。 原则:草稿门控的是”收件人能看到的内容”;后台元数据即时生效。

Q2 测试发送发哪个版本?

  • 选项:线上版(客户实际收到的)/ 草稿版(未发布)/ 有草稿时让用户选。
  • 建议:测试弹窗内提供「线上版 / 草稿版」选择(仅有草稿时出现),默认线上版(“客户收到的”规范测试),可切到草稿版验证未发布改动。

Q3 「保存并生效」是否顺带启用(enabled=true)?

  • 建议:生效只发布内容,不改启用开关(保持正交);如确需”生效即上线发送”,另加”保存并启用”。

5. 落地影响范围(实现时)

  • 实体:src/common/data/shared-entities(EmailTemplate)加 draft* 列 + draftUpdatedAt。
  • 映射:src/common/data/portal/mappings.ts(email_template 表)同步加列。
  • 控制器:src/backoffice/controllers/email/email-template.controller.ts
    • save 写草稿;publish 发布(草稿覆盖线上 + 清草稿);discard 放弃草稿;list / item 读线上列 + 返回”有草稿”标记。
  • 前端:src/backoffice/public/pages/email/template/index.tsx
    • 三按钮(保存草稿 / 保存并生效 / 放弃草稿)、卡片草稿徽标、编辑横幅、预览「草稿 / 线上版」切换、测试弹窗版本选择。
  • 发送链路:renderEmail 读线上列(不变,只要保证仅”发布”写线上列)。

6. 关联

  • 原型:doc/prototypes/marketing/email-template-prototype.html
  • 实体 / 映射 / 控制器 / 前端:见上文路径
  • 数据访问约定:data-context-lifecycle skill(上下文经装饰器取得、响应结束统一 close)