Type-safe email library for Igniter.js with React Email templates and multiple provider adapters
npm install @igniter-js/mail

Type-safe email library for Igniter.js applications with React Email templates and multiple provider adapters. Send transactional emails with confidence using compile-time type safety and runtime validation.
- ✅ Type-Safe Templates - Full TypeScript inference with template payload validation
- ✅ React Email - Build beautiful emails with React components
- ✅ Multiple Providers - Resend, Postmark, SendGrid, SMTP
- ✅ Mock Adapter - In-memory adapter for unit testing
- ✅ Schema Validation - Runtime validation with StandardSchema (Zod 3.23+ or any StandardSchemaV1-compatible lib)
- ✅ Telemetry Ready - Optional integration with @igniter-js/telemetry
- ✅ Queue Integration - Schedule emails with BullMQ or custom queues
- ✅ Lifecycle Hooks - React to send events (started, success, error)
- ✅ Builder Pattern - Fluent API for configuration
- ✅ Server-First - Built for Node.js, Bun, Deno (no browser dependencies)
``bashnpm
npm install @igniter-js/mail @react-email/components react
$3
Install the adapter you need:
Resend:
`bash
npm install resend
`SMTP:
`bash
npm install nodemailer @types/nodemailer
`$3
Telemetry:
`bash
npm install @igniter-js/telemetry
`Validation (StandardSchemaV1-compatible):
`bash
npm install zod
`Quick Start
$3
Use React Email components to build your templates:
`tsx
// src/emails/welcome.tsx
import { Button, Html, Text } from '@react-email/components'export interface WelcomeEmailProps {
name: string
verifyUrl: string
}
export function WelcomeEmail({ name, verifyUrl }: WelcomeEmailProps) {
return (
Hi {name},
Welcome to our platform! Click below to verify your email:
)
}
`$3
`typescript
import { IgniterMail } from '@igniter-js/mail'
import { z } from 'zod'
import { WelcomeEmail } from './emails/welcome'// Create mail instance with builder
export const mail = IgniterMail.create()
.withFrom('no-reply@example.com')
.withAdapter('resend', process.env.RESEND_API_KEY!)
.addTemplate('welcome', {
subject: 'Welcome to Our Platform',
schema: z.object({
name: z.string(),
verifyUrl: z.string().url(),
}),
render: WelcomeEmail,
})
.build()
`$3
`typescript
// Send immediately
await mail.send({
to: 'user@example.com',
template: 'welcome',
data: {
name: 'John Doe',
verifyUrl: 'https://example.com/verify?token=abc123',
},
})// Schedule for later (requires withQueue configured)
await mail.schedule(
{
to: 'user@example.com',
template: 'welcome',
data: {
name: 'John Doe',
verifyUrl: 'https://example.com/verify?token=abc123',
},
},
new Date(Date.now() + 24 60 60 * 1000) // Send in 24 hours
)
`Core Concepts
$3
Templates combine React Email components with schema validation:
`typescript
import { IgniterMail } from '@igniter-js/mail'
import { z } from 'zod'const mail = IgniterMail.create()
.withFrom('no-reply@example.com')
.withAdapter('resend', process.env.IGNITER_MAIL_SECRET!)
.addTemplate('resetPassword', {
subject: 'Reset Your Password',
schema: z.object({
name: z.string(),
resetLink: z.string().url(),
expiresAt: z.date(),
}),
render: ({ name, resetLink, expiresAt }) => (
Hi {name},
Click below to reset your password:
This link expires at {expiresAt.toLocaleString()}
),
})
.build()
`$3
Access template metadata and render previews without sending:
`typescript
const templates = await mail.templates.list()
const template = await mail.templates.get('resetPassword')
const preview = await mail.templates.render('resetPassword', {
name: 'Jane',
resetLink: 'https://example.com/reset',
expiresAt: new Date(),
})
`$3
The library provides end-to-end type safety:
`typescript
// ✅ TypeScript knows 'welcome' template exists
await mail.send({
to: 'user@example.com',
template: 'welcome',
data: {
name: 'John Doe',
verifyUrl: 'https://example.com/verify',
},
})// ❌ TypeScript error: unknown template
await mail.send({
to: 'user@example.com',
template: 'unknown', // Error: Type '"unknown"' is not assignable to type '"welcome"'
data: {},
})
// ❌ TypeScript error: invalid data shape
await mail.send({
to: 'user@example.com',
template: 'welcome',
data: {
invalidProp: true, // Error: Object literal may only specify known properties
},
})
`$3
Templates support StandardSchemaV1 for runtime validation (Zod 3.23+ or any compatible library):
`typescript
import { z } from 'zod'
import { IgniterMail } from '@igniter-js/mail'const mail = IgniterMail.create()
.addTemplate('notification', {
subject: 'New Notification',
schema: z.object({
message: z.string().min(1).max(500),
priority: z.enum(['low', 'medium', 'high']),
}),
render: ({ message, priority }) => (
Priority: {priority}
{message}
),
})
.build()
// ✅ Valid data
await mail.send({
to: 'user@example.com',
template: 'notification',
data: {
message: 'Your order has shipped!',
priority: 'high',
},
})
// ❌ Runtime validation error
await mail.send({
to: 'user@example.com',
template: 'notification',
data: {
message: '', // Error: String must contain at least 1 character(s)
priority: 'urgent', // Error: Invalid enum value
},
})
`Adapters
$3
`typescript
import { IgniterMail } from '@igniter-js/mail'
import { ResendMailAdapterBuilder } from '@igniter-js/mail'// Using builder shorthand
const mail = IgniterMail.create()
.withFrom('no-reply@example.com')
.withAdapter('resend', process.env.RESEND_API_KEY!)
.build()
// Or using adapter builder
const adapter = ResendMailAdapterBuilder.create()
.withSecret(process.env.RESEND_API_KEY!)
.withFrom('no-reply@example.com')
.build()
const mail = IgniterMail.create()
.withFrom('no-reply@example.com')
.withAdapter(adapter)
.build()
`$3
`typescript
const mail = IgniterMail.create()
.withFrom('no-reply@example.com')
.withAdapter('postmark', process.env.POSTMARK_SERVER_TOKEN!)
.build()
`$3
`typescript
const mail = IgniterMail.create()
.withFrom('no-reply@example.com')
.withAdapter('sendgrid', process.env.SENDGRID_API_KEY!)
.build()
`$3
`typescript
const mail = IgniterMail.create()
.withFrom('no-reply@example.com')
.withAdapter('smtp', JSON.stringify({
host: 'smtp.gmail.com',
port: 587,
secure: false,
auth: {
user: process.env.SMTP_USER,
pass: process.env.SMTP_PASS,
},
}))
.build()
`$3
For unit tests:
`typescript
import { MockMailAdapter } from '@igniter-js/mail/adapters'const adapter = MockMailAdapter.create()
const mail = IgniterMail.create()
.withFrom('no-reply@example.com')
.withAdapter(adapter)
.build()
// Send email
await mail.send({ ... })
// Verify in tests
expect(adapter.sent).toHaveLength(1)
expect(adapter.sent[0].to).toBe('user@example.com')
expect(adapter.sent[0].html).toContain('Welcome')
`Queue Integration
Integrate with BullMQ or custom job queues for async email delivery:
`typescript
import { IgniterMail } from '@igniter-js/mail'
import { createBullMQAdapter } from '@igniter-js/adapter-bullmq'const queueAdapter = createBullMQAdapter({
connection: {
host: process.env.REDIS_HOST,
port: Number(process.env.REDIS_PORT),
},
})
const mail = IgniterMail.create()
.withFrom('no-reply@example.com')
.withAdapter('resend', process.env.RESEND_API_KEY!)
.withQueue(queueAdapter, {
queue: 'mail',
job: 'send',
attempts: 3,
removeOnComplete: true,
})
.addTemplate('welcome', { ... })
.build()
// This now enqueues the email instead of sending immediately
await mail.schedule(
{
to: 'user@example.com',
template: 'welcome',
data: { name: 'John' },
},
new Date(Date.now() + 60000) // Send in 1 minute
)
`Lifecycle Hooks
React to email sending events:
`typescript
const mail = IgniterMail.create()
.withFrom('no-reply@example.com')
.withAdapter('resend', process.env.RESEND_API_KEY!)
.onSendStarted(async (params) => {
console.log('Starting to send email:', params.template)
})
.onSendSuccess(async (params) => {
console.log('Email sent successfully:', params.template)
// Log to analytics, update database, etc.
})
.onSendError(async (params, error) => {
console.error('Failed to send email:', error)
// Log error, send alert, etc.
})
.build()
`Telemetry Integration
Use
@igniter-js/telemetry to capture mail events with a typed schema:`typescript
import { IgniterTelemetry } from '@igniter-js/telemetry'
import { IgniterMailTelemetryEvents } from '@igniter-js/mail/telemetry'const telemetry = IgniterTelemetry.create()
.withService('my-api')
.addEvents(IgniterMailTelemetryEvents)
.build()
const mail = IgniterMail.create()
.withTelemetry(telemetry)
// ...
.build()
`API Reference
$3
Main mail client created by the builder.
#### Methods
#####
send(params)Sends an email immediately.
`typescript
await mail.send({
to: string
template: TemplateKey
data: TemplatePayload
subject?: string // Optional subject override
})
`#####
schedule(params, date)Schedules an email for a future date. Requires a queue adapter.
`typescript
await mail.schedule(
{
to: string
template: TemplateKey
data: TemplatePayload
subject?: string
},
date: Date
)
`#####
templates.list()Lists template metadata available in the registry.
`typescript
const templates = await mail.templates.list()
`#####
templates.get(id)Gets a single template metadata entry.
`typescript
const template = await mail.templates.get('welcome')
`#####
templates.render(id, variables?)Renders a template to HTML and plain text without sending.
`typescript
const preview = await mail.templates.render('welcome', {
name: 'User',
})
`$3
Fluent API for configuring the mail service.
#### Methods
#####
create()Creates a new builder instance.
`typescript
const builder = IgniterMail.create()
`#####
withFrom(from)Sets the default FROM address.
`typescript
builder.withFrom('no-reply@example.com')
`#####
withAdapter(adapter)Sets the mail adapter (instance or provider + secret).
`typescript
// With provider string + secret
builder.withAdapter('resend', process.env.RESEND_API_KEY!)// With adapter instance
builder.withAdapter(adapterInstance)
`#####
withLogger(logger)Attaches a logger for debugging.
`typescript
builder.withLogger(logger)
`#####
withQueue(adapter, options?)Enables queue-based delivery.
`typescript
builder.withQueue(queueAdapter, {
queue: 'mail',
job: 'send',
attempts: 3,
})
`#####
addTemplate(key, template)Registers an email template.
`typescript
builder.addTemplate('welcome', {
subject: 'Welcome',
schema: z.object({ name: z.string() }),
render: ({ name }) => ...,
})
`#####
onSendStarted(hook)Registers a hook for send start events.
`typescript
builder.onSendStarted(async (params) => {
console.log('Sending:', params.template)
})
`#####
onSendSuccess(hook)Registers a hook for send success events.
`typescript
builder.onSendSuccess(async (params) => {
console.log('Sent:', params.template)
})
`#####
onSendError(hook)Registers a hook for send error events.
`typescript
builder.onSendError(async (params, error) => {
console.error('Failed:', error)
})
`#####
build()Builds the mail instance.
`typescript
const mail = builder.build()
`Error Handling
All errors are instances of
IgniterMailError with stable error codes:`typescript
try {
await mail.send({ ... })
} catch (error) {
if (error instanceof IgniterMailError) {
console.error('Code:', error.code)
console.error('Metadata:', error.metadata)
}
}
`Common Error Codes:
-
MAIL_PROVIDER_ADAPTER_REQUIRED - No adapter configured
- MAIL_PROVIDER_TEMPLATES_REQUIRED - No templates registered
- MAIL_PROVIDER_TEMPLATE_NOT_FOUND - Template key doesn't exist
- MAIL_PROVIDER_TEMPLATE_DATA_INVALID - Schema validation failed
- MAIL_PROVIDER_SEND_FAILED - Failed to send email
- MAIL_PROVIDER_SCHEDULE_DATE_INVALID - Schedule date is in the past
- MAIL_PROVIDER_SCHEDULE_QUEUE_NOT_CONFIGURED - Queue adapter is required for scheduling
- MAIL_PROVIDER_SCHEDULE_FAILED - Failed to schedule email
- MAIL_ADAPTER_CONFIGURATION_INVALID - Adapter configuration errorTypeScript Support
Full TypeScript support with compile-time type inference:
`typescript
const mail = IgniterMail.create()
.addTemplate('welcome', {
subject: 'Welcome',
schema: z.object({
name: z.string(),
email: z.string().email(),
}),
render: (props) => ...,
})
.build()// Type inference works!
type Templates = typeof mail.$Infer.Templates // 'welcome'
type WelcomePayload = typeof mail.$Infer.Payloads['welcome'] // { name: string; email: string }
type SendInput = typeof mail.$Infer.SendInput // Union of all send params
`Best Practices
1. Centralize Templates - Define all templates in one place for consistency
2. Use Schema Validation - Always provide schemas for runtime safety
3. Leverage Hooks - Use hooks for logging, analytics, and error tracking
4. Queue Heavy Loads - Use queue integration for high-volume sending
5. Mock Adapter First - Use MockMailAdapter in unit tests before deploying
6. Environment Variables - Store API keys and secrets in environment variables
7. Preview Emails - Use React Email's preview feature during development
Examples
$3
`tsx
import { Button, Html, Text } from '@react-email/components'
import { IgniterMail } from '@igniter-js/mail'
import { z } from 'zod'const mail = IgniterMail.create()
.withFrom('security@example.com')
.withAdapter('resend', process.env.RESEND_API_KEY!)
.addTemplate('resetPassword', {
subject: 'Reset Your Password',
schema: z.object({
name: z.string(),
resetLink: z.string().url(),
expiresIn: z.number(),
}),
render: ({ name, resetLink, expiresIn }) => (
Hi {name},
You requested to reset your password.
This link expires in {expiresIn} minutes.
),
})
.build()
// Usage
await mail.send({
to: 'user@example.com',
template: 'resetPassword',
data: {
name: 'John Doe',
resetLink: 'https://example.com/reset?token=abc',
expiresIn: 15,
},
})
`$3
`typescript
import { IgniterMail } from '@igniter-js/mail'
import { createBullMQAdapter } from '@igniter-js/adapter-bullmq'const queueAdapter = createBullMQAdapter({ ... })
const mail = IgniterMail.create()
.withFrom('orders@example.com')
.withAdapter('postmark', process.env.POSTMARK_TOKEN!)
.withQueue(queueAdapter, {
queue: 'mail',
job: 'send',
attempts: 5,
priority: 10,
})
.addTemplate('orderConfirmation', { ... })
.build()
// Sends via queue
await mail.send({
to: 'customer@example.com',
template: 'orderConfirmation',
data: { orderNumber: '12345', items: [...] },
})
``Contributions are welcome! Please see the main CONTRIBUTING.md for details.
MIT License - see LICENSE for details.
- Documentation: https://igniterjs.com/docs/mail
- GitHub: https://github.com/felipebarcelospro/igniter-js
- NPM: https://www.npmjs.com/package/@igniter-js/mail
- Issues: https://github.com/felipebarcelospro/igniter-js/issues
- React Email: https://react.email