Skip to content

Commit bb5ebba

Browse files
committed
feat(mail): support notification locale preferences
Signed-off-by: Eli Ma <eli@patch.sh>
1 parent 44aeaa3 commit bb5ebba

11 files changed

Lines changed: 204 additions & 31 deletions

File tree

‎README.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -313,7 +313,7 @@ cargo test --test <name>
313313
can disable a running dispatcher via `mail.enabled = false` and hot-reload
314314
dispatcher batch/concurrency limits plus retry policy; re-enabling mail or
315315
changing SMTP settings still requires restart. Users can inspect and update
316-
notification settings and per-event preferences under
316+
notification settings, preferred locale, and per-event preferences under
317317
`/user/notification/preferences`; admins can manage notification event
318318
types under `/admin/notification-event-types` and prune old terminal outbox
319319
jobs under `/admin/email-jobs/prune`.

‎docs/refactoring/mail.md‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222

2323
2. **`MailConfig` 当前已进入 monoengine 的编译 Config。** 结构位于 `src/config/model.rs`,字段包括 `enabled`、`provider`(默认 `smtp`)、`smtp_host`、`smtp_port`、`username`、兼容期 `password`、推荐的 `password_ref`、`from`、`starttls`、`dispatcher_batch_size`、`dispatcher_max_in_flight`、`retry_max_attempts`、`retry_backoff_base_secs`、`retry_backoff_max_secs`;`password` / `password_ref` 互斥且只适用于 SMTP provider,`mail.enabled = true` 且 `provider = "smtp"` 时要求 `smtp_host` / `from` 非空,dispatcher 限流值与 retry policy 值要求非零。
2424

25-
3. **Notification 系统已接入主 crate,并在 mail 启用时启动 dispatcher**。`src/notification/{dispatcher, triggers, mod}.rs` + callisto 中的 `email_jobs`、`notification_event_types`、`user_notification_settings`、`user_notification_preferences` 等实体存在,触发器逻辑(`on_cl_comment_created` 等,尊重用户偏好)已从 mega 移植;`main.rs:18` 已声明 `mod notification;`,`AppContext::new` 在 vault 之后通过 `mail::mailer_from_config` 构造 SMTP 或 console mailer、创建 `EmailDispatcher` 并 `tokio::spawn`。失败投递已有可配置 retry + dead-letter,dispatcher tick 已具备可配置批次/并发限流、结构化汇总日志、stale `sending` job 恢复和批次背压测试;邮件作业管理 API 首批已落地(admin-only list / stats / failed retry);admin 事件类型 API 首批已落地(list / upsert);用户自助通知偏好 API 首批已落地(列出当前用户 settings/event prefs、更新 global enabled / delivery mode、批量或单个 event preference);基础模板系统已落地并扩展出 template registry + locale fallback,CL 评论邮件已通过 registry 渲染 subject/html/text 并默认 HTML 转义变量;mailer 层已支持 HTML/Text + 附件 multipart 构造。当前仍需补齐真实 SMTP/Mailpit、更多业务触发器调用面和更完整运维面。
25+
3. **Notification 系统已接入主 crate,并在 mail 启用时启动 dispatcher**。`src/notification/{dispatcher, triggers, mod}.rs` + callisto 中的 `email_jobs`、`notification_event_types`、`user_notification_settings`、`user_notification_preferences` 等实体存在,触发器逻辑(`on_cl_comment_created` 等,尊重用户偏好)已从 mega 移植;`main.rs:18` 已声明 `mod notification;`,`AppContext::new` 在 vault 之后通过 `mail::mailer_from_config` 构造 SMTP 或 console mailer、创建 `EmailDispatcher` 并 `tokio::spawn`。失败投递已有可配置 retry + dead-letter,dispatcher tick 已具备可配置批次/并发限流、结构化汇总日志、stale `sending` job 恢复和批次背压测试;邮件作业管理 API 首批已落地(admin-only list / stats / failed retry);admin 事件类型 API 首批已落地(list / upsert);用户自助通知偏好 API 首批已落地(列出当前用户 settings/event prefs、更新 global enabled / delivery mode / preferred_locale、批量或单个 event preference);基础模板系统已落地并扩展出 template registry + locale fallback,CL 评论邮件已通过 registry 按 `user_notification_settings.preferred_locale` 渲染 subject/html/text 并默认 HTML 转义变量;mailer 层已支持 HTML/Text + 附件 multipart 构造。当前仍需补齐真实 SMTP/Mailpit、更多业务触发器调用面和更完整运维面。
2626

