LogoZonai
zonai.dev

Built-in Templates

The seven built-in email templates, what sends each one, the variables they receive, and their default preview text.

Zonai ships seven HTML email templates. The first time you run zonai dev in a new project, it writes them into emailTemplatesPath (default lib/src/email_templates). They are ordinary files in your project, so edit them freely. Existing files are never overwritten. Zonai reads templates from disk at send time, so edits take effect without recompiling.

If a template file is missing (for example, a project created before that template existed), the send fails and the server log names the path it looked for: Email template not found: …. Copy the file from a fresh project, or create one with zonai db email template create <name>.

What sends each template#

TemplateSent whenDefault expiry
verify_email The default onSignUp hook on an auth table with an email column, POST /auth/verify-email , or email.send.verifyEmail(...) 24 hours (VerifyEmailConfig)
otp_code POST /auth with type: "sendOtp", or email.send.otpCode(...) 10 minutes (fixed)
magic_link POST /auth with type: "sendMagicLink" 10 minutes (MagicLinkConfig)
password_reset POST /auth/reset-password, or email.send.passwordReset(...) 10 minutes (ResetPasswordConfig)
admin_invite Inviting an admin (zonai db admin invite or the dashboard) 7 days
confirm_change_emailNothing yet (see below)
login_noticeNothing yet (see below)

The expiry settings live on your auth operations. OTP, magic-link and password-reset sends are limited to one per address per minute. See Auth Rate Limits.

login_notice and confirm_change_email are templates only for now. The server does not implement email.send.loginNotice(...), email.send.magicLink(...) or email.send.confirmEmailChange(...): each one sends nothing and raises an UnimplementedError on the server. The default onSignIn hook calls loginNotice for auth tables with an email column, so override onSignIn if you don't want that failure in your logs. To send either message today, call email.send(Email(template: 'login_notice', ...)) yourself with the variables below.

Variables#

Every template also receives appName (from AppConfig) and preheader (see Preview text) without you passing them. Anything you add in variables: is merged in as well.

TemplateVariables
verify_email.html email, verificationUrl, expiresIn, optional name
otp_code.html email, otp, expiresIn, optional name
magic_link.html email, magicLinkUrl, expiresIn, optional name
password_reset.html email , passwordResetUrl , expiresIn , optional name
admin_invite.html email , inviteUrl , expiresIn , optional invitedByEmail
confirm_change_email.html currentEmail , newEmail , confirmChangeEmailUrl , expiresIn , optional name
login_notice.html email, signedInAt, optional name

expiresIn is already formatted as text, such as 10 minutes, 24 hours or 7 days.

Verify, magic-link and password-reset links are built from AppConfig.baseUrl plus the path on the matching config in your auth operations (/auth/verify-email, /auth/magic-link and /auth/reset-password by default). A path that already starts with http is used as-is. The one-time token is appended as ?s=<token>.

If baseUrl is still the default http://localhost:8080, every link in production points at localhost. See Server Binding.

Preview text#

Every built-in template opens with a hidden {{preheader}} block. That's the line the inbox shows next to the subject. Each built-in auth email sets its own default:

EmailDefault preview line
otp_codeYour sign-in code expires in 10 minutes.
magic_linkYour sign-in link expires in 10 minutes.
verify_email Confirm <address> to finish setting up your account.
password_resetYour reset link expires in 10 minutes.
admin_inviteYour invite expires in 7 days.

The expiry in each line follows the expiry you configure. The OTP preview deliberately leaves out the code, because the preview line is what shows on a locked phone.

To override one, pass preheader: when you construct the email. If you write your own template, see Custom Templates for the markup.

Customizing#

Edit the HTML files in emailTemplatesPath directly. Templates use Mustache. See Custom Templates for the syntax and rendering rules. To preview a template with your own values without sending it, use Preview email in zonai dev.

zonai build copies the templates directory into the build output. Ship it alongside the binary, because production reads templates from there.