尧图精选

Tabby 邮件模板开发指南:从 React Email 组件到 SMTP 发送的完整链路

🕒 发布时间:2026/9/10 12:04:27 📁 来源:尧图网络
Tabby 邮件模板开发指南从 React Email 组件到 SMTP 发送的完整链路【免费下载链接】tabbySelf-hosted AI coding assistant项目地址: https://gitcode.com/GitHub_Trending/tab/tabby本文以 Tabby 仓库中ee/tabby-email模块为核心系统讲解如何为 Tabby自托管 AI 编程助手新增一封邮件模板并打通「React Email 组件编写 → 本地预览 → HTML 生成 → Rust 模板渲染 → SMTP 发送」的完整流水线。读完本文你将掌握 Tabby 邮件系统的目录结构、占位符替换约定、make update-email-templates的产物同步机制以及EmailServicetrait 的注册与实现方式能够独立为 Tabby 增加邀请、密码重置、注册成功之外的任意业务邮件。一、Tabby 邮件体系的整体架构Tabby 的邮件功能采用「前端组件 后端渲染 异步发送」的分层设计涉及以下关键模块ee/tabby-emailReact Email 模板源码目录存放所有邮件的 TSX 组件ee/tabby-webserver/email_templates由 TSX 渲染生成的静态 HTML 产物目录ee/tabby-webserver/src/service/email/templates.rsRust 端邮件模板渲染层负责把占位符替换为真实数据ee/tabby-webserver/src/service/email/mod.rsSMTP 连接管理与邮件发送实现ee/tabby-schema/src/schema/email.rsEmailServicetrait 与邮件设置SMTP、加密、认证方式的类型定义。整个链路可以概括为*.tsx 邮件组件 │ react-emailemail build email export ▼ ee/tabby-email/out/*.html │ make同步到 ee/tabby-webserver/email_templates ▼ email_templates/*.html │ templates.rs 的 include_str! 占位符替换{{EMAIL}}、{{CODE}}、{{EXTERNAL_URL}} ▼ 完整的 HTML 邮件正文 │ lettre 异步 SMTP 发送后台任务 ▼ 收件人邮箱其中 TSX 组件中书写的是{{EMAIL}}这类模板占位符渲染成 HTML 后占位符原样保留真正的内容注入发生在 Rust 端运行时替换。这一约定是整个邮件体系的灵魂理解它才能正确编写新模板。二、环境准备与项目依赖进入模板开发目录前先确认ee/tabby-email的依赖配置。其 package.json 内容如下{ name: emails, version: 0.0.19, private: true, scripts: { build: email build email export, dev: email dev }, devDependencies: { react-email: 2.1.3, react-email/components: 0.0.18, react-email/tailwind: 0.0.17, react: 18.2.0 } }react-email是核心渲染引擎提供 CLI 命令email dev启动本地预览服务器email build email export将 TSX 编译为静态 HTMLreact-email/components提供Button、Heading、Hr、Link、Section、Text等邮件友好组件react-email/tailwind允许在邮件模板中直接使用 Tailwind 工具类如text-[24px]、bg-[#645740]由公共布局组件引入。三、启动本地预览yarn yarn dev在 ee/tabby-email/emails 目录下按 README 指引执行yarn yarn dev执行后react-email会启动一个本地 Web 服务器并在终端打印预览链接默认通常为http://localhost:3000之类地址。打开该链接即可在浏览器中逐个查看emails目录下所有模板的渲染效果并针对不同PreviewProps预览不同数据形态下的展示效果。这一步骤是编写模板时的核心调试手段无需配置任何 SMTP 服务即可快速验证邮件在真实客户端中的排版与样式。四、编写新模板复制现有模板并理解结构README 给出的建议是复制现有邮件模板来创建新邮件。以 emails/invitation.tsx 为蓝本可以看到 Tabby 邮件模板的典型结构import { Button, Heading, Hr, Link, Section, Text } from react-email/components; import * as React from react; import RootLayout from ../components/root-layout; interface Invitation { email?: string; inviteLink?: string; } export const Invitation ({ email {{EMAIL}}, inviteLink {{EXTERNAL_URL}}/auth/signup?invitationCode{{CODE}}email{{EMAIL}}, }: Invitation) { const title Youve been invited to join a Tabby server!; return ( RootLayout previewText{title} Heading classNametext-black text-[24px] font-normal text-center p-0 mb-[30px] mx-0 {title} /Heading Text classNametext-black text-[14px] leading-[24px] Hello, /Text Text classNametext-black text-[14px] leading-[24px] You have been invited to join a strongTabby/strong Server, where you can tap into AI-driven code completions and chat assistants. /Text Section classNametext-center mt-[32px] mb-[32px] Button classNamebg-[#645740] rounded-md text-white text-sm font-semibold no-underline text-center px-5 py-3 href{inviteLink} Accept Invitation /Button /Section Text classNametext-black text-[14px] leading-[24px] or copy and paste this URL into your browser:{ } Link href{inviteLink} classNametext-blue-600 no-underline {inviteLink} /Link /Text Hr classNameborder border-solid border-[#eaeaea] my-[26px] mx-0 w-full / Text classNametext-[#666666] text-[12px] leading-[24px] This invitation was intended for{ } span classNametext-black{email}/span. If you were not expecting this invitation, you can ignore this email. /Text /RootLayout ); }; Invitation.PreviewProps { email: usertabbyml.com, inviteLink: http://localhost:8080/auth/signup?invitationCode{{CODE}}email{{EMAIL}}, } as Invitation; export default Invitation;从中提炼新模板需要遵守的四条约定组件默认参数即占位符email {{EMAIL}}、inviteLink {{EXTERNAL_URL}}/auth/signup?...这类默认值中的{{XXX}}就是后续 Rust 端要替换的占位符。默认参数保证了组件在无 props 传入时依然可渲染同时也定义了 HTML 产物中的占位符格式统一使用公共布局所有模板都应包裹在RootLayout来自 components/root-layout.tsx中通过previewText传入邮件预览摘要。公共布局统一了页面底色bg-[#FBF9F5]、卡片容器bg-[#E8E2D2]、字体与圆角风格并引入Preview组件提供 PreviewProps为组件声明PreviewProps静态属性供email dev预览时渲染示例数据默认导出组件每个模板文件末尾export default确保 react-email 能发现并导出该模板。其余模板password_reset.tsx、signup_success.tsx、test.tsx遵循同样的模式。例如密码重置模板使用resetLink {{EXTERNAL_URL}}/auth/reset-password?code{{CODE}}注册成功模板则使用link {{EXTERNAL_URL}}指向实例主页。五、生成 HTML 产物并同步模板开发完成后需要把 TSX 编译为 HTML 静态文件供 Rust 端在编译期嵌入。README 指示执行make update-email-templatespackage.json中对应的构建脚本是email build email export产物输出到ee/tabby-email/out目录。需要说明的是在当前仓库的 Makefile 中实际执行「将ee/tabby-email/out覆盖到ee/tabby-webserver/email_templates」这一同步动作的是update-ui目标其核心命令为update-ui: pnpm build rm -rf ee/tabby-webserver/ui cp -R ee/tabby-ui/out ee/tabby-webserver/ui rm -rf ee/tabby-webserver/email_templates cp -R ee/tabby-email/out ee/tabby-webserver/email_templates也就是说无论通过哪个目标触发最终都要保证 ee/tabby-webserver/email_templates 下存在对应名称的 HTML 文件当前已有invitation.html、password_reset.html、signup_success.html、test.html四个产物。Rust 端在编译期通过include_str!读取这些 HTML因此每新增一个模板都必须重新生成并同步 HTML否则 Rust 侧找不到对应文件。六、Rust 端注册模板渲染函数生成 HTML 之后进入 Rust 端。打开 ee/tabby-webserver/src/service/email/templates.rs可以看到占位符替换的核心实现fn format_email(body: static str, replacements: [(str, str)]) - String { let mut body body.to_string(); for (name, replacement) in replacements { body body.replace(name, replacement); } body } macro_rules! template_email { ($lit:ident: $($arg:ident),*) { { let contents include_str!(concat!( ../../../email_templates/, stringify!($lit), .html )); format_email(contents, [ $( (format!({{{{{}}}}}, stringify!($arg).to_uppercase()), $arg) ),* ]) } }; }其原理值得拆解include_str!在编译期将ee/tabby-webserver/email_templates/模板名.html的内容直接嵌入二进制避免运行时读文件template_email!宏把传入的参数名如email转成大写并包上{{ }}生成{{EMAIL}}形式的键再用String::replace全局替换因此 TSX 中写下的占位符名称必须与 Rust 函数参数名一一对应大小写自动转换。现有模板函数如下新模板就仿照它们添加pub fn invitation(external_url: str, code: str, email: str) - String { template_email!(invitation: external_url, code, email) } pub fn test() - String { template_email!(test: ) } pub fn password_reset(external_url: str, email: str, code: str) - String { template_email!(password_reset: external_url, email, code) } pub fn signup_success(external_url: str, email: str) - String { template_email!(signup_success: external_url, email) }从参数命名可以看出{{EXTERNAL_URL}}对应external_url来自网络设置中的external_url即 Tabby 实例的对外地址{{CODE}}对应code邀请码或重置码{{EMAIL}}对应email。七、在 EmailService trait 中添加发送方法模板渲染函数就绪后需要在EmailServicetrait 上声明发送方法并在实现中接入。trait 定义位于 ee/tabby-schema/src/schema/email.rs#[async_trait] pub trait EmailService: Send Sync { async fn read_setting(self) - ResultOptionEmailSetting; async fn update_setting(self, input: EmailSettingInput) - Result(); async fn delete_setting(self) - Result(); async fn send_test(self, to: String) - ResultJoinHandle(); async fn send_password_reset(self, to: String, code: String) - ResultJoinHandle(); async fn send_invitation(self, email: String, code: String) - ResultJoinHandle(); async fn send_signup(self, email: String) - ResultJoinHandle(); }注意所有发送方法返回JoinHandle()——这表明发送是异步后台任务调用方拿到句柄后不阻塞业务主流程。同文件还定义了邮件服务相关的枚举与设置类型EncryptionStartTls、SslTls、None三种加密方式AuthMethodNone、Plain、Login三种 SMTP 认证方式EmailSetting包含smtp_username、smtp_server、smtp_port、from_address、encryption、auth_methodEmailSettingInput带校验注解from_address必须是合法邮箱smtp_port取值范围 1–65535。接着在 ee/tabby-webserver/src/service/email/mod.rs 的impl EmailService for EmailServiceImpl中实现新方法。以现有的send_invitation为例可以看到完整的三段式写法async fn send_invitation(self, email: String, code: String) - ResultJoinHandle() { let network_setting self.setting.read_network_setting().await?; let external_url network_setting.external_url; let body templates::invitation(external_url, code, email); self.send_email_in_background( email, Youve been invited to join a Tabby server!.into(), body, ) .await }即读取网络设置拿到external_url→ 调用templates模块的渲染函数生成正文 → 调用send_email_in_background投递。新模板只需照抄这个模式替换模板函数、主题与参数即可。八、底层发送链路SMTP 连接与后台投递send_email_in_background位于 ee/tabby-webserver/src/service/email/mod.rs负责真正的投递关键逻辑如下async fn send_email_in_background( self, to: String, subject: String, message: String, ) - ResultJoinHandle() { let smtp_server self.smtp_server.clone(); // Check if the email service is actually configured. if smtp_server.read().await.is_none() { return Err(CoreError::EmailNotConfigured); } let from self.from.read().await.clone(); let address_from to_address(from)?; let address_to to_address(to)?; let msg MessageBuilder::new() .subject(subject) .header(ContentType::TEXT_HTML) .from(Mailbox::new(Some(Tabby Admin.to_owned()), address_from)) .to(Mailbox::new(None, address_to)) .body(message) .map_err(anyhow::Error::msg)?; Ok(tokio::spawn(async move { if let Some(smtp_server) *(smtp_server.read().await) { match smtp_server.send(msg).await.map_err(anyhow::Error::msg) { Ok(_) {} Err(err) { warn!(Failed to send mail due to {}, err); } }; } })) }值得注意的实现细节未配置即报错若smtp_server连接为None说明管理员尚未在后台配置 SMTP直接返回EmailNotConfigured错误不会静默丢失邮件HTML 邮件通过ContentType::TEXT_HTML声明正文为 HTML发件人显示名为固定的Tabby Admin后台投递tokio::spawn将 SMTP 发送放入独立任务发送失败只记录warn!日志不影响主流程。SMTP 连接的建立由make_smtp_builder完成它根据Encryption选择不同 TLS 策略StartTls使用Tls::RequiredSslTls使用Tls::WrapperNone则为明文连接。此外还支持通过环境变量TABBY_WEBSERVER_EMAIL_CERT注入 PEM 格式的自定义根证书适用于自签名证书的内部 SMTP 服务器。连接凭据更新时会调用reset_smtp_connection重建连接删除设置时调用shutdown_smtp_connection关闭连接见update_setting/delete_setting的实现。九、用测试验证新模板仓库在mod.rs内自带了针对邮件服务的单元测试验证新模板与发送链路时可参考test_update_email_with_service验证设置更新后可正确读回覆盖 SMTP 用户名、端口、加密方式等字段test_send_email启动一个内存态测试 SMTP 服务器TestEmailServer实际发送邀请邮件与测试邮件断言收件人地址与发件人正确test_send_test_email发送测试邮件并断言邮件主题包含ready to go。这些测试直接调用了EmailServiceImpl与模板渲染函数证明「模板渲染 → SMTP 投递」整条链路是端到端可验证的。新增模板后可以仿照test_send_email编写对应的发送断言测试测试工具类位于 ee/tabby-webserver/src/service/email/testutils.rs。同时若你已配置好 SMTP也可通过send_test发送一封「Your mail server is ready to go!」的测试邮件对应模板 emails/test.tsx快速确认邮件服务器配置无误。十、端到端操作清单将本文内容收敛为一张可执行的 Checklist用于新增一封 Tabby 邮件模板在 ee/tabby-email/emails 下复制一个现有模板重命名为my_new_email.tsx编写组件结构与{{XXX}}占位符在ee/tabby-email目录执行yarn yarn dev通过本地预览确认排版与样式重新生成 HTML 并同步到ee/tabby-webserver/email_templatesREADME 指示的make update-email-templates当前仓库 Makefile 中执行cp -R ee/tabby-email/out ee/tabby-webserver/email_templates的目标为update-ui确认生成my_new_email.html在 ee/tabby-webserver/src/service/email/templates.rs 用template_email!宏添加pub fn my_new_email(...)渲染函数在 ee/tabby-schema/src/schema/email.rs 的EmailServicetrait 中添加send_my_new_email(...)方法签名在 ee/tabby-webserver/src/service/email/mod.rs 的impl EmailService中实现该方法复用send_email_in_background后台投递可选仿照现有测试编写发送断言运行测试验证整条链路。完成以上步骤后新邮件即与邀请、密码重置、注册成功邮件一样纳入 Tabby 统一的 SMTP 发送体系可在任意业务代码中通过EmailService触发。【免费下载链接】tabbySelf-hosted AI coding assistant项目地址: https://gitcode.com/GitHub_Trending/tab/tabby创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
上一篇/下一篇内容由系统自动关联 返回资讯列表 →