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

@devoven/email

v0.1.1

Published

Email Package for NestJS Hexagonal Architecture

Downloads

445

Readme

@devoven/email

Email delivery service for NestJS. Send raw and templated emails via SMTP or a console sink for local development.

Installation

npm install @devoven/email
# or
pnpm add @devoven/email

Peer Dependencies

Any NestJS application already includes the standard peers (@nestjs/common, @nestjs/core, rxjs, reflect-metadata). No extra install is needed for those.

If you want to send emails via SMTP, install the optional nodemailer peer:

npm install nodemailer

Quick Start

import { EmailModule } from '@devoven/email';

@Module({
  imports: [
    EmailModule.register({
      defaultSender: '[email protected]',
    }),
  ],
})
export class AppModule {}

Without smtpConfig the module uses ConsoleEmailProvider, which prints emails to stdout. Pass smtpConfig to switch to NodemailerProvider.

Module Options

| Option | Type | Default | Description | |--------|------|---------|-------------| | defaultSender | string | — (required) | The from address used when no sender is specified on the message | | smtpConfig | SmtpConfig | undefined | SMTP connection config. When provided, NodemailerProvider is used; otherwise ConsoleEmailProvider | | smtpConfig.host | string | — | SMTP server hostname | | smtpConfig.port | number | — | SMTP server port | | smtpConfig.secure | boolean | — | Use TLS. When true, auth is required | | smtpConfig.auth.user | string | — | SMTP username | | smtpConfig.auth.pass | string | — | SMTP password | | templateRegistry | Class or instance | InMemoryEmailRepository | Custom EmailTemplateRepositoryPort implementation | | templateRenderer | Class or instance | SimpleEmailTemplateRenderer | Custom EmailTemplateRenderPort implementation | | templates | EmailTemplateValueObject[] | [] | Templates seeded into the repository on module init | | global | boolean | false | Register the module globally |

SMTP validation rules

  • Both host and port must be provided together.
  • When secure is true, auth must be provided.
  • When auth is provided, both user and pass are required.

Async Registration

Use registerAsync when SMTP credentials come from environment variables or a config service:

import { EmailModule } from '@devoven/email';
import { ConfigService } from '@nestjs/config';

@Module({
  imports: [
    EmailModule.registerAsync({
      useFactory: (config: ConfigService) => ({
        defaultSender: config.get('MAIL_FROM'),
        smtpConfig: {
          host: config.get('SMTP_HOST'),
          port: config.get<number>('SMTP_PORT'),
          secure: config.get<boolean>('SMTP_SECURE'),
          auth: {
            user: config.get('SMTP_USER'),
            pass: config.get('SMTP_PASS'),
          },
        },
      }),
      inject: [ConfigService],
      global: true,
    }),
  ],
})
export class AppModule {}

Sending Emails

Inject TOKENS.SendEmailPort for raw messages or TOKENS.SendTemplateEmailPort for template-based emails. Both tokens are exported from the module.

Raw email

import { Inject, Injectable } from '@nestjs/common';
import { TOKENS, SendEmailPort, EmailMessageValueObject } from '@devoven/email';

@Injectable()
export class NotificationService {
  constructor(
    @Inject(TOKENS.SendEmailPort)
    private readonly sendEmail: SendEmailPort,
  ) {}

  async sendWelcome(to: string) {
    const message = EmailMessageValueObject.create({
      recipients: to,
      subject: 'Welcome!',
      html: '<p>Thanks for signing up.</p>',
    });

    await this.sendEmail.execute(message);
  }
}

Templated email

import { Inject, Injectable } from '@nestjs/common';
import { TOKENS, SendTemplateEmailPort } from '@devoven/email';

@Injectable()
export class NotificationService {
  constructor(
    @Inject(TOKENS.SendTemplateEmailPort)
    private readonly sendTemplate: SendTemplateEmailPort,
  ) {}

  async sendPasswordReset(to: string, resetUrl: string) {
    await this.sendTemplate.execute('password-reset', {
      recipients: to,
      variables: { resetUrl },
    });
  }
}

Registering templates at startup

Pass templates to seed the repository when the module initialises:

import { EmailModule, EmailTemplateValueObject } from '@devoven/email';

EmailModule.register({
  defaultSender: '[email protected]',
  templates: [
    EmailTemplateValueObject.create({
      name: 'password-reset',
      subject: 'Reset your password',
      html: '<p>Click <a href="${resetUrl}">here</a> to reset your password.</p>',
    }),
  ],
})

