Email

Uses Resend by default, with Cloudflare Email as an alternative. Authentication emails are built in. Use emailService and existing templates for new business emails.

The Saavo template provides a shared email service and a set of authentication email templates. Signup verification, password recovery, email address changes, and two-factor authentication changes all send through the same service.

When developing your own product, you usually do not need to call email provider SDKs such as Resend or Cloudflare directly from pages or business code. Choose an email provider, configure sender details, and send through emailService. Local development does not send real email. It prints the content to the console for debugging.

Available features

After template initialization, the following email features are available out of the box:

FeatureDefaultDescription
Email providerResendconfig/deploy.ts → emailProvider.type
Senderconfig/base.ts → fromEmailAddressDisplay name and address
Reply-to addresssupportEmailUsed when users reply
Local development sendingConsole previewDoes not invoke a real transport
Sending rate limit5 emails per recipient address per dayExceeding the limit fails in production

Registered templates and their current triggers:

TemplateCurrent purpose
registerPost-signup verification email, triggered by the signup Reaction
verifyEmailEmail verification
accountVerificationAccount verification for highly sensitive operations
passwordResetPassword reset
emailChangedSecurity notification for an email address change
twoFactorSecurityChangedSecurity notification after a two-factor authentication settings change
welcomeWelcome email after successful signup, currently unused

Business code sends through emailService in src/core/services/email. Do not use a third-party email SDK directly in page components.

Try sending email first

Register an account with the default configuration. The development server console shows a log like this:

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

Use the code in the log to verify the email address, following the same flow described in the authentication documentation.

Development email preview

Only production or preview environments send real email. In development mode, reaching the sending rate limit only writes a log, and the preview flow still completes.

Important

Before requiring email verification, you must confirm that production can send real email. Otherwise, new users will be stuck at the verification step.

Configure email

Choose a provider

Default:

emailProvider: {
    type: 'resend',
}

Resend is the template's current default. You also need to configure RESEND_API_KEY in production and verify the domain.

To use Cloudflare Email:

emailProvider: {
    type: 'cloudflare',
}

Cloudflare Email uses the send_email binding named EMAIL in wrangler.jsonc. Before switching, confirm that your Cloudflare account has permission to send email to real users.

Change the sender

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

The production From address must belong to a domain allowed by the email provider and match the configured address here. Sender details and the reply-to address are global settings shared by all emails.

Adjust sending rate limits

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

This protection counts messages per recipient email address. It is not a marketing email quota. Authentication emails are also subject to it. Do not set a very high production limit for testing convenience, as this may affect real users.

Prepare the email service

Cloudflare Email requires a sending domain configured in Cloudflare and a Worker binding. Resend requires an API key and a verified domain. Local email previews work without these settings, but you must send a real email from a non-development environment before making the product public.

For key and domain configuration, see:

Configure Resend

Send email from business code

New business emails should reuse emailService rather than introducing another SMTP client.

An existing method:

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

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

Other methods include sendRegisterEmail, sendForgotPasswordEmail, and sendAccountVerificationEmail. The generic entry point accepts WorkerCtx:

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

In HTTP requests, use resolveFetchWorkerCtx(c) to obtain WorkerCtx. Do not pass the Hono context directly to the generic send.

For a new use case, reuse an existing template or:

  1. Add a template in src/libs/email/template
  2. Register it in EMAIL_TEMPLATES in factory.ts
  3. Wrap it in a clearly named send* method

For business workflows that may retry, you should prefer SendEmailCommand in Reaction to better handle repeated execution and asynchronous delivery. Call emailService directly only when the current request must immediately obtain the sending result.

Built-in templates currently output plain text by default. The email service and both the Resend and Cloudflare adapters support an html field. For HTML email, generate and return HTML from the template while keeping the plain-text content.

Pre-launch checks

Email affects critical flows such as signup and password recovery. Before launch, you should confirm at least the following:

  • fromEmailAddress and supportEmail use your own addresses.
  • Production emailProvider.type matches the actual credentials.
  • The sending domain is verified in Cloudflare or Resend.
  • A real signup or verification email sent with production configuration arrives in the inbox.
  • If email verification is required, users who do not receive the email cannot continue using the product. Reliable sending must be established first.
  • Sending rate limits take effect when endpoints are abused.

Do not substitute development console previews for production email tests.

Frequently asked questions

Next steps

Choose further reading based on what you need to build:

For most products, completing this chapter only requires choosing an email provider and verifying the domain.

Add new product emails using the existing template pattern instead of calling email SDKs directly from pages.