Better Auth

Send Better Auth's verification, password reset, magic link, one-time code and invitation emails through coss.com.

Better Auth asks you for a function per email it sends. cossEmail gives you all of them, with plain default templates you can replace one by one.

npm install coss better-auth
import {  } from "better-auth";
import {  } from "coss/better-auth";

const  = ({
  : "Acme <auth@acme.com>",
  : "Acme",
});

export const  = ({
  : {
    : true,
    : .,
  },
  : {
    : true,
    : .,
  },
  : {
    : {
      : true,
      : .,
    },
    : {
      : true,
      : .,
    },
  },
});

Plugins

The plugins that send email take the matching callback:

const  = ({
  : "Acme <auth@acme.com>",
  : "Acme",
  // Better Auth passes no link for invitations, so tell coss.com where invitees accept them.
  : ({  }) => `https://acme.com/accept-invitation/${}`,
});

export const  = ({
  : [
    ({ : . }),
    ({ : . }),
    ({ : . }),
  ],
});

Templates

Replace any template with your own. Each one gets the data Better Auth passed plus your appName, and returns a subject and both bodies:

const  = ({
  : "Acme <auth@acme.com>",
  : {
    : ({ ,  }) => ({
      : "Reset your Acme password",
      : `<p>Hi ${. ?? "there"}, <a href="${}">choose a new password</a>.</p>`,
      : `Hi ${. ?? "there"}, choose a new password: ${}`,
    }),
  },
});

Escape anything user-controlled (names, organization names) in your own HTML. The default templates already do.

email.send({ to, subject, html, text }) sends any other email with the same sender and defaults, and returns the email. Use it for things like a welcome email from a Better Auth hook.

Behaviour

  • Emails go out with category: "account" and open and click tracking off. Pass category to change the category.
  • Each email with a link derives its idempotency key from a hash of the link's token, so a callback that runs twice for the same token sends once. Codes and invitations can be legitimately resent with the same value, so they get a fresh key per send.
  • Callbacks throw a CossSendError when coss.com refuses the email.

Better Auth recommends not awaiting email sends, so response times don't reveal whether an account exists (a timing attack). On serverless platforms, hand the promise to waitUntil so the send still finishes:

export const  = ({
  : {
    : true,
    : async () => {
      (.());
    },
  },
});