> ## Documentation Index
> Fetch the complete documentation index at: https://loops.so/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Better Auth

> Use Loops as the email sender for Better Auth authentication emails.

This guide shows how to use Loops to send [Better Auth](https://better-auth.com) authentication emails.

For this guide, you will need a working app with Better Auth installed and a [Loops](https://app.loops.so) account with a verified sending domain set up.

## Create transactional emails in Loops

The first step is to create transactional templates in Loops for each auth message.

In this guide we have three example emails for the [Email & Password](https://better-auth.com/docs/authentication/email-password), [Magic Link](https://better-auth.com/docs/plugins/magic-link) and [Email OTP](https://better-auth.com/docs/plugins/email-otp) authentication methods.

For the authentication method you are using, you should [create transactional emails](/transactional) for these emails in Loops.

Include [data variables](/transactional#add-data-variables) for the data that will be passed from Better Auth. You can add data variables by typing "\{", via the `{}` button above the editor, or by using the `/` slash menu.

<img src="https://mintcdn.com/loops/b20ECDOV_0naB96o/images/better-auth-variables.png?fit=max&auto=format&n=b20ECDOV_0naB96o&q=85&s=baf668ceb2b3e293a1cd03c07419e071" alt="Adding data variables to a transactional email" width="2280" height="1358" data-path="images/better-auth-variables.png" />

After publishing each transactional email, note the **Transactional ID** from the **Review** page. You will need this for the next step.

<img src="https://mintcdn.com/loops/b20ECDOV_0naB96o/images/better-auth-copy-id.png?fit=max&auto=format&n=b20ECDOV_0naB96o&q=85&s=9d41213d38dee7937fdd543f84e777bc" alt="Copying the transactional ID" width="2280" height="1649" data-path="images/better-auth-copy-id.png" />

<Tip>
  The following variable names are suggestions and can be changed to whatever you want. Just make sure to use the same names in your Better Auth config code and in your Loops emails.
</Tip>

### Email & Password data variables

Depending on the authentication method you are using, you will need to add different data variables to your emails.

#### Verification email

This email is sent when a user is requested to verify their email address.

* `verificationUrl`
* `token`

#### Password reset email

This email is sent when a user requests to reset their password.

* `resetPasswordUrl`
* `token`

### Magic link data variables

This email is sent when a user signs in with a magic link.

* `magicLinkUrl`
* `token`

### Email OTP data variables

#### Sign in email

This email is sent when a user signs in using an email OTP.

* `otpCode`

#### Email verification email

This email is sent when a user needs to verify their email using an email OTP.

* `otpCode`

#### Password reset email

This email is sent when a user resets their password using an email OTP.

* `otpCode`

## Add the Loops SDK

Install the [Loops SDK](/sdks/javascript) in your project. This will allow you to send transactional emails via Loops.

```bash theme={"dark"}
npm i loops
```

Next you need to add some environment variables to your project, for example in an `.env` file.

You can create an API key from your [API Settings](https://app.loops.so/settings?page=api) page.

The transactional IDs are the IDs of the transactional emails you created in the previous step. (These are not all required, you will only need the ones for the authentication method you are using.)

```bash theme={"dark"}
LOOPS_API_KEY=replace-with-your-loops-api-key

# Email & Password
LOOPS_VERIFY_EMAIL_TRANSACTIONAL_ID=replace-with-verify-template-id
LOOPS_RESET_PASSWORD_TRANSACTIONAL_ID=replace-with-reset-template-id

# Magic Link
LOOPS_MAGIC_LINK_TRANSACTIONAL_ID=replace-with-magic-link-template-id

# Email OTP
LOOPS_OTP_SIGNIN_TRANSACTIONAL_ID=replace-with-email-otp-signin-template-id
LOOPS_OTP_VERIFICATION_TRANSACTIONAL_ID=replace-with-email-otp-verification-template-id
LOOPS_OTP_PASSWORD_RESET_TRANSACTIONAL_ID=replace-with-email-otp-password-reset-template-id
```

## Create a Loops server helper

Next, create a helper function to keep email sending logic clean and reusable. This helper will be used in the next step to configure Better Auth to send emails through Loops.

<Warning>
  It is important that this helper is used server-side only. Never expose your Loops API key in client-side code.
</Warning>

```typescript lib/loops.ts theme={"dark"}
import { LoopsClient } from "loops";

const loops = new LoopsClient(process.env.LOOPS_API_KEY as string);

type DataVariables = Record<string, string | number | boolean | null>;

export async function sendLoopsTransactionalEmail(input: {
  email: string;
  dataVariables?: DataVariables;
}) {
  const { transactionalId, email, dataVariables } = input;
  transactionalId: string;

  const response = await loops.sendTransactionalEmail({
    transactionalId,
    email,
    dataVariables,
  });

  if (!response.success) {
    throw new Error(response.message || "Failed to send Loops transactional email");
  }
}
```

## Configure Better Auth to send through Loops

In your Better Auth config, enable the authentication method you want to use and add callbacks to send emails.

Inside the `sendLoopsTransactionalEmail()` calls, we populate the content for the data variables you added in your Loops emails.

<Warning>
  Make sure that the data variable names inside `dataVariables` match the names of the data variables you added in your Loops emails.
</Warning>

<Note>
  Better Auth recommends not awaiting email sends in these callbacks to reduce timing-attack risk and keep auth responses fast, so we use `void`. If your platform supports background work (for example `waitUntil`), you can use that instead.
</Note>

### Password reset

For this email, make sure that `emailAndPassword.enabled` is set to `true`, then add a `sendResetPassword` callback containing our `sendLoopsTransactionalEmail()` helper function.

[Better Auth docs](https://better-auth.com/docs/authentication/email-password#request-password-reset)

```ts auth.ts {8-15} theme={"dark"}
import { betterAuth } from "better-auth";
import { sendLoopsTransactionalEmail } from "./lib/loops";

export const auth = betterAuth({
  emailAndPassword: {
    enabled: true,
    sendResetPassword: async ({ user, url, token }) => {
      void sendLoopsTransactionalEmail({
        transactionalId: process.env.LOOPS_RESET_PASSWORD_TRANSACTIONAL_ID as string,
        email: user.email,
        dataVariables: {
          resetPasswordUrl: url,
          token,
        },
      });
    },
  },
});
```

### Email verification

For this email, add a `sendVerificationEmail` callback containing our `sendLoopsTransactionalEmail()` helper function.

[Better Auth docs](https://better-auth.com/docs/authentication/email-password#email-verification)

```ts auth.ts {7-14} theme={"dark"}
import { betterAuth } from "better-auth";
import { sendLoopsTransactionalEmail } from "./lib/loops";

export const auth = betterAuth({
  emailVerification: {
    sendVerificationEmail: async ({ user, url, token }) => {
      void sendLoopsTransactionalEmail({
        transactionalId: process.env.LOOPS_VERIFY_EMAIL_TRANSACTIONAL_ID as string,
        email: user.email,
        dataVariables: {
          verificationUrl: url,
          token,
        },
      });
    },
  },
});
```

### Magic link plugin

Add the `magicLink` plugin to your Better Auth config and add a `sendMagicLink` callback containing our `sendLoopsTransactionalEmail()` helper function.

[Better Auth docs](https://better-auth.com/docs/plugins/magic-link)

```ts auth.ts {2,9-16} theme={"dark"}
import { betterAuth } from "better-auth";
import { magicLink } from "better-auth/plugins"; 
import { sendLoopsTransactionalEmail } from "./lib/loops";

export const auth = betterAuth({
  plugins: [
    magicLink: {
      sendMagicLink: async ({ email, token, url }) => {
        void sendLoopsTransactionalEmail({
          transactionalId: process.env.LOOPS_MAGIC_LINK_TRANSACTIONAL_ID as string,
          email,
          dataVariables: {
            magicLinkUrl: url,
            token,
          },
        });
      },
    },
  ],
});
```

### Email OTP plugin

Add the `emailOTP` plugin to your Better Auth config and add a `sendVerificationOTP` callback containing our `sendLoopsTransactionalEmail()` helper function.

There are three types of OTP emails: sign in, email verification and password reset, so you will need to create an email template for each type in Loops.

[Better Auth docs](https://better-auth.com/docs/plugins/email-otp)

```ts auth.ts {2,10-16,18-24,26-32} theme={"dark"}
import { betterAuth } from "better-auth";
import { emailOTP } from "better-auth/plugins"; 
import { sendLoopsTransactionalEmail } from "./lib/loops";

export const auth = betterAuth({
  plugins: [
    emailOTP: {
      async sendVerificationOTP({ email, otp, type }) { 
        if (type === "sign-in") { 
          void sendLoopsTransactionalEmail({
            transactionalId: process.env.LOOPS_OTP_SIGNIN_TRANSACTIONAL_ID as string,
            email,
            dataVariables: {
              otpCode: otp,
            },
          });
        } else if (type === "email-verification") { 
          void sendLoopsTransactionalEmail({
            transactionalId: process.env.LOOPS_OTP_VERIFICATION_TRANSACTIONAL_ID as string,
            email,
            dataVariables: {
              otpCode: otp,
            },
          });
        } else { 
          void sendLoopsTransactionalEmail({
            transactionalId: process.env.LOOPS_OTP_PASSWORD_RESET_TRANSACTIONAL_ID as string,
            email,
            dataVariables: {
              otpCode: otp,
            },
          });
        } 
      }, 
    },
  ],
});
```

## Call Better Auth from your app

Now your app is set up to send Better Auth emails through Loops.

For example, Loops will send a transactional when you [sign in a user with an email OTP](https://better-auth.com/docs/plugins/email-otp#sign-in-with-otp):

```ts theme={"dark"}
const data = await auth.api.sendVerificationOTP({
  body: {
    email: "test@example.com", // required
    type: "sign-in", // required
  },
});
```

Or when you [reset a user's password](https://better-auth.com/docs/authentication/email-password#api-method-request-password-reset):

```ts theme={"dark"}
const data = await auth.api.requestPasswordReset({
  body: {
    email: "test@example.com", // required
    redirectTo: "https://example.com/reset-password",
  },
});
```

## Troubleshooting

If your emails are not sending:

* Confirm each transactional email in Loops is **Published**
* Confirm each data variable name in your Better Auth code matches the name of the data variable in your Loops email
* Check that you're using a valid Loops API, from the correct account
* Check that your sending domain is verified in Loops

## Read more

<CardGroup cols={2}>
  <Card title="Send transactional email" icon="envelope" href="/api-reference/send-transactional-email">
    Loops API endpoint used to send transactional emails.
  </Card>

  <Card title="Send emails from Bolt.new" href="/guides/bolt-emails" icon="bolt">
    Read our guide for sending emails from Bolt.new.
  </Card>

  <Card title="Integrations" href="/integrations" icon="arrows-turn-right">
    Integrate Loops into lots more platforms.
  </Card>
</CardGroup>
