ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Bitwarden Server 邮件模板体系全解析:MJML 源模板与 Handlebars 渲染的双层邮件生成管线

Bitwarden Server 邮件模板体系全解析:MJML 源模板与 Handlebars 渲染的双层邮件生成管线 Bitwarden Server 邮件模板体系全解析MJML 源模板与 Handlebars 渲染的双层邮件生成管线【免费下载链接】serverBitwarden infrastructure/backend (API, database, Docker, etc).项目地址: https://gitcode.com/GitHub_Trending/ser/server本篇指南围绕 src/Core/MailTemplates/README.md 展开系统讲解 Bitwarden Server 后端API、数据库、Docker 基础设施中全部用户邮件的模板工程化方案从*.mjml源模板编写、Node 构建管线编译到*.html.hbs与*.txt.hbs双版本模板被IMailService/IMailer消费的完整链路并涵盖assets.bitwarden.com邮件资产的托管与更新流程。读完本文你将掌握 Bitwarden 邮件模板的三种文件类型、构建命令、自定义 MJML 组件开发、Handlebars 变量注入规范以及从源码到可发送邮件的完整实战工作流。一、邮件模板体系总览三种文件类型各司其职Bitwarden Server 使用MJML来生成邮件服务发送给用户的 HTML。围绕邮件生成流程的不同阶段仓库在 src/Core/MailTemplates 目录下维护了三种文件类型文件类型角色定位是否手工维护关键特征*.mjml邮件源模板是开发者编写组件化标记语言位于Mjml/子目录通过 Node 构建脚本编译*.html.hbs编译后的 HTML 邮件模板否构建产物内嵌 CSS 的跨客户端兼容 HTML叠加 Handlebars 动态插值语法*.txt.hbs纯文本邮件模板是开发者编写与*.html.hbs一一对应保证可访问性与投递兼容性整个渲染管线可以概括为一条单向数据流*.mjml 源模板 ──(npm run build)──► *.html.hbs ──(Handlebars 引擎)──► 最终 HTML 邮件 ▲ │ 变量注入 │ View ModelIMailService / IMailer 绑定*.txt.hbs不经过 MJML 编译直接以纯文本形式与 HTML 版本并行提供给邮件客户端。二、*.mjml源模板组件化邮件开发的起点2.1 MJML 与目录结构MJMLMailJet Markup Language是专为响应式邮件设计的标记语言其组件化开发特性显著提升了邮件代码的质量与可复用性——这也是 Bitwarden 选它的核心理由。在仓库中MJML 源模板统一存放在 src/Core/MailTemplates/Mjml 下其结构为emails/按业务域组织的邮件源模板例如 emails/Auth认证类邮件、AdminConsole/、Billing/等components/可复用的共享组件包括head.mjml、footer.mjml、logo.mjml以及mj-bw-hero.js、mj-bw-icon-row.js、mj-bw-simple-hero.js等自定义 JavaScript 组件package.json/build.js/.mjmlconfig构建脚本与组件注册配置。2.2 一个真实的 MJML 模板invite.mjml以 emails/invite.mjml 为例可以看到 MJML 源模板的典型写法——在mj-head中通过mj-include引入共享的head.mjml在mj-body中使用标准 MJML 标签mj-wrapper、mj-section、mj-column、mj-button、mj-text与自定义组件组合mjml mj-head mj-include path../components/head.mjml / /mj-head mj-body mj-wrapper css-classborder-fix padding20px 20px mj-bw-hero img-srchttps://assets.bitwarden.com/email/v1/business.png titleA Bitwarden member has invited you to Bitwarden Password Manager button-textFinish account setup button-url# / mj-section mj-column mj-button href#Join Organization Now/mj-button mj-text This invitation expires on bTuesday, January 23, 2024 2:59PM UTC/b. /mj-text /mj-column /mj-section mj-bw-learn-more-footer / /mj-wrapper mj-include path../components/footer.mjml / /mj-body /mjml这段模板体现了 Bitwarden 的两个核心复用机制mj-include引用静态共享片段head 与 footer自定义组件mj-bw-hero/mj-bw-learn-more-footer封装高频 UI 块。2.3 自定义组件mj-bw-hero 的实现MJML 支持通过自定义组件扩展能力组件本质上是返回 MJML 标记字符串的 JavaScript 类。查看 components/mj-bw-hero.js 的源码可以完整理解其机制const { BodyComponent } require(mjml-core); class MjBwHero extends BodyComponent { static dependencies { mj-column: [mj-bw-hero], // 声明允许的父级标签 mj-wrapper: [mj-bw-hero], mj-bw-hero: [], // 声明允许的子级标签 }; static allowedAttributes { img-src: string, // REQUIRED: 蓝色头部右侧展示的图片源 title: string, // REQUIRED: 说明邮件主要目的的大号文本 button-text: string, // OPTIONAL: 按钮上展示的文本 button-url: string, // OPTIONAL: 按钮点击跳转的 URL sub-title: string, // OPTIONAL: 提供补充上下文的较小文本 }; static defaultAttributes {}; // render() 方法根据属性拼装 MJML 字符串返回…… }组件属性由开发者自行定义可设为必填或可选render()方法根据传入属性动态拼装 MJML 标记例如只有同时提供button-text与button-url时才渲染按钮只有提供sub-title时才渲染副标题。此外组件还可以通过componentHeadStyle注入响应式 CSS如mj-bw-hero-responsive-img在小屏下隐藏配图。所有自定义组件必须在.mjmlconfig中注册才能被编译与渲染注册格式如下{ packages: [components/mj-bw-hero] }2.4 mj-include 与 head.mjml静态共享片段除了动态组件还可以用mj-include引用更静态的 MJML 模板例如在任意mj-wrapper内引入页脚mj-wrapper padding5px 20px 10px 20px mj-include path../../components/learn-more-footer.mjml / /mj-wrapper当前所有 MJML 模板都会在mj-head中引入 components/head.mjml它承载共享样式与格式确保全部邮件在视觉上保持一致。仓库文档也明确指出未来若需支持不同布局可能改变这一惯例并同步更新文档。三、构建管线把 MJML 编译为html.hbs3.1 npm 脚本总览MJML 构建工具链定义在 Mjml/package.json 中依赖mjml与mjml-core版本 4.15.3开发依赖nodemon与prettier。核心脚本如下npm ci # 编译 *.mjml → *.html输出到 ./out 目录 npm run build # 监听 *.mjml 与 *.js 文件变更并增量编译新增文件不会自动追踪需重新运行 npm run build:watch # 编译 *.mjml → *.html.hbs输出到 ./out 目录 npm run build:hbs # 编译并压缩minify*.html.hbs输出到 ./out 目录 npm run build:minify # 使用 prettier 统一格式化 npm run prettier脚本对应的定义如下build.js支持--hbs、--minify等命令行参数scripts: { build: node ./build.js, build:hbs: node ./build.js --hbs, build:minify: node ./build.js --hbs --minify, build:watch: nodemon ./build.js --watch emails --watch components --ext mjml,js, prettier: prettier --cache --write . }3.2 build.js 的实现细节build.js 是构建管线的核心其关键实现逻辑从源码结构看如下入参与出参输入目录固定为emails/输出目录为out/编译过程以glob递归查找所有*.mjml文件并显式排除**/components/**共享组件不单独产出编译选项调用mjml2html时设置validationLevel: strict严格校验、minify由--minify控制、filePath用于解析mj-include的相对路径以及mjmlConfigPath指向含.mjmlconfig的目录产物命名根据是否传入--hbs决定输出扩展名——*.html或*.html.hbs并按源文件目录结构在out/下保留相对层级错误处理编译出错时打印格式化错误信息并以退出码 1 结束进程同时汇总 Success/Failed 统计。3.3 构建产物去向构建产物是交付物build:hbs/build:minify生成的压缩版*.html.hbs必须被复制到运行时可被邮件服务读取的位置具体规则在第四章与第六章详述。四、*.html.hbsHandlebars 增强的 HTML 邮件模板*.html.hbs是 Bitwarden 平台所有 HTML 邮件的基础模板由 MJML 源文件编译而来并叠加 Handlebars 模板能力实现动态内容注入。4.1 生成过程源由Mjml/目录下的*.mjml文件构建生成。MJML 为开发者提供生成 HTML 的工具集而生成 HTML 并确保其可被IMailService实现访问是开发者的责任构建工具通过 Node 构建脚本npm run build完成脚本定义见 Mjml/package.json 与 Mjml/build.js输出内嵌 CSS 的跨客户端兼容 HTML最大化邮件客户端支持度模板引擎叠加 Handlebars 语法实现动态内容替换。4.2 Handlebars 语法与变量注入模板使用 Handlebars 双花括号语法做动态内容替换原文档给出的规范示例为!-- Example Handlebars usage -- h1Welcome {{userName}}!/h1 pYour organization {{organizationName}} has invited you to join./p a href{{actionUrl}}Accept Invitation/a变量类型简单变量{{userName}}、{{email}}、{{organizationName}}用于文本内容注入。变量命名规范开发指南统一使用 camelCase 保持一致性{{userName}}、{{organizationName}}URL 类变量使用描述性前缀{{actionUrl}}、{{logoUrl}}。4.3 布局 Partial 与三花括号在实际产物中还可以看到两个高级用法。以 Handlebars/Welcome.html.hbs 为例模板整体被{{#FullHtmlLayout}}/{{/FullHtmlLayout}}包裹——这是 Handlebars 的partial block 语法将内容注入到 Handlebars/Layouts 目录下的Full.html.hbs等共享布局模板中从而实现页眉、页脚、响应式样式的一次编写、全站复用。对于 URL 注入则使用三花括号{{{WebVaultUrl}}}不转义 HTML保证、?等查询参数按原样输出a href{{{WebVaultUrl}}}/?utm_sourcewelcome_emailutm_mediumemail ... Add passwords to your vault /a4.4 IMailService 的消费流程IMailService通过以下三步消费这些模板见 src/Core/Platform/Mail/IMailService.cs模板选择Template Selection服务根据邮件类型选择对应的.html.hbs模板模型绑定Model Binding视图模型View Model属性映射到 Handlebars 变量编译CompilationHandlebars 引擎处理变量并生成最终 HTML。4.5 开发与测试指南用真实视图模型数据验证 Handlebars 变量替换是否生效必要时确保变量缺失或为 null 时能够优雅降级graceful degradation校验 HTML 结构与可访问性合规性。五、*.txt.hbs不可忽视的纯文本版本*.txt.hbs为每封邮件提供纯文本版本是邮件可访问性与可投递性的关键保障。5.1 存在的意义可访问性屏幕阅读器与辅助技术对纯文本版本的解析往往更可靠邮件客户端兼容性部分客户端更偏好甚至只显示纯文本版本回退内容HTML 渲染失败时纯文本版本确保信息依然可读。5.2 结构规范纯文本模板使用与 HTML 模板相同的 Handlebars 语法{{variable}}做动态替换同时应遵循只包含核心消息内容不携带 HTML 格式使用换行与留白保证可读性所有重要链接以完整 URL 形式呈现通过间距与简单文本排版维持逻辑内容层级。以 Handlebars/Welcome.text.hbs 为例可以看到纯文本版本如何用分隔标题、用完整 URL 呈现链接、用空行分层Welcome to Bitwarden! Here are a few simple steps to get up and running with Bitwarden Password Manager: 1. Install the browser extension Autofill passwords, save new logins, access the password generator, and more from the Bitwarden browser extension. Install the extension (http://www.bitwarden.com/download) 2. Add passwords to your vault ... Add passwords to your vault ({{{WebVaultUrl}}}/?utm_sourcewelcome_emailutm_mediumemail)5.3 双版本发送机制IMailService发送邮件时自动同时使用两个版本HTML 版本来自*.html.hbs提供丰富的排版与样式纯文本版本来自*.txt.hbs作为文本替代内容邮件客户端根据用户偏好与能力自行选择展示哪个版本。5.4 开发指南每个*.html.hbs模板都必须有对应的*.txt.hbs文件内容保持简洁但完整——覆盖 HTML 版本的全部关键信息测试纯文本模板确保其可读且传达相同信息。六、邮件服务集成从 IMailService 到 IMailer 的演进6.1 遗留的 IMailService 接口src/Core/Platform/Mail/IMailService.cs 定义了 Bitwarden 历史上全部邮件发送契约涵盖欢迎邮件、组织邀请、两步验证、账单发票、紧急访问、Secrets Manager 等几十种场景如SendWelcomeEmailAsync、SendOrganizationInviteEmailsAsync、SendInvoiceUpcoming、SendTwoFactorEmailAsync等并附带大量 XML 文档注释说明每个方法的触发时机。但需要注意该接口已在源码中标记为废弃见第 15-16 行[Obsolete(The IMailService has been deprecated in favor of the IMailer. All new emails should be sent with an IMailer implementation.)] public interface IMailService6.2 新一代 IMailer 接口新的发送契约在 src/Core/Platform/Mail/Mailer/IMailer.cs 中定义接口被大幅简化——只需提供一个泛型方法public interface IMailer { public Task SendEmailT(BaseMailT message) where T : BaseMailView; }其约定是ViewModel、由 MJML 构建出的.html.hbs产物以及.text.hbs文件必须位于同一目录。这取代了旧接口中每个场景一个方法的模式新邮件一律通过IMailer发送。6.3 用 IMailer 测试邮件模板的完整流程在开发完 MJML 源模板后按以下步骤验证 Handlebars 变量是否正确填充运行npm run build:hbs将Mjml/out目录下构建出的所有*.html.hbs文件复制到IMailer期望的目录——即src/Core/MailTemplates/Mjml下的对应目录确保与相应 ViewModel 处于同一目录。若修改了共享组件务必复制并覆盖该目录下所有文件以捕获*.html.hbs中的变更运行会触发该邮件发送的代码进行验证。压缩后的html.hbs产物是交付物必须被放置到正确的src/Core/MailTemplates/Mjml目录下才能被IMailer实现使用。历史上对应IMailService的复制目标则是src/Core/MailTemplates/Handlebars/MJML目录随IMailService一并废弃新开发请遵循上文的IMailer流程。七、邮件资产的托管与更新7.1 资产托管体系邮件中引用的图片等静态资产托管在assets.bitwarden.com路径前缀为/email/v1对应由 SRE 团队管理的静态文件存储容器。例如https://assets.bitwarden.com/email/v1/mail-github.png这是任何环境下发送邮件所使用资产的统一 URL 前缀——无论开发、测试还是生产环境邮件中的资产地址都指向该处。7.2 添加、修改或删除资产的 Git 工作流资产的维护遵循标准的 Git PR 工作流资产本身存放在独立的 assets 仓库中完整步骤如下# 1. 克隆资产仓库 git clone gitgithub.com:bitwarden/assets.git # 2. 新建分支以进行并暂存所需变更 git checkout -b name-of-your-branch # 3. 添加并提交变更 git add path/to/your-asset.png git commit -m commit message # 4. 推送分支到远程仓库 git push origin branch之后打开 PR 等待评审PR 获得批准后即可合并合并动作会构建并部署assets.bitwarden.com的 GitHub Pages 站点。[!NOTE] 合并后的变更可能需要一些时间才能传播生效上线前需预留传播时间。八、开发工作流速查阶段操作说明编写源模板在Mjml/emails/业务域/下创建*.mjml可在Mjml/components/开发自定义组件实时预览npm run build:watch浏览器查看out/下编译出的 HTML改完刷新即可生成 Handlebars 产物npm run build:hbs或build:minify压缩版产物输出到Mjml/out/复制产物复制out/下全部*.html.hbs到对应 ViewModel 所在目录覆盖所有文件以捕获共享组件变更验证渲染运行触发邮件发送的代码通过IMailer实现验证变量填充新增资产走 assets 仓库的 Git PR 流程合并后自动部署到assets.bitwarden.com/email/v1/延伸阅读邮件模板总览文档本文的原始依据MJML 邮件模板开发文档构建命令、自定义组件与开发流程的官方说明MJML 构建脚本 与 构建配置编译管线的实现细节自定义组件示例组件化邮件开发的参考实现IMailService 接口已废弃与 IMailer 接口现行邮件发送契约的演进Handlebars 模板目录*.html.hbs、*.txt.hbs与共享布局Layouts/的真实产物。【免费下载链接】serverBitwarden infrastructure/backend (API, database, Docker, etc).项目地址: https://gitcode.com/GitHub_Trending/ser/server创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表