Better Auth + Shootmail - Send custom emails on authentication events

Published :
subhendu singh subhendu singh
Better Auth + Shootmail - Send custom emails on authentication events

#Pre-built templates for Better Auth

First things first, here are the pre-built email templates for better auth that you can use today.

#What is Better Auth?

According to the Better Auth docs, it is a framework agnostic authentication and authorization framework for typescript. It has multiple options for authentication like:

  • Email & password
  • Social sign-on
  • SSO, etc

It also has a plugin ecosystem that allows you to extend your authentication with features like 2FA, organizations, RBAC etc.

#Why are emails needed with Better Auth?

Emails are needed with better auth to notify your users during various stages of authentication and authorization. For example:

  • Send email to confirm signup email
  • Send magic link email for sign in
  • Send reset password email with a link to reset password
  • Send one time passwords (OTP) on mail
  • Invite a new member to organization via email

While Better Auth provides you a simple to use typescript SDK to quickly roll your own auth with all the features, it doesn’t yet have an inbuilt solution to send emails.

This is where Shootmail comes in. You can plug Shootmail’s SDK with Better Auth, that automatically sends emails to your users at different events. These emails are already designed to look beautiful and are highly compatible with major email clients, even in dark mode.

Let’s jump straight into how to configure Shootmail and Better Auth together.

#Setup Shootmail

I will go through the step by step process here. You can always refer the docs.

#Grab Shootmail API key

You can signup on Shootmail using this registration link. One you have signed up, go to API Keys and generate a new api key.

#Install Shootmail SDK

Install the Shootmail SDK using npm or pnpm

npm i --save shootmail
// or pnpm i shootmail

#Initialize Shootmail client

In a separate file shootmail.ts, initialize and configure Shootmail SDK client.

import { Shootmail, type ShootMailConfig } from "shootmail";

export const shootmail = new Shootmail({
	 shootmailApiKey: "shootmail-api-key",
	 from: {
			"name": "your-email",
			"email": "your-email"
	 },
	 providers: [
		 {
         "provider": "resend", // or any supported provider
         "apiKey": "resend-api-key"
      },
	 ]
});

Here is a list of all the supported providers you can use. In the above code, do not forget to replace:

  • From email
  • Shootmail API Key
  • Provider information

#Setup Better Auth

Install better auth SDK and setup the database, here is the documentation to get started.

#Use Shootmail to send emails at different authentication event

In all these email calls, you will use the shootmail client configured previously in shootmail.ts. Let us pre-configure all our email template IDs in an enum to make it easy for us to reference them

const enum ShootmailTemplates {
    VerificationEmail = "wxltewafrklxhgg",
    MagicLink = "sgarbaenwceoywo",
    OTP = "wcxzbahqggyogtz",
    TeamInvite = "ygrpeyqfdpzibcl",
    AccountDeletion = "gcpdbxildytxogo",
    EmailChange = "hrjwebwpocangal",
    PasswordReset = "aykfbwltyitbxnp"
}

Please note, these are default email templates IDs and are not recommended to user in production. Instead, you should create your own simply by cloning these default email templates in the Shootmail dashboard. Here is how to do this:

  • Login to you Shootmail dashboard
  • Go to purchased templates section
  • Search for better auth
  • Click on the template you want to use and clone it. This will generate a new ID for that template. Copy this ID to use later.

#Signup confirmation email

Method

shootmail.betterAuth.sendVerificationEmail()

Usage

const shootmailResponse = await shootmail.betterAuth.sendVerificationEmail({
      templateId: ShootmailTemplates.VerificationEmail,
      verificationUrl: data.url,
      toEmail: data.user.email,
 });

Implementation