2727
4. **Vault 约束对 mail 的决定性影响**:`password_ref` 的真实值读取**必须**发生在 `VaultCore` 就绪之后。当前 `AppContext::new` 顺序为 `Storage::new (DB + object_storage + Buck 校验) → init_connection(redis) → VaultCore::new → 若 provider 为 SMTP 则解析 mail.password_ref → mailer_from_config + EmailDispatcher spawn → init_monorepo`。因此 mailer 的**构造时机**是 mail 模块设计的核心约束(详见「运行时注入与晚绑定构造」)。
2828

@@ -44,10 +44,10 @@
4444
| 晚于 Vault 的 mailer 构造 | **已落地** | `SmtpMailer::new` / `mailer_from_config` 本身是同步且轻量的,当前调用点在 `context/mod.rs:46-55`,严格晚于 `VaultCore::new`。 |
4545
| SecretRef / `password_ref` 支持 | **已落地首批** | 当前 `MailConfig.password: Option<SecretString>` 仅为兼容期入口,`password_ref: Option<SecretRef>` 为推荐路径;两者互斥且仅适用于 `provider = "smtp"`,且 `mail.password_ref` / `config secret mail.password` 只接受 `vault://secret/config/<profile>/mail/password#<field>` namespace。`AppContext::new` 在 vault 就绪后通过 resolver 解析 SMTP `password_ref` 并构造 SMTP mailer。 |
4646
| 多种后端(SES、SendGrid 等) | **首批本地 provider 已实现** | 已支持 `provider = "smtp"` 与 `provider = "console"`;console provider 用于本地/dev/CI 干跑,只记录收件人、主题和正文长度,不使用 SMTP 凭据。仍无 SES、SendGrid 等真实第三方 provider。 |
47-
| 模板 / 富文本 / 附件 | **模板 registry + HTML/Text + mailer 附件** | `src/mail/template.rs` 已提供轻量 `MailTemplate`、`MailTemplateRegistry`、`LocalizedMailTemplate` 和 `MailTemplateKey`,支持 `{{var}}` 渲染、HTML 变量默认转义、缺失变量脱敏诊断、按 locale 查找以及 language/default fallback;CL 评论触发器已改为 registry 渲染 subject/html/text。`send_html(to, subject, html, text?)` 实现 alternative multipart;`send_html_with_attachments(...)` + `MailAttachment` 已支持 SMTP mixed multipart 附件构造,console provider 只记录附件数量/字节数且不输出正文。仍无外部模板引擎、用户级 locale 来源或 outbox 级附件持久化/管理。 |
47+
| 模板 / 富文本 / 附件 | **模板 registry + HTML/Text + 用户 locale + mailer 附件** | `src/mail/template.rs` 已提供轻量 `MailTemplate`、`MailTemplateRegistry`、`LocalizedMailTemplate` 和 `MailTemplateKey`,支持 `{{var}}` 渲染、HTML 变量默认转义、缺失变量脱敏诊断、按 locale 查找以及 language/default fallback;CL 评论触发器已按每个收件人的 `user_notification_settings.preferred_locale` 渲染 subject/html/text,并提供 `zh-CN` 首个本地化模板。`send_html(to, subject, html, text?)` 实现 alternative multipart;`send_html_with_attachments(...)` + `MailAttachment` 已支持 SMTP mixed multipart 附件构造,console provider 只记录附件数量/字节数且不输出正文。仍无外部模板引擎、outbox 级附件持久化/管理。 |
4848
| 与 user_notification_* / 事件类型 的完整联动 | **实体+存储+触发器骨架存在,API 首批落地** | callisto 实体 + NotificationStorage 方法 + triggers(cl.comment 等)已移植自 mega,dispatcher 常驻任务、mailer 注入和失败 dead-letter 基线已接入;admin-only 邮件作业管理 API 已支持按状态/用户/事件查询、状态统计、failed job 手动重排,以及旧 `sent`/`skipped` 终态 job 清理;admin-only 事件类型 API 已支持 list/upsert;用户自助 API 已支持查询当前用户 settings/event preference effective 状态,并更新 global enabled、delivery mode、批量或单个 event preference。仍缺更多业务触发器调用面、更完整观测和运维控制。 |
4949
| Profile / 热加载 / 集中校验对 mail 的支持 | **部分实现** | Profile、集中校验和 source warning 已接入 config 管线;热加载当前支持 `mail.enabled` true→false 关停 dispatcher,并支持 `mail.dispatcher_batch_size` / `mail.dispatcher_max_in_flight` 运行期调整 dispatcher 背压参数,以及 `mail.retry_max_attempts` / `mail.retry_backoff_base_secs` / `mail.retry_backoff_max_secs` 运行期调整 retry/dead-letter 策略。重新启用 mail、`mail.provider`、SMTP 参数和凭据变更仍要求重启或后续动态 mailer 重建设计,该边界已有 config reload restart-required 矩阵测试。 |
50-
| 测试与 CI 覆盖 | **部分实现** | mail 自身有构造/消息验证、provider 工厂、console provider、SMTP 附件 mixed multipart 构造、附件 content-type 校验、模板渲染/缺失变量/HTML 转义、template registry、locale fallback 测试;dispatcher 有使用 Noop 的集成风格测试(需 DB + migration);已覆盖 `password_ref` 解析失败脱敏、坏配置不 panic、`mail.enabled` 关停热加载、dispatcher 批次/并发限流热加载、retry policy 热加载、provider/SMTP 参数/凭据重配只报告需重启、失败发送的 retry/dead-letter disposition、dispatcher 有界并发、单 tick 批次背压、stale `sending` 恢复、storage-level 并发 claim 竞争、邮件作业 list/stats/failed retry 管理原语、用户 notification preference/settings response 映射和 update payload 校验,以及 CL 评论触发器 registry 模板渲染。仍缺真实 SMTP/Mailpit、长时间高水位背压压测和真实多进程 claim 竞争矩阵。 |
50+
| 测试与 CI 覆盖 | **部分实现** | mail 自身有构造/消息验证、provider 工厂、console provider、SMTP 附件 mixed multipart 构造、附件 content-type 校验、模板渲染/缺失变量/HTML 转义、template registry、locale fallback 测试;dispatcher 有使用 Noop 的集成风格测试(需 DB + migration);已覆盖 `password_ref` 解析失败脱敏、坏配置不 panic、`mail.enabled` 关停热加载、dispatcher 批次/并发限流热加载、retry policy 热加载、provider/SMTP 参数/凭据重配只报告需重启、失败发送的 retry/dead-letter disposition、dispatcher 有界并发、单 tick 批次背压、stale `sending` 恢复、storage-level 并发 claim 竞争、邮件作业 list/stats/failed retry 管理原语、用户 notification preference/settings response 映射和 update payload 校验、用户 preferred_locale 存储/响应校验,以及 CL 评论触发器按收件人 locale 的 registry 模板渲染。仍缺真实 SMTP/Mailpit、长时间高水位背压压测和真实多进程 claim 竞争矩阵。 |
5151

