Configure and send typed emails
The email channel is provided by @betternotify/email. It defines the slots, validation, and rendering logic for email notifications — subject, template, from address, reply-to, tags, and priority.
When to use email
Email is the right channel when the recipient needs a persistent, rich-content notification they can read on their own time. Use it for transactional messages (welcome emails, password resets, order confirmations), marketing campaigns, invoices, reports, or anything where the content benefits from HTML formatting, attachments, or a paper trail the recipient can search later.
npm install @betternotify/emailSetup
Create the channel with optional defaults that apply to every email procedure:
import { createNotify } from '@betternotify/core';
import { emailChannel } from '@betternotify/email';
const email = emailChannel({
defaults: {
from: { name: 'My App', email: 'hello@myapp.com' },
replyTo: 'support@myapp.com',
headers: { 'X-App': 'myapp' },
},
});
const rpc = createNotify({ channels: { email } });Channel defaults
Prop
Type
Slots
These are the builder methods available on rpc.email():
Prop
Type
Send arguments
When calling .send(), these are the arguments the caller provides:
Prop
Type
Full example
import { createNotify, createClient } from '@betternotify/core';
import { emailChannel } from '@betternotify/email';
import { smtpTransport } from '@betternotify/smtp';
import { z } from 'zod';
const email = emailChannel({
defaults: { from: { name: 'Acme', email: 'noreply@acme.com' } },
});
const rpc = createNotify({ channels: { email } });
const catalog = rpc.catalog({
welcome: rpc
.email()
.input(z.object({ name: z.string(), verifyUrl: z.string().url() }))
.subject(({ input }) => `Welcome, ${input.name}!`)
.template({
render: async ({ input }) => ({
html: `<h1>Welcome, ${input.name}!</h1><a href="${input.verifyUrl}">Verify</a>`,
text: `Welcome, ${input.name}! Verify: ${input.verifyUrl}`,
}),
}),
});
const mail = createClient({
catalog,
transportsByChannel: {
email: smtpTransport({ host: 'smtp.gmail.com', port: 587, auth: { user: '...', pass: '...' } }),
},
});
await mail.welcome.send({
to: 'ada@example.com',
input: { name: 'Ada', verifyUrl: 'https://acme.com/verify?token=abc' },
});Templates
The template slot accepts either a TemplateAdapter object or a plain function. For React Email, MJML, or Handlebars templates, see Templates.
// Plain function
.template(async ({ input }) => ({
html: `<p>Hello ${input.name}</p>`,
text: `Hello ${input.name}`,
}))
// TemplateAdapter object
.template({
render: async ({ input }) => ({
html: `<p>Hello ${input.name}</p>`,
text: `Hello ${input.name}`,
}),
})The template can also return a subject field to override the .subject() slot — useful when the template adapter controls the full email.
Address format
Addresses accept a string or an object with name and email:
// String
await mail.welcome.send({ to: 'ada@example.com', input });
// Object with display name
await mail.welcome.send({
to: { name: 'Ada Lovelace', email: 'ada@example.com' },
input,
});
// Array of recipients
await mail.welcome.send({
to: [
'ada@example.com',
{ name: 'Charles Babbage', email: 'charles@example.com' },
],
input,
});