How to batch generate magic links for invitation emails? #1780

Closed
opened 2026-03-13 09:02:41 -05:00 by GiteaMirror · 4 comments
Owner

Originally created by @99Kies on GitHub (Aug 25, 2025).

Hi Better Auth team,

My service currently only supports magic link login.
Now I want to implement an invitation flow:

  • I have an email list (all users are not registered yet).
  • I want to invite them in by sending magic links.
  • To optimize cost and efficiency, I’d like to use Resend’s batch send API to send all invitation emails at once.

The problem is:

👉 How can I pre-generate magic links for a given email list (before the users request sign-in themselves)?

Specifically, I’d like to:

  1. Provide an email list to my backend.
  2. For each email, generate a valid magic link (with token, expiration etc.).
  3. Collect all links and send them in one batch email via Resend.

Questions:

  • Is there a built-in way in Better Auth to generate magic links ahead of time, without triggering the normal email sending?
  • If not, what’s the recommended approach? Should I hook into sendMagicLink and capture the { url, token } payload manually?
  • How do you suggest handling token expiration and one-time use in this batch-invite scenario?
Originally created by @99Kies on GitHub (Aug 25, 2025). Hi Better Auth team, My service currently only supports **magic link login.** Now I want to implement an invitation flow: - I have an email list (all users are not registered yet). - I want to invite them in by sending magic links. - To optimize cost and efficiency, I’d like to use Resend’s batch send API to send all invitation emails at once. **The problem is:** **👉 How can I pre-generate magic links for a given email list (before the users request sign-in themselves)?** **Specifically, I’d like to:** 1. Provide an email list to my backend. 2. For each email, generate a valid magic link (with token, expiration etc.). 3. Collect all links and send them in one batch email via Resend. **Questions:** - Is there a built-in way in Better Auth to generate magic links ahead of time, without triggering the normal email sending? - If not, what’s the recommended approach? Should I hook into sendMagicLink and capture the { url, token } payload manually? - How do you suggest handling token expiration and one-time use in this batch-invite scenario?
GiteaMirror added the question label 2026-03-13 09:02:41 -05:00
Author
Owner

@dosubot[bot] commented on GitHub (Aug 25, 2025):

Hi @99Kies! I'm Dosu and I’m helping the better-auth team.

Better Auth doesn't have a built-in API to batch generate magic links ahead of time without sending emails, but you can achieve this by leveraging the internal logic of the magic link plugin. The recommended approach is to programmatically invoke the token and URL generation for each email in your list, collect the { email, url, token } objects, and then use Resend's batch API to send your invitations.

You can do this by customizing the sendMagicLink function: instead of sending the email immediately, capture the generated link and token for each email and store them for batch processing. The plugin generates a secure token (either via your own generateToken function or a default random string), stores it with an expiration (default 5 minutes, configurable via expiresIn), and constructs the magic link URL with the token as a query parameter. You can replicate this logic in your backend to generate all links in advance and send them in a batch email via Resend. See the relevant implementation details here.

Token expiration and one-time use are handled by storing the token with an expiresAt timestamp and deleting the token record upon verification, so tokens can't be reused after they're used or expired. For batch invites, set an appropriate expiration window and consider periodic cleanup of unused tokens to avoid cluttering your verification table.

If you need a code outline or want to see how to adapt the internal utilities for your batch workflow, let me know!

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other  Chat with Dosu Join Discord Share on X

