npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@igniter-js/mail

v0.1.14

Published

Type-safe email library for Igniter.js with React Email templates and multiple provider adapters

Readme

@igniter-js/mail

NPM Version License: MIT

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.

Features

  • 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)

Installation

# npm
npm install @igniter-js/mail @react-email/components react

# pnpm
pnpm add @igniter-js/mail @react-email/components react

# yarn
yarn add @igniter-js/mail @react-email/components react

# bun
bun add @igniter-js/mail @react-email/components react

Provider Dependencies

Install the adapter you need:

Resend:

npm install resend

SMTP:

npm install nodemailer @types/nodemailer

Optional Dependencies

Telemetry:

npm install @igniter-js/telemetry

Validation (StandardSchemaV1-compatible):

npm install zod

Quick Start

1. Create Email Templates

Use React Email components to build your templates:

// 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 (
    <Html>
      <Text>Hi {name},</Text>
      <Text>Welcome to our platform! Click below to verify your email:</Text>
      <Button href={verifyUrl}>Verify Email</Button>
    </Html>
  )
}

2. Initialize Mail Service

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('[email protected]')
  .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. Send Emails

// Send immediately
await mail.send({
  to: '[email protected]',
  template: 'welcome',
  data: {
    name: 'John Doe',
    verifyUrl: 'https://example.com/verify?token=abc123',
  },
})

// Schedule for later (requires withQueue configured)
await mail.schedule(
  {
    to: '[email protected]',
    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

Templates

Templates combine React Email components with schema validation:

import { IgniterMail } from '@igniter-js/mail'
import { z } from 'zod'

const mail = IgniterMail.create()
  .withFrom('[email protected]')
  .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 }) => (
      <Html>
        <Text>Hi {name},</Text>
        <Text>Click below to reset your password:</Text>
        <Button href={resetLink}>Reset Password</Button>
        <Text>This link expires at {expiresAt.toLocaleString()}</Text>
      </Html>
    ),
  })
  .build()

Template Registry

Access template metadata and render previews without sending:

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(),
})

Type Safety

The library provides end-to-end type safety:

// ✅ TypeScript knows 'welcome' template exists
await mail.send({
  to: '[email protected]',
  template: 'welcome',
  data: {
    name: 'John Doe',
    verifyUrl: 'https://example.com/verify',
  },
})

// ❌ TypeScript error: unknown template
await mail.send({
  to: '[email protected]',
  template: 'unknown', // Error: Type '"unknown"' is not assignable to type '"welcome"'
  data: {},
})

// ❌ TypeScript error: invalid data shape
await mail.send({
  to: '[email protected]',
  template: 'welcome',
  data: {
    invalidProp: true, // Error: Object literal may only specify known properties
  },
})

Schema Validation

Templates support StandardSchemaV1 for runtime validation (Zod 3.23+ or any compatible library):

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 }) => (
      <Html>
        <Text>Priority: {priority}</Text>
        <Text>{message}</Text>
      </Html>
    ),
  })
  .build()

// ✅ Valid data
await mail.send({
  to: '[email protected]',
  template: 'notification',
  data: {
    message: 'Your order has shipped!',
    priority: 'high',
  },
})

// ❌ Runtime validation error
await mail.send({
  to: '[email protected]',
  template: 'notification',
  data: {
    message: '', // Error: String must contain at least 1 character(s)
    priority: 'urgent', // Error: Invalid enum value
  },
})

Adapters

Resend

import { IgniterMail } from '@igniter-js/mail'
import { ResendMailAdapterBuilder } from '@igniter-js/mail'

// Using builder shorthand
const mail = IgniterMail.create()
  .withFrom('[email protected]')
  .withAdapter('resend', process.env.RESEND_API_KEY!)
  .build()

// Or using adapter builder
const adapter = ResendMailAdapterBuilder.create()
  .withSecret(process.env.RESEND_API_KEY!)
  .withFrom('[email protected]')
  .build()

const mail = IgniterMail.create()
  .withFrom('[email protected]')
  .withAdapter(adapter)
  .build()

Postmark

const mail = IgniterMail.create()
  .withFrom('[email protected]')
  .withAdapter('postmark', process.env.POSTMARK_SERVER_TOKEN!)
  .build()

SendGrid

const mail = IgniterMail.create()
  .withFrom('[email protected]')
  .withAdapter('sendgrid', process.env.SENDGRID_API_KEY!)
  .build()

SMTP

