邮件

默认使用 Resend,也可以改用 Cloudflare Email。认证邮件已内置,新的业务邮件统一使用 emailService 和现有模板。

Saavo 模板已经提供统一的邮件发送能力,并内置了一组认证相关的邮件模板。注册验证、找回密码、修改邮箱和两步验证变更等流程都会通过同一套邮件服务发送。

开发自己的产品时,通常不需要在页面或业务代码中直接调用 Resend、Cloudflare 等邮件服务 SDK。只需要选择合适的邮件服务,配置发件人信息,然后通过 emailService 统一发送即可。本地开发环境不会真正发送邮件,而是将邮件内容输出到控制台,方便调试。

已有功能

模板初始化后,以下邮件功能开箱即用:

功能默认值说明
邮件发送方式Resendconfig/deploy.ts → emailProvider.type
发件人config/base.ts → fromEmailAddress显示名和地址
回复地址supportEmail用户回复时使用
本地开发发送控制台预览不调用真实传输
发送限流每邮箱每天 5 封生产环境超限会失败

已经注册的模板及当前触发方式:

模板当前用途
register注册后的验证邮件,由注册 Reaction 触发
verifyEmail邮箱验证邮件
accountVerification账号高敏操作验证邮件
passwordReset密码重置邮件
emailChanged邮箱修改安全通知邮件
twoFactorSecurityChanged两步验证设置变更后的安全通知邮件
welcome注册成功欢迎邮件,但当前没有被使用

业务侧通过 src/core/services/email 的 emailService 发送。不要在页面组件里直接使用第三方邮件服务的 SDK。

先体验邮件发送

用默认配置注册一个账号。开发服务器控制台会出现类似下面的日志:

[DEV EMAIL PREVIEW] {
  recipients: [ 'you@example.com' ],
  subject: '...',
  text: '...Your verification code is ...'
}

用日志里的验证码即可完成邮箱验证。这与认证文档中的流程一致。

开发环境邮件预览

生产环境或预览环境才会真正发送邮件,开发模式即使触发了发送限流,也只打日志,仍会走完预览流程。

注意事项

开启强制邮箱验证前,必须先确认生产环境能真正发送邮件。否则新用户将卡在验证步骤。

配置邮件

选择发送方式

默认:

emailProvider: {
    type: 'resend',
}

Resend 是模板当前的默认选择,还需要在生产环境配置 RESEND_API_KEY,并完成域名验证。

改用 Cloudflare Email:

emailProvider: {
    type: 'cloudflare',
}

Cloudflare Email 使用 wrangler.jsonc 中名为 EMAIL 的 send_email 绑定。切换前要先确认当前 Cloudflare 账户已经具备向真实用户发送邮件的权限。

修改发件人

fromEmailAddress: {
    name: 'Acme',
    email: 'noreply@your-domain.com',
},
supportEmail: 'support@your-domain.com',

生产环境的 From 必须属于邮件服务已经允许使用的域名,并与这里的地址一致。发件人信息和回复地址是全局配置,所有邮件都会使用相同的设置。

调整发送频率限制

spam: {
    resourceProtection: {
        emailSendService: {
            lifetimeDuration: '1d',
            maxEmailsPerUser: 5,
            idleTimeout: '2d',
        },
    },
}

这是按收件邮箱计数的保护,不是营销邮件配额。认证邮件也受它约束。不要为了测试方便在生产里把上限调得很高,否则可能会影响实际用户的使用。

准备邮件服务

Cloudflare Email 需要在 Cloudflare 中设置用于发送邮件的域名,并完成 Worker 绑定。Resend 需要 API Key 和已经通过验证的域名。本地开发不依赖这些配置也能预览邮件,正式对外前必须用非开发环境发出一封真实邮件。

密钥和域名说明见:

配置 Resend

在业务中发送邮件

新的业务邮件应复用 emailService,而不是另写 SMTP 客户端。

现成方法:

import emailService from '@/core/services/email';

await emailService.sendVerifyEmail(c, {
    email: user.email,
    name: user.displayName ?? user.userName,
    verification: { type: 'code', code },
});

也有 sendRegisterEmail、sendForgotPasswordEmail、sendAccountVerificationEmail。通用入口接收 WorkerCtx:

await emailService.send(workerCtx, templateKind, email, data)

HTTP 请求里应使用 resolveFetchWorkerCtx(c) 得到 WorkerCtx,不要把 Hono context 直接传进通用 send。

新场景要么复用现有模板,要么:

  1. 在 src/libs/email/template 增加模板
  2. 在 factory.ts 的 EMAIL_TEMPLATES 注册
  3. 再包一层明确的 send* 方法

对于可能重试的业务流程,建议优先通过 Reaction 中的 SendEmailCommand 发送邮件,这样可以更好地处理重复执行和异步投递。只有当前请求必须立即获取邮件发送结果时,才直接调用 emailService。

当前内置模板默认输出纯文本。邮件服务和 Resend、Cloudflare 两个发送适配器都支持 html 字段,需要 HTML 邮件时,可以在模板中生成并返回 HTML,同时保留纯文本内容。

上线检查

邮件发送功能会影响注册和找回密码等关键流程,上线前建议至少确认:

  • fromEmailAddress 和 supportEmail 已换成自己的地址。
  • 生产环境 emailProvider.type 与真实密钥匹配。
  • 用于发送邮件的域名已在 Cloudflare 或 Resend 中完成验证。
  • 用生产配置实际发出注册或验证邮件,收件箱能收到。
  • 如开启强制邮箱验证,未收到邮件的用户无法继续使用产品——必须先保证邮件能够稳定发送。
  • 发送限流在异常刷接口时会生效。

不要只用开发控制台预览代替生产环境中的邮件发送测试。

常见问题

接下来

根据接下来要开发的功能,可以继续阅读:

大多数产品走完本章后,只需要选择邮件发送方式并完成域名验证。

新的产品邮件按现有模板方式增加,而不是在页面里直接调用邮件服务 SDK。