@dosubot[bot] commented on GitHub (Aug 25, 2025): <!-- Greeting --> Hi @99Kies! I'm [Dosu](https://go.dosu.dev/dosubot) and I’m helping the better-auth team. <!-- Answer --> Better Auth doesn't have a built-in API to batch generate magic links ahead of time without sending emails, but you can achieve this by leveraging the internal logic of the magic link plugin. The recommended approach is to programmatically invoke the token and URL generation for each email in your list, collect the `{ email, url, token }` objects, and then use Resend's batch API to send your invitations. You can do this by customizing the `sendMagicLink` function: instead of sending the email immediately, capture the generated link and token for each email and store them for batch processing. The plugin generates a secure token (either via your own `generateToken` function or a default random string), stores it with an expiration (default 5 minutes, configurable via `expiresIn`), and constructs the magic link URL with the token as a query parameter. You can replicate this logic in your backend to generate all links in advance and send them in a batch email via Resend. See the relevant implementation details [here](https://github.com/better-auth/better-auth/blob/8c23eb07396478a51b4dc7de669c010cdadc5479/packages/better-auth/src/plugins/magic-link/index.ts). Token expiration and one-time use are handled by storing the token with an `expiresAt` timestamp and deleting the token record upon verification, so tokens can't be reused after they're used or expired. For batch invites, set an appropriate expiration window and consider periodic cleanup of unused tokens to avoid cluttering your verification table. If you need a code outline or want to see how to adapt the internal utilities for your batch workflow, let me know! <!-- Dosu Comment Footer --> *To reply, just mention [@dosu](https://go.dosu.dev/dosubot).* --- <sup>How did I do? [Good](https://app.dosu.dev/response-feedback/21b9fe6b-ac17-45a1-b95d-e29ae04305e0?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/21b9fe6b-ac17-45a1-b95d-e29ae04305e0?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/21b9fe6b-ac17-45a1-b95d-e29ae04305e0?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/21b9fe6b-ac17-45a1-b95d-e29ae04305e0?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/21b9fe6b-ac17-45a1-b95d-e29ae04305e0?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/21b9fe6b-ac17-45a1-b95d-e29ae04305e0?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/21b9fe6b-ac17-45a1-b95d-e29ae04305e0?feedback_type=other)</sup>&nbsp;&nbsp;[![Chat with Dosu](https://dosu.dev/dosu-chat-badge.svg)](https://app.dosu.dev/cdda13d9-dd27-4d31-b09a-5d8bec92de21/ask?utm_source=github)&nbsp;[![Join Discord](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&label=)](https://go.dosu.dev/discord-bot)&nbsp;[![Share on X](https://img.shields.io/badge/X-share-black)](https://twitter.com/intent/tweet?text=%40dosu_ai%20helped%20me%20solve%20this%20issue!&url=https%3A//github.com/better-auth/better-auth/issues/4216)
Author
Owner

@99Kies commented on GitHub (Aug 25, 2025):

Here is my current implementation: I create a separate betterAuth instance that captures all generated magic links (without sending emails) so I can collect them and later send invitations in bulk via Resend batch send.

// simplified excerpt
function createBatchAuth(capturedLinks: MagicLinkInfo[]) {
  return betterAuth({
    baseURL: betterAuthBaseUrl,
    secret: process.env.BETTER_AUTH_SECRET,
    database: prismaAdapter(prisma, { provider: 'postgresql' }),
    plugins: [
      magicLink({
        sendMagicLink: async ({ email, token, url }) => {
          capturedLinks.push({ email, token, url });
          // Do not send email here
        },
        expiresIn: 48 * 60 * 60, // 48h invitations
      }),
      nextCookies(),
    ],
  });
}

Then I loop through an email list, call batchAuth.api.signInMagicLink, capture all { email, url, token }, and finally pass them into resend.batch.send(...) to deliver the invitations.

My question

Since I already have a normal auth.ts in my app where betterAuth is instantiated for login, I’m concerned about potential conflicts or security implications when creating another temporary betterAuth instance specifically for batch generation.

  1. Is it safe to spin up a second betterAuth instance in the same codebase just for this batch process?
  2. Could this conflict with my normal login/session flow?
  3. Is there a recommended approach for handling this use case (pre-generating links for invitations) without instantiating multiple betterAuth objects?

Thanks again for your guidance! 🙏

@99Kies commented on GitHub (Aug 25, 2025): Here is my current implementation: I create a separate `betterAuth` instance that captures all generated magic links (without sending emails) so I can collect them and later send invitations in bulk via Resend batch send. ```ts // simplified excerpt function createBatchAuth(capturedLinks: MagicLinkInfo[]) { return betterAuth({ baseURL: betterAuthBaseUrl, secret: process.env.BETTER_AUTH_SECRET, database: prismaAdapter(prisma, { provider: 'postgresql' }), plugins: [ magicLink({ sendMagicLink: async ({ email, token, url }) => { capturedLinks.push({ email, token, url }); // Do not send email here }, expiresIn: 48 * 60 * 60, // 48h invitations }), nextCookies(), ], }); } ``` Then I loop through an email list, call `batchAuth.api.signInMagicLink`, capture all `{ email, url, token }`, and finally pass them into `resend.batch.send(...)` to deliver the invitations. ## ❓My question Since I already have a normal `auth.ts` in my app where `betterAuth` is instantiated for login, I’m concerned about potential conflicts or security implications when creating another temporary betterAuth instance specifically for batch generation. 1. Is it safe to spin up a second betterAuth instance in the same codebase just for this batch process? 2. Could this conflict with my normal login/session flow? 3. Is there a recommended approach for handling this use case (pre-generating links for invitations) without instantiating multiple betterAuth objects? Thanks again for your guidance! 🙏
Author
Owner

