Send transactional emails with React Email templates, recipient-locale rendering, and pluggable delivery adapters.
VitNode provides a unified transactional email service accessible via c.get("email"). Emails are rendered using React components, localized to the recipient's language, and delivered through your chosen provider.
Quick start
Queue an email from a Hono route
import { buildRoute } from '@/api/builders'
export const sendWelcomeRoute = buildRoute({
route: {
method: 'post',
path: '/welcome',
responses: { 200: { description: 'Email sent' } },
},
handler: async (c) => {
const user = c.get('user')
await c.get('email').send({
user, // Resolves email address and preferred locale
subject: 'Welcome to VitNode!',
content: () => 'Thank you for joining our community.',
})
return c.json({ sent: true })
},
})send() renders the template and queues delivery. It keeps a successful API
response quick even when the mail provider is having a moody afternoon. The
queued email is handed to the provider right after the response goes out, so
a sign-up or password-reset link doesn't wait for the next cron tick; if that
attempt fails, the queue worker retries it as usual.
Use await c.get('email').build(args) followed by deliver(email) only when
a route truly needs immediate delivery. send(args) is the normal, queued
path.
deliver() takes an optional idempotencyKey and returns what the provider
reported, such as its message id:
const email = await c.get('email').build(args)
const { id } = await c.get('email').deliver(email, {
idempotencyKey: `order-receipt:${order.id}`, // same key on every retry
})Adapters whose provider deduplicates on a key, like Resend, forward it - so a retry after an unconfirmed send is not delivered twice. SMTP has no such feature and ignores it. Notification emails use this for every send.
Configuring Email Adapters
Configure your delivery transport in apps/api/src/vitnode.api.config.ts:
Each provider ships as its own package. Install one, then pass it as email.adapter - the sender address belongs to the adapter.
1. Resend Adapter
import { ResendEmailAdapter } from '@vitnode/resend'
export const vitNodeApiConfig = buildApiConfig({
email: {
adapter: ResendEmailAdapter({
apiKey: process.env.RESEND_API_KEY,
from: process.env.RESEND_FROM_EMAIL,
}),
},
})See Resend for installation and setup.
2. SMTP Adapter (Nodemailer)
import { NodemailerEmailAdapter } from '@vitnode/nodemailer'
export const vitNodeApiConfig = buildApiConfig({
email: {
adapter: NodemailerEmailAdapter({
from: process.env.NODE_MAILER_FROM,
host: process.env.NODE_MAILER_HOST,
port: Number(process.env.NODE_MAILER_PORT ?? 587),
user: process.env.NODE_MAILER_USER,
password: process.env.NODE_MAILER_PASSWORD,
}),
},
})port defaults to 587 and secure to false. See Nodemailer for installation and setup.
Testing Email in AdminCP
Verify email configuration in the AdminCP at System → Integrations (/admin/core/system/integrations):
- Click Test Email on the email card.
- Enter an email address to dispatch an immediate test delivery.