const mail = IgniterMail.create()
  .withFrom('[email protected]')
  .withAdapter('smtp', JSON.stringify({
    host: 'smtp.gmail.com',
    port: 587,
    secure: false,
    auth: {
      user: process.env.SMTP_USER,
      pass: process.env.SMTP_PASS,
    },
  }))
  .build()

Mock Adapter

For unit tests:

import { MockMailAdapter } from '@igniter-js/mail/adapters'

const adapter = MockMailAdapter.create()

const mail = IgniterMail.create()
  .withFrom('[email protected]')
  .withAdapter(adapter)
  .build()

// Send email
await mail.send({ ... })

// Verify in tests
expect(adapter.sent).toHaveLength(1)
expect(adapter.sent[0].to).toBe('[email protected]')
expect(adapter.sent[0].html).toContain('Welcome')

Queue Integration

Integrate with BullMQ or custom job queues for async email delivery:

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('[email protected]')
  .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: '[email protected]',
    template: 'welcome',
    data: { name: 'John' },
  },
  new Date(Date.now() + 60000) // Send in 1 minute
)

Lifecycle Hooks

React to email sending events:

const mail = IgniterMail.create()
  .withFrom('[email protected]')
  .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:

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

IgniterMail

Main mail client created by the builder.

Methods

send(params)

Sends an email immediately.

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.

await mail.schedule(
  {
    to: string
    template: TemplateKey
    data: TemplatePayload
    subject?: string
  },
  date: Date
)
templates.list()

Lists template metadata available in the registry.

const templates = await mail.templates.list()
templates.get(id)

Gets a single template metadata entry.

const template = await mail.templates.get('welcome')
templates.render(id, variables?)

Renders a template to HTML and plain text without sending.

const preview = await mail.templates.render('welcome', {
  name: 'User',
})

IgniterMailBuilder

Fluent API for configuring the mail service.

Methods

create()

Creates a new builder instance.

const builder = IgniterMail.create()
withFrom(from)

Sets the default FROM address.

builder.withFrom('[email protected]')
withAdapter(adapter)

Sets the mail adapter (instance or provider + secret).

// 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.

builder.withLogger(logger)
withQueue(adapter, options?)

Enables queue-based delivery.

builder.withQueue(queueAdapter, {
  queue: 'mail',
  job: 'send',
  attempts: 3,
})
addTemplate(key, template)

Registers an email template.

builder.addTemplate('welcome', {
  subject: 'Welcome',
  schema: z.object({ name: z.string() }),
  render: ({ name }) => <Html>...</Html>,
})
onSendStarted(hook)

Registers a hook for send start events.

builder.onSendStarted(async (params) => {
  console.log('Sending:', params.template)
})
onSendSuccess(hook)

Registers a hook for send success events.

builder.onSendSuccess(async (params) => {
  console.log('Sent:', params.template)
})
onSendError(hook)

Registers a hook for send error events.

builder.onSendError(async (params, error) => {
  console.error('Failed:', error)
})
build()

Builds the mail instance.

const mail = builder.build()

Error Handling

All errors are instances of IgniterMailError with stable error codes:

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 error

TypeScript Support

Full TypeScript support with compile-time type inference:

const mail = IgniterMail.create()
  .addTemplate('welcome', {
    subject: 'Welcome',
    schema: z.object({
      name: z.string(),
      email: z.string().email(),
    }),
    render: (props) => <Html>...</Html>,
  })
  .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

Password Reset Email

import { Button, Html, Text } from '@react-email/components'
import { IgniterMail } from '@igniter-js/mail'
import { z } from 'zod'

const mail = IgniterMail.create()
  .withFrom('[email protected]')
  .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 }) => (
      <Html>
        <Text>Hi {name},</Text>
        <Text>You requested to reset your password.</Text>
        <Button href={resetLink}>Reset Password</Button>
        <Text>This link expires in {expiresIn} minutes.</Text>
      </Html>
    ),
  })
  .build()

// Usage
await mail.send({
  to: '[email protected]',
  template: 'resetPassword',
  data: {
    name: 'John Doe',
    resetLink: 'https://example.com/reset?token=abc',
    expiresIn: 15,
  },
})

Order Confirmation with Queue

import { IgniterMail } from '@igniter-js/mail'
import { createBullMQAdapter } from '@igniter-js/adapter-bullmq'

const queueAdapter = createBullMQAdapter({ ... })

const mail = IgniterMail.create()
  .withFrom('[email protected]')
  .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: '[email protected]',
  template: 'orderConfirmation',
  data: { orderNumber: '12345', items: [...] },
})

Contributing

Contributions are welcome! Please see the main CONTRIBUTING.md for details.

License

MIT License - see LICENSE for details.

Links

  • 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