@dosubot[bot] commented on GitHub (Aug 25, 2025):

It's safe to create a second betterAuth instance for batch magic link generation—Better Auth is designed to be instance-based and doesn't use any global or singleton state, so each instance operates independently as long as you use the same database and secret. This won't conflict with your normal login/session flow, since all tokens, sessions, and verification records are managed through your adapter (like Prisma) and are tied to the instance context, not shared in memory between instances source.

The main thing to watch out for is database connection management: Better Auth (and Prisma) recommend using a single, shared Prisma Client instance across your app to avoid connection exhaustion, especially in serverless environments source. If both your main and batch betterAuth instances use the same Prisma Client, you're good.

There isn't a built-in batch magic link generation API, and your approach—customizing sendMagicLink to capture links instead of sending emails—is the recommended pattern for this use case. Just make sure your configs (like secret and database) are consistent between instances, and you'll avoid any security or session issues.

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other  Chat with Dosu Join Discord Share on X

@dosubot[bot] commented on GitHub (Aug 25, 2025): <!-- Answer --> It's safe to create a second betterAuth instance for batch magic link generation—Better Auth is designed to be instance-based and doesn't use any global or singleton state, so each instance operates independently as long as you use the same database and secret. This won't conflict with your normal login/session flow, since all tokens, sessions, and verification records are managed through your adapter (like Prisma) and are tied to the instance context, not shared in memory between instances [source](https://github.com/better-auth/better-auth/blob/8c23eb07396478a51b4dc7de669c010cdadc5479/packages/better-auth/src/plugins/magic-link/index.ts). The main thing to watch out for is database connection management: Better Auth (and Prisma) recommend using a single, shared Prisma Client instance across your app to avoid connection exhaustion, especially in serverless environments [source](https://app.dosu.dev/documents/dfca922a-f368-4b8d-b16c-99433641e9a0). If both your main and batch betterAuth instances use the same Prisma Client, you're good. There isn't a built-in batch magic link generation API, and your approach—customizing sendMagicLink to capture links instead of sending emails—is the recommended pattern for this use case. Just make sure your configs (like secret and database) are consistent between instances, and you'll avoid any security or session issues. <!-- Dosu Comment Footer --> *To reply, just mention [@dosu](https://go.dosu.dev/dosubot).* --- <sup>How did I do? [Good](https://app.dosu.dev/response-feedback/6f66f4a2-49d4-4df0-bc31-2cba09e0a7c4?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/6f66f4a2-49d4-4df0-bc31-2cba09e0a7c4?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/6f66f4a2-49d4-4df0-bc31-2cba09e0a7c4?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/6f66f4a2-49d4-4df0-bc31-2cba09e0a7c4?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/6f66f4a2-49d4-4df0-bc31-2cba09e0a7c4?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/6f66f4a2-49d4-4df0-bc31-2cba09e0a7c4?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/6f66f4a2-49d4-4df0-bc31-2cba09e0a7c4?feedback_type=other)</sup>&nbsp;&nbsp;[![Chat with Dosu](https://dosu.dev/dosu-chat-badge.svg)](https://app.dosu.dev/cdda13d9-dd27-4d31-b09a-5d8bec92de21/ask?utm_source=github)&nbsp;[![Join Discord](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&label=)](https://go.dosu.dev/discord-bot)&nbsp;[![Share on X](https://img.shields.io/badge/X-share-black)](https://twitter.com/intent/tweet?text=%40dosu_ai%20helped%20me%20solve%20this%20issue!&url=https%3A//github.com/better-auth/better-auth/issues/4216)
Author
Owner

@Kinfe123 commented on GitHub (Sep 2, 2025):

i dont think there is workaround for from the current implementation of BA. the only route i can see is creating a temporary better-auth instance make sure to expose the base options some where to avoid any duplciation and just modify the magicLink functionalities. as mention make sure to handle a db pool issue or make it excute timely as well even if the batch request and BA is using the same db instance.

@Kinfe123 commented on GitHub (Sep 2, 2025): i dont think there is workaround for from the current implementation of BA. the only route i can see is creating a temporary better-auth instance make sure to expose the base options some where to avoid any duplciation and just modify the magicLink functionalities. as mention make sure to handle a db pool issue or make it excute timely as well even if the batch request and BA is using the same db instance.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#1780