Emails
Better Auth leaves sending auth emails to your app. The module does the sending for you: plain-text emails by default, or your own templates, delivered by your own function (Resend, SMTP, Postmark, …).
Setting it up
Point the module at your files
Paths are relative to the project root; ~~/ works too.
export default defineNuxtConfig({
betterAuthUi: {
emails: {
send: './server/emails/send.ts',
// Optional: emails without a template are sent as plain text.
templates: {
'verify-email': './server/emails/verify-email.ts'
}
}
}
})
Deliver them
The send file's default export delivers one email. For example, with Resend:
import { defineBetterAuthEmailSender } from '@treygrr/nuxt-better-auth-ui/emails'
export default defineBetterAuthEmailSender(async ({ to, subject, text, html }) => {
await resend.emails.send({ from: 'Acme <hello@acme.com>', to, subject, text, html })
})
It also gets email, the email's name (verify-email, …), if you want to send some differently.
Hand them to Better Auth
In your Better Auth config, use betterAuthEmails for the emails Better Auth asks you to send:
import { betterAuthEmails } from '@treygrr/nuxt-better-auth-ui/emails'
export default defineServerAuth({
emailAndPassword: {
enabled: true,
sendResetPassword: betterAuthEmails.resetPassword,
onExistingUserSignUp: betterAuthEmails.existingUserSignUp
},
emailVerification: {
sendVerificationEmail: betterAuthEmails.verifyEmail
},
user: {
changeEmail: {
enabled: true,
sendChangeEmailConfirmation: betterAuthEmails.changeEmailConfirmation
},
deleteUser: {
enabled: true,
sendDeleteAccountVerification: betterAuthEmails.deleteAccount
}
}
})
The emails of the features you turn on in the module (two-factor codes, sign-in links, invitations, …) are wired up for you.
Your own templates
Template any email you want to design. Its input is typed by the email's name, so a wrong name or field is a type
error. Return HTML (sent with the default subject and plain-text version), or { subject, html, text }:
import { defineBetterAuthEmail } from '@treygrr/nuxt-better-auth-ui/emails'
export default defineBetterAuthEmail('verify-email', async ({ user, url }) => ({
subject: 'Verify your Acme email',
html: `<p>Hi ${user.name}!</p><a href="${url}">Verify your email</a>`
}))
Every email
| Sent for | Template gets | |
|---|---|---|
verify-email | emailVerification.sendVerificationEmail | { user, url, token } |
reset-password | emailAndPassword.sendResetPassword | { user, url, token } |
existing-user-sign-up | emailAndPassword.onExistingUserSignUp | { user } |
change-email-confirmation | user.changeEmail.sendChangeEmailConfirmation | { user, newEmail, url, token } |
delete-account | user.deleteUser.sendDeleteAccountVerification | { user, url, token } |
two-factor-code | Two-factor codes by email (twoFactor.otp) | { user, otp } |
magic-link | Sign-in links | { email, url, token } |
email-code | Codes by email | { email, otp, type } |
organization-invitation | Organization invitations | { email, organization, inviter, role, url } |
new-sign-in | Security alert: a new device | { user, device, ipAddress, time, accountUrl } |
password-changed | Security alert: password changed | { user, time, accountUrl } |
two-factor-changed | Security alert: two-factor on or off | { user, enabled, time, accountUrl } |
passkey-added | Security alert: passkey added | { user, name, time, accountUrl } |
magic-link, email-code and organization-invitation get an email instead of a user: there may be no account
yet. email-code's type is sign-in, email-verification, forget-password or change-email.
Good to know
- Emails are sent in the background, not awaited, as Better Auth advises: waiting would reveal through timing which emails have accounts. A failed send is logged.
- A missing file fails the build, so a typo in a path doesn't go unnoticed.
- The admin's configuration page lists which emails have your own template.
- Default emails are in the pages' language; your templates are in whatever language you write them.