export const auth = betterAuth({
    appName: "Better Auth Demo",
    database: drizzleAdapter(db, {
        provider: "sqlite",
    }),
    emailVerification: {
       sendOnSignUp: true,
        sendVerificationEmail: async (data, request) => {
            // Shootmail code
            const shootmailResponse = await shootmail.betterAuth.sendVerificationEmail({
                templateId: ShootmailTemplates.VerificationEmail,
                verificationUrl: data.url,
                toEmail: data.user.email,
            });

            console.log("shootmailResponse in email verification", shootmailResponse);
        },
    }

Required better auth magic link plugin

Method

shootmail.betterAuth.sendMagicLink()

Usage

const shootmailResponse = await shootmail.betterAuth.sendMagicLink({
  templateId: ShootmailTemplates.MagicLink,
  toEmail: data.email,
  verificationUrl: data.url,
});

Implementation

export const auth = betterAuth({
 ....
  plugins: [
    magicLink({
      sendMagicLink: async (data, request) => {
        const shootmailResponse = await shootmail.betterAuth.sendMagicLink({
          templateId: ShootmailTemplates.MagicLink,
          toEmail: data.email,
          verificationUrl: data.url,
        });

        console.log("shootmailResponse in magiclink", shootmailResponse);
      },
    }),
  ],
});

#Send OTP email - 2FA

Required better auth two factor plugin

Method

shootmail.betterAuth.sendVerificationOtp()

Usage

const shootmailResponse = await shootmail.betterAuth.sendVerificationOtp({
  templateId: ShootmailTemplates.OTP,
  toEmail: data.user.email,
  otp: data.otp,
  type: "sign-in",
  validityInMinutes: 5,
});

Implementation

export const auth = betterAuth({
 ....
  plugins: [
    twoFactor({
      otpOptions: {
        sendOTP: async (data, request) => {
          const shootmailResponse =
            await shootmail.betterAuth.sendVerificationOtp({
              templateId: ShootmailTemplates.OTP,
              toEmail: data.user.email,
              otp: data.otp,
              type: "sign-in",
              validityInMinutes: 5,
            });

          console.log("shootmailResponse in otp", shootmailResponse);
        },
      },
    }),
  ],
});

#Send reset password email

Method

shootmail.betterAuth.sendResetPasswordEmail()

Usage

const shootmailResponse = await shootmail.betterAuth.sendResetPasswordEmail({
  templateId: ShootmailTemplates.PasswordReset,
  toEmail: data.user.email,
  resetPasswordUrl: data.url,
  subject: "Reset Password",
  preHeader: "We have received a request to reset your password :)",
});

Implementation

export const auth = betterAuth({
	....
  emailAndPassword: {
    enabled: true,
    requireEmailVerification: true,
    sendResetPassword: async (data, request) => {
      const shootmailResponse =
        await shootmail.betterAuth.sendResetPasswordEmail({
          templateId: ShootmailTemplates.PasswordReset,
          toEmail: data.user.email,
          resetPasswordUrl: data.url,
          subject: "Reset Password",
          preHeader: "We have received a request to reset your password :)",
        });

      console.log("shootmailResponse in reset password", shootmailResponse);
    },
  },
});

#Send email change confirmation email

Method

shootmail.betterAuth.sendChangeEmailVerificationEmail()

Usage

const shootmailResponse =
  await shootmail.betterAuth.sendChangeEmailVerificationEmail({
    templateId: ShootmailTemplates.EmailChange,
    toEmail: data.user.email,
    newEmail: data.newEmail,
    verificationUrl: data.url,
  });

Implementation

export const auth = betterAuth({
  user: {
    changeEmail: {
      enabled: true,
      sendChangeEmailVerification: async (data, request) => {
        const shootmailResponse =
          await shootmail.betterAuth.sendChangeEmailVerificationEmail({
            templateId: ShootmailTemplates.EmailChange,
            toEmail: data.user.email,
            newEmail: data.newEmail,
            verificationUrl: data.url,
          });

        console.log("shootmailResponse in email change", shootmailResponse);
      },
    },
  },
});

#Send invitation to organization email

Requires better auth organization plugin

Method

shootmail.betterAuth.sendOrganizationInvitation()

Usage

const shootmailResponse = await shootmail.betterAuth.sendOrganizationInvitation(
  {
    templateId: ShootmailTemplates.TeamInvite,
    toEmail: data.email,
    invitedByEmail: data.inviter.user.email,
    invitedByUsername: data.inviter.user.name,
    teamName: data.organization.name,
    inviteUrl,
  }
);

Implementation

export const auth = betterAuth({
	....
  emailAndPassword: {
    enabled: true,
    requireEmailVerification: true,
    sendResetPassword: async (data, request) => {
      const shootmailResponse =
        await shootmail.betterAuth.sendResetPasswordEmail({
          templateId: ShootmailTemplates.PasswordReset,
          toEmail: data.user.email,
          resetPasswordUrl: data.url,
          subject: "Reset Password",
          preHeader: "We have received a request to reset your password :)",
        });

      console.log("shootmailResponse in reset password", shootmailResponse);
    },
  },
});

#Send email change confirmation email

Method

shootmail.betterAuth.sendChangeEmailVerificationEmail()

Usage

const shootmailResponse =
  await shootmail.betterAuth.sendChangeEmailVerificationEmail({
    templateId: ShootmailTemplates.EmailChange,
    toEmail: data.user.email,
    newEmail: data.newEmail,
    verificationUrl: data.url,
  });

Implementation

export const auth = betterAuth({
	....
  plugins: [
    organization({
      sendInvitationEmail: async (data, request) => {
        const inviteUrl = `https://example.com/accept-invitation/${data.id}`;

        const shootmailResponse =
          await shootmail.betterAuth.sendOrganizationInvitation({
            templateId: ShootmailTemplates.TeamInvite,
            toEmail: data.email,
            invitedByEmail: data.inviter.user.email,
            invitedByUsername: data.inviter.user.name,
            teamName: data.organization.name,
            inviteUrl,
          });

        console.log("shootmailResponse in invitation", shootmailResponse);
      },
    }),
  ],
});

That’s it. Just like that, you can start sending beautiful emails with Better Auth using Shootmail. All the email templates are customizable and you can add other sections to these emails like customer feedback link, CTA to your website, support handles, social media links etc.

If you are using Supabase auth, there is a guide for that too. Here is the link.

Want to know more about Shootmail, visit the website and check the docs.

If you have questions, feel free to shoot a DM to me on Twitter.