Sign in Create account
Integrations

Better Auth

Better Auth email verification, password reset and magic link emails sent through the Postal API.

Better Auth leaves email sending up to you: just call Postal's POST /emails in its callbacks. There is no connection to create.

#Key and sender

Create a sending-only key under API keys and store it in POSTAL_API_KEY. The sender must belong to a verified domain.

Sending through Postal
// lib/postal.ts
export async function sendWithPostal(to: string, subject: string, html: string) {
  const response = await fetch('https://api.postal.so/emails', {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${process.env.POSTAL_API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      from: 'Acme Store <[email protected]>',
      to: [to],
      subject,
      html,
    }),
  })

  if (!response.ok) {
    throw new Error(`Postal responded with ${response.status}: ${await response.text()}`)
  }
}

#Verification, password and magic link

auth.ts
import { betterAuth } from 'better-auth'
import { magicLink } from 'better-auth/plugins'
import { sendWithPostal } from './lib/postal'

export const auth = betterAuth({
  emailAndPassword: {
    enabled: true,
    requireEmailVerification: true,
    sendResetPassword: async ({ user, url }) => {
      await sendWithPostal(user.email, 'Reset your password', `<p><a href="${url}">Create a new password</a></p>`)
    },
  },
  emailVerification: {
    sendOnSignUp: true,
    sendVerificationEmail: async ({ user, url }) => {
      await sendWithPostal(user.email, 'Confirm your email', `<p><a href="${url}">Confirm email</a></p>`)
    },
  },
  plugins: [
    magicLink({
      sendMagicLink: async ({ email, url }) => {
        await sendWithPostal(email, 'Your sign-in link', `<p><a href="${url}">Sign in</a></p>`)
      },
    }),
  ],
})

#With Postal templates

To edit the emails in the dashboard instead of in code, create a template with the {{{URL}}} variable and send with template instead of html:

POST /emails body
{
  from: 'Acme Store <[email protected]>',
  to: [user.email],
  template: { id: 'confirm-email', variables: { URL: url } },
}

To also bring sign-ups into your audience and trigger automations, call POST /events in Better Auth's databaseHooks.user.create.after, with the user ID in external_id. See the Integrate your app guide.