5252
**已知加载/启动/安全风险点(必须在相应阶段消除,与 config.md 风险点重叠)**:
5353
- mailer 或 dispatcher 若被移动到 Storage::new / vault 前路径,会违反 vault 就绪顺序;当前代码位置正确,但需防止后续回归。
@@ -210,7 +210,7 @@ ConfigLoader + Config::new (含未解析 SecretRef 的 mail)
210210

211211
**阶段 2(已完成首批)**:与 config SecretRef 基础设施联动。`MailConfig` 已支持 `password_ref`,resolver 解析路径已在 `AppContext::new` 中落地,`config secret set/check` 已支持 mail password 引用。剩余是继续治理兼容期明文 `password` 的退场策略。
212212

213-
**阶段 3(已完成首批管理面 + template registry + 本地 provider + mailer 附件 + dispatcher 限流/退信配置)**:邮件作业管理 API 已先落地 admin-only `email-jobs/list`、`email-jobs/stats`、`email-jobs/{id}/retry` 和 `email-jobs/prune`,支持查询 outbox、按状态统计、将 `failed` job 重新排回 `pending`,以及按保留期清理旧 `sent`/`skipped` 终态 job;admin-only 事件类型 API 已支持 `notification-event-types` list/upsert;用户自助偏好 API 已支持 `GET /user/notification/preferences`、`PUT /user/notification/preferences` 和 `PUT /user/notification/preferences/{event_type_code}`,覆盖 global enabled、delivery mode、批量或单个 event preference;`src/mail/template.rs` 已提供基础模板渲染、template registry 和 locale fallback,CL 评论邮件已改为通过 registry 生成 subject/html/text 并默认 HTML 转义变量;`mail.provider = "console"` 已提供本地/dev/CI 干跑 provider;`MailAttachment` + `send_html_with_attachments(...)` 已支持 SMTP 附件 multipart 构造;`mail.dispatcher_batch_size` / `mail.dispatcher_max_in_flight` 已支持运行期调整 dispatcher 背压;`mail.retry_max_attempts` / `mail.retry_backoff_base_secs` / `mail.retry_backoff_max_secs` 已支持运行期调整 retry/dead-letter 策略。剩余是真实第三方 Provider 扩展(SES/SendGrid 等)、外部/用户可配置模板、用户级 locale 来源、outbox 级附件持久化/管理,以及更完整的管理端编辑/审计能力。
213+
**阶段 3(已完成首批管理面 + template registry + 用户 locale + 本地 provider + mailer 附件 + dispatcher 限流/退信配置)**:邮件作业管理 API 已先落地 admin-only `email-jobs/list`、`email-jobs/stats`、`email-jobs/{id}/retry` 和 `email-jobs/prune`,支持查询 outbox、按状态统计、将 `failed` job 重新排回 `pending`,以及按保留期清理旧 `sent`/`skipped` 终态 job;admin-only 事件类型 API 已支持 `notification-event-types` list/upsert;用户自助偏好 API 已支持 `GET /user/notification/preferences`、`PUT /user/notification/preferences` 和 `PUT /user/notification/preferences/{event_type_code}`,覆盖 global enabled、delivery mode、preferred_locale、批量或单个 event preference;`src/mail/template.rs` 已提供基础模板渲染、template registry 和 locale fallback,CL 评论邮件已改为通过 registry 按收件人 locale 生成 subject/html/text 并默认 HTML 转义变量;`mail.provider = "console"` 已提供本地/dev/CI 干跑 provider;`MailAttachment` + `send_html_with_attachments(...)` 已支持 SMTP 附件 multipart 构造;`mail.dispatcher_batch_size` / `mail.dispatcher_max_in_flight` 已支持运行期调整 dispatcher 背压;`mail.retry_max_attempts` / `mail.retry_backoff_base_secs` / `mail.retry_backoff_max_secs` 已支持运行期调整 retry/dead-letter 策略。剩余是真实第三方 Provider 扩展(SES/SendGrid 等)、外部/用户可配置模板、outbox 级附件持久化/管理,以及更完整的管理端编辑/审计能力。
214214

215215
**阶段 4**:Profile 感知的 mail 配置、热加载支持(当前已支持 `mail.enabled` true→false 关停 dispatcher,并支持 dispatcher batch / max-in-flight 与 retry policy 热更新;重新启用、from/SMTP/凭据变更仍需重启或后续动态 mailer 重建设计,且该重启边界已有 reload 测试覆盖)、更强的可观测(发送指标、链路追踪)。
216216

0 commit comments

Comments
 (0)