Customization

Emails

Send Better Auth's emails through your own provider, with your own templates.

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.

nuxt.config.ts
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:

server/emails/send.ts
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:

server/auth.config.ts
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 }:

server/emails/verify-email.ts
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>`
}))
Templates are plain functions, so any email library works: Vue Email, MJML, React Email, or a string.

Every email

EmailSent forTemplate gets
verify-emailemailVerification.sendVerificationEmail{ user, url, token }
reset-passwordemailAndPassword.sendResetPassword{ user, url, token }
existing-user-sign-upemailAndPassword.onExistingUserSignUp{ user }
change-email-confirmationuser.changeEmail.sendChangeEmailConfirmation{ user, newEmail, url, token }
delete-accountuser.deleteUser.sendDeleteAccountVerification{ user, url, token }
two-factor-codeTwo-factor codes by email (twoFactor.otp){ user, otp }
magic-linkSign-in links{ email, url, token }
email-codeCodes by email{ email, otp, type }
organization-invitationOrganization invitations{ email, organization, inviter, role, url }
new-sign-inSecurity alert: a new device{ user, device, ipAddress, time, accountUrl }
password-changedSecurity alert: password changed{ user, time, accountUrl }
two-factor-changedSecurity alert: two-factor on or off{ user, enabled, time, accountUrl }
passkey-addedSecurity 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.