Skip to content
Docs menu

Guides

Features

API

Integrations

Better Auth

Better Auth asks you for a function that sends each email. Call Wakio from it.

npm install wakio
// auth.ts
import { createHash } from "node:crypto"
import { betterAuth } from "better-auth"
import { magicLink } from "better-auth/plugins"
import { Wakio } from "wakio"

const wakio = new Wakio()
const keyFor = (kind: string, token: string) => `${kind}-${createHash("sha256").update(token).digest("hex")}`

export const auth = betterAuth({
  emailVerification: {
    sendVerificationEmail: async ({ user, url, token }) => {
      void wakio.emails
        .send({ to: user.email, template: "verify-email", mergeData: { url } }, { idempotencyKey: keyFor("verify", token) })
        .catch((error) => console.error("verification email failed", error))
    },
  },
  emailAndPassword: {
    enabled: true,
    sendResetPassword: async ({ user, url, token }) => {
      void wakio.emails
        .send({ to: user.email, template: "reset-password", mergeData: { url } }, { idempotencyKey: keyFor("reset", token) })
        .catch((error) => console.error("password reset email failed", error))
    },
  },
  plugins: [
    magicLink({
      sendMagicLink: async ({ email, url, token }) => {
        void wakio.emails
          .send({ to: email, template: "magic-link", mergeData: { url } }, { idempotencyKey: keyFor("magic", token) })
          .catch((error) => console.error("magic link email failed", error))
      },
    }),
  ],
})

Set WAKIO_API_KEY in your environment.

The sends are started, not awaited. Better Auth answers a reset request only after your function returns, and it calls the function only for an address with an account, so an awaited send makes the answer slower for real accounts and tells anyone timing it who has signed up. In production, put the send on your job queue instead, so a restart cannot lose it.

Each key is a hash of the email's token, so a retried request cannot send the email twice, and the token is kept out of the key, which everyone on your Wakio account can see.

A send to a suppressed address resolves normally, with the address in result.suppressed. Read it in a .then if you want to log those.