bunderstackbunderstack/ docs

Email

Send emails with resend, SMTP, console, or a custom adapter

Bunderstack includes an email facade with pluggable providers. Add an email key to your config and use app.email.send() anywhere on the server.

Configuration

import { bunderstack } from 'bunderstack'
import { libsql } from 'bunderstack/libsql'
import * as schema from './schema'

export const backend = bunderstack({
  schema,
  database: {
    adapter: libsql(),
    url: 'file:./data.db',
  },
  auth: { emailAndPassword: { enabled: true } },
  email: {
    from: '[email protected]',
    provider: 'resend', // or 'console' or a custom adapter
  },
})

export const app = await backend.start()

Providers

Resend

RESEND_API_KEY=re_xxx bun run server.ts
email: {
  from: '[email protected]',
  provider: 'resend',
}

Uses the Resend API. Set RESEND_API_KEY in your environment. Bunderstack validates it at boot when provider: 'resend'.

SMTP

SMTP_URL=smtps://user:[email protected]:465 bun run server.ts
import { smtp } from 'bunderstack/email-smtp'

email: {
  from: '[email protected]',
  provider: smtp({ url: process.env.SMTP_URL! }),
}

Uses nodemailer under the hood. Install it as an optional peer:

bun add nodemailer

The SMTP integration is isolated to this subpath, so projects that do not use it do not load Nodemailer.

Console (development default)

When no provider is specified in development, emails are logged to the console instead of being sent. In production, omitting a provider throws at boot.

email: {
  from: '[email protected]'
}
// provider defaults to 'console' in development — logs to stdout

Custom adapter

Pass a full EmailAdapter or just a send function:

email: {
  from: '[email protected]',
  provider: {
    async send(msg) {
      // msg has `from` already resolved
      await mySendService(msg)
      return { id: 'msg_123' }
    },
  },
}

Or the function shorthand:

email: {
  from: '[email protected]',
  provider: async (msg) => {
    await fetch('https://my-email-api.com/send', {
      method: 'POST',
      body: JSON.stringify(msg),
    })
    return {}
  },
}

Sending

await app.email.send({
  to: '[email protected]',
  subject: 'Welcome!',
  html: '<h1>Hello</h1>',
  text: 'Hello',
})

All fields except subject and one of html/text are optional. The from field defaults to the config's from but can be overridden per-message.

await app.email.send({
  to: ['[email protected]', '[email protected]'],
  subject: 'Team update',
  html: '<p>Hi team</p>',
  from: '[email protected]', // overrides config default
  replyTo: '[email protected]',
  cc: '[email protected]',
  bcc: '[email protected]',
})

Returns { id?: string } — the provider-specific message ID when available.

BetterAuth auto-wiring

When you configure an email key, Bunderstack automatically wires it into BetterAuth for email verification and password reset flows. No extra config needed — just add email to bunderstack.

Without email

If you don't need email, omit the email key entirely. app.email is still present on the app, but calling send() throws with a descriptive error.

On this page