Email Flows

Default email verification, password reset, and change-email flows

Email Flows

The plugin ships default implementations for the three transactional email flows Better Auth supports. They use payload.sendEmail(...), so they pick up whatever Payload email adapter you configured (Resend, SMTP, etc.). Every default is shallow-merged with your own options, so anything you pass wins.

What is enabled by default

FlowDefault behavior
Email verification on sign-upsendOnSignUp: true, autoSignInAfterVerification: true
Email verification on sign-insendOnSignIn: true (when the user is not yet verified)
Password resetemailAndPassword.sendResetPassword wired to payload.sendEmail
Change-emailuser.changeEmail.enabled: true, sendChangeEmailVerification wired to payload.sendEmail (sent to the current email for approval)

emailAndPassword.enabled is also set to true by default.

Overriding a flow

Pass your own implementation through betterAuth.emailVerification, betterAuth.emailAndPassword, or betterAuth.user.changeEmail. Your value replaces the default for that field.

import { betterAuthPlugin } from '@b3nab/payload-better-auth'

betterAuthPlugin({
  betterAuth: {
    emailVerification: {
      sendOnSignUp: true,
      autoSignInAfterVerification: false,
      sendVerificationEmail: async ({ user, url }) => {
        await myMailer.send({
          to: user.email,
          subject: 'Confirm your email',
          html: renderTemplate('verify', { url }),
        })
      },
    },
    emailAndPassword: {
      sendResetPassword: async ({ user, url }) => {
        await myMailer.send({
          to: user.email,
          subject: 'Reset your password',
          html: renderTemplate('reset', { url }),
        })
      },
    },
    user: {
      changeEmail: {
        sendChangeEmailVerification: async ({ user, newEmail, url }) => {
          await myMailer.send({
            to: user.email,
            subject: `Approve change to ${newEmail}`,
            html: renderTemplate('change-email', { url, newEmail }),
          })
        },
      },
    },
  },
})

Notes

  • The default handlers swallow errors via the plugin logger and log the verification URL/token to the console so you are never locked out during local development. In production you should provide your own implementations that surface errors properly.
  • payload.sendEmail requires a Payload email adapter to be configured. Without one, the default handlers will throw.
  • See the Better Auth email verification docs for the full option surface (template tokens, expiry, etc.).

On this page