tinysend

@tinysend/better-auth turns your better-auth users into a consented tinysend audience. Sign-ups become subscribers on a newsletter you can broadcast to, with tags and metadata synced onto the contact, account-security emails, and unsubscribes that propagate back into your own database.

Two parts, use either or both:

  • tinysend() — a real better-auth plugin: audience sync + security notifications + unsubscribe webhook
  • senders — option adapters that route better-auth’s own emails (verification, reset, magic link, OTP) through tinysend
npm install better-auth tinysend @tinysend/better-auth

The audience plugin

import { betterAuth } from 'better-auth';
import { tinysend } from '@tinysend/better-auth';

export const auth = betterAuth({
  plugins: [
    tinysend({
      apiKey: process.env.TINYSEND_API_KEY!,
      listId: 'lst_...',                 // the newsletter users land on
      appUrl: 'https://your-app.com',    // for the unsubscribe webhook
      optIn: 'double',                   // 'double' (default) | 'single'
      // map a user onto the tinysend contact (all additive, nothing is cleared):
      mapContact: (user) => ({
        tags: ['from:app', user.plan === 'pro' ? 'pro' : 'free'],
        metadata: { plan: user.plan, signup_source: 'web' },
        org: user.company,
        jobTitle: user.role,
      }),
    }),
  ],
});

Run npx @better-auth/cli generate after adding it — the plugin adds two fields to user and a small tinysendWebhook table.

How it works:

  • sign-up sync: on user create the plugin subscribes them (optIn: 'double' sends a confirmation email and holds at pending; 'single' subscribes immediately — only with explicit consent on your form). tags, metadata, org, and job title from mapContact are written onto the contact, additively.
  • unsubscribe always propagates back: footer link, one-click List-Unsubscribe, or replying STOP all fire a subscriber.unsubscribed webhook (HMAC-SHA256 verified) that updates the user row. the plugin self-registers that webhook on first sync.
  • client helpers: @tinysend/better-auth/client exposes authClient.tinysend.subscribe(), .unsubscribe(), and .preferences().

Security notifications

The plugin also emails users after events better-auth has no built-in callback for — password changed, email changed, 2FA enabled/disabled. These never block the auth operation. Disable per event with notifications: { passwordChanged: false }, or all with notifications: false.

Senders: route better-auth’s own emails through tinysend

The senders adapters wire better-auth’s email callbacks to tinysend. Because tinysend addresses are real two-way mailboxes, replies to those emails land in an inbox with webhooks and automations, not a dead no-reply.

import { betterAuth } from 'better-auth';
import { magicLink, emailOTP } from 'better-auth/plugins';
import { Tinysend } from 'tinysend';
import { senders } from '@tinysend/better-auth';

const ts = new Tinysend(process.env.TINYSEND_API_KEY!);

betterAuth({
  emailVerification: { sendVerificationEmail: senders.verification(ts) },
  emailAndPassword: { enabled: true, sendResetPassword: senders.reset(ts) },
  plugins: [
    magicLink({ sendMagicLink: senders.magicLink(ts) }),
    emailOTP({ sendVerificationOTP: senders.otp(ts) }),
  ],
});

senders covers verification, reset, magic link, OTP, two-factor, change-email, delete-account, and invitations — each returns the exact callback shape better-auth expects and throws on failure so better-auth surfaces “could not send email”. Every sender takes an optional template to override subject, html, text, and tag. With a mailbox key (sk_mbx_…) the from defaults to the mailbox; with an account-wide key, pass from: senders.verification(ts, { from: 'auth@yourdomain.com' }).

Testing auth flows with no signup

tinysend gives every account disposable inboxes over the API, so you can end-to-end test the whole verification/OTP loop in CI without a human mailbox — register anonymously, create an inbox, send through better-auth, read the OTP back.

Source

github.com/tiny-send/tinysend-better-auth. Questions: hi@tinysend.com.