The built-in SimpleEmailTemplateRenderer replaces ${variableName} placeholders with values from the variables map.

Architecture

The module follows hexagonal architecture. The two driving ports are exported and available for injection in consuming services.

Port / Token Mapping

| Token | Interface | Default Implementation | Purpose | |-------|-----------|------------------------|---------| | TOKENS.SendEmailPort | SendEmailPort | SendEmailUseCase | Send a raw EmailMessageValueObject | | TOKENS.SendTemplateEmailPort | SendTemplateEmailPort | SendTemplateEmailUseCase | Look up a named template and send a rendered email | | TOKENS.EmailProviderPort | EmailProviderPort | ConsoleEmailProvider or NodemailerProvider | Delivery transport | | TOKENS.EmailTemplateRepositoryPort | EmailTemplateRepositoryPort | InMemoryEmailRepository | Store and retrieve email templates | | TOKENS.EmailTemplateRenderPort | EmailTemplateRenderPort | SimpleEmailTemplateRenderer | Render template strings with variable substitution |

The two driving ports (SendEmailPort, SendTemplateEmailPort) and the template repository port (EmailTemplateRepositoryPort) are exported from the module so consuming services can inject them.

Driven port interfaces

EmailProviderPort — delivery transport:

interface EmailProviderPort {
  send(emailMessage: EmailMessageValueObject): Promise<void>;
}

EmailTemplateRepositoryPort — template storage:

interface EmailTemplateRepositoryPort {
  findByName(name: string): Promise<EmailTemplateValueObject | undefined>;
  register(template: EmailTemplateValueObject): Promise<void>;
}

EmailTemplateRenderPort — template rendering:

interface EmailTemplateRenderPort {
  render(template: string, variables: Record<string, unknown>): string;
}

Domain Model

EmailMessageValueObject

Created via EmailMessageValueObject.create({ recipients, subject, html?, text?, from?, cc?, bcc? }). At least one of html or text is required. All email addresses are validated on construction.

| Property | Type | Description | |----------|------|-------------| | recipients | string \| string[] | Primary recipient(s) | | subject | string | Email subject line | | html | string \| undefined | HTML body | | text | string \| undefined | Plain-text body | | from | string \| undefined | Sender address (falls back to defaultSender if omitted) | | cc | string \| string[] \| undefined | CC recipients | | bcc | string \| string[] \| undefined | BCC recipients |

withFrom(address) returns a new value object with the sender set. If a sender is already present it is a no-op.

EmailTemplateValueObject

Created via EmailTemplateValueObject.create({ name, subject, html?, text? }). At least one of html or text is required.

| Property | Type | Description | |----------|------|-------------| | name | string | Unique template identifier | | subject | string | Template subject line | | html | string \| undefined | HTML template body | | text | string \| undefined | Plain-text template body |

Custom Adapters

Custom email provider (e.g. SendGrid)

import { Injectable } from '@nestjs/common';
import { EmailProviderPort, EmailMessageValueObject } from '@devoven/email';

@Injectable()
export class SendGridProvider implements EmailProviderPort {
  async send(message: EmailMessageValueObject): Promise<void> {
    // call SendGrid SDK
  }
}

Pass it as templateRenderer or use TOKENS.EmailProviderPort directly. For a fully custom provider, pass it via EmailModule.register — the module currently selects the provider automatically based on the presence of smtpConfig. To supply an arbitrary provider, implement EmailProviderPort and wire it yourself by extending EmailModule or wrapping it in a feature module.

Custom template repository

import { Injectable } from '@nestjs/common';
import {
  EmailTemplateRepositoryPort,
  EmailTemplateValueObject,
} from '@devoven/email';

@Injectable()
export class PrismaEmailTemplateRepository implements EmailTemplateRepositoryPort {
  async findByName(name: string): Promise<EmailTemplateValueObject | undefined> {
    // query database
  }

  async register(template: EmailTemplateValueObject): Promise<void> {
    // persist template
  }
}

Pass it to register:

EmailModule.register({
  defaultSender: '[email protected]',
  templateRegistry: PrismaEmailTemplateRepository,
})

Custom template renderer

import { Injectable } from '@nestjs/common';
import { EmailTemplateRenderPort } from '@devoven/email';
import * as Handlebars from 'handlebars';

@Injectable()
export class HandlebarsRenderer implements EmailTemplateRenderPort {
  render(template: string, variables: Record<string, unknown>): string {
    return Handlebars.compile(template)(variables);
  }
}
EmailModule.register({
  defaultSender: '[email protected]',
  templateRenderer: HandlebarsRenderer,
})