電子郵件

預設使用 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 完成驗證。
  • 使用正式環境設定實際寄出註冊或驗證郵件,且收件匣可以收到。
  • 若啟用強制電子郵件驗證,未收到郵件的使用者就無法繼續使用產品,因此必須先確保能穩定寄信。
  • 異常大量呼叫 API 時,寄送速率限制會生效。

不要只用開發主控台的預覽,代替正式環境的郵件寄送測試。

常見問題

接下來

依接下來要開發的功能,可以繼續閱讀:

大多數產品完成本章後,只需要選擇郵件寄送方式,並完成網域驗證。

新增產品郵件時,沿用現有範本的方式,不要在頁面中直接呼叫郵件服務 SDK。