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.tsemail: {
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.tsimport { 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 nodemailerThe 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 stdoutCustom 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.