Send Emails with NestJS

Create Email Templates and send them with nodemailer from your Nest application

Authors
Marc Stammerjohann Marc Stammerjohann
Published at

This post gets you up and running with everything you need to know about sending Emails using nest-modules/mailer in your NestJS backend. πŸ‘‡

πŸ“§ Sending emails using Nodemailer
🧩 Creating email templates with handlebars (alternatives: pug or ejs)
βš™οΈ Configure smtp via .env file

If you want to craft beautiful email templates follow the new post

Install Dependencies

Add the @nestjs-modules/mailer and the peer dependency nodemailer to your Nest application. Choose one of the supported template engines for creating your email templates: handlebars, pug or ejs.

npm install --save @nestjs-modules/mailer nodemailer
npm install --save-dev @types/nodemailer

# pick one template adapter and install
npm install --save handlebars
# or
npm install --save pug
# or
npm install --save ejs

In this guide, you are creating email templates using handlebars.

npm install --save @nestjs-modules/mailer nodemailer handlebars
npm install --save-dev @types/nodemailer

Mail Module

Let's begin with creating a mail module and service via the Nest CLI and followed by creating a templates folder.

nest g module mail
nest g service mail

mkdir src/mail/templates

Import the MailerModule into your MailModule and configure your mail server transport via smtp. Provide a default from email address to consistently use the same mail throughout your application. No worries, you can always override the default whenever necessary. Last step, configure the templates folder and the adapter in this case HandlebarsAdapter. Find out more about the other template adapters in the Mailer documentation.

import { MailerModule } from '@nestjs-modules/mailer';
import { HandlebarsAdapter } from '@nestjs-modules/mailer/dist/adapters/handlebars.adapter';
import { Module } from '@nestjs/common';
import { MailService } from './mail.service';
import { join } from 'path';

@Module({
  imports: [
    MailerModule.forRoot({
      // transport: 'smtps://user@example.com:topsecret@smtp.example.com',
      // or
      transport: {
        host: 'smtp.example.com',
        secure: false,
        auth: {
          user: 'user@example.com',
          pass: 'topsecret',
        },
      },
      defaults: {
        from: '"No Reply" <noreply@example.com>',
      },
      template: {
        dir: join(__dirname, 'templates'),
        adapter: new HandlebarsAdapter(), // or new PugAdapter() or new EjsAdapter()
        options: {
          strict: true,
        },
      },
    }),
  ],
  providers: [MailService],
  exports: [MailService], // πŸ‘ˆ export for DI
})
export class MailModule {}

Export the MailService to provide it via Dependency Injection (DI) for your controllers, resolvers and services.

Handlebars Mail Template

Create your first email template confirmation.hbs in the src/mail/templates folder. Add the following simple template for a user confirmation.

<p>Hey {{ name }},</p>
<p>Please click below to confirm your email</p>
<p>
    <a href="{{ url }}">Confirm</a>
</p>

<p>If you did not request this email you can safely ignore it.</p>

Those curly brackets are handlebars expressions and you will provide the context later while sending an email.

When you build your Nest application you will notice that the build output is missing your template files (dist/mail/templates).

handlebars templates missing in compilation output

By default, Nest only distributes TypeScript compiled files (.js and .d.ts) during the build step. To distribute your .hbs files, open your nest-cli.json and add your templates directory to the assets property in the global compilerOptions.

{
  "collection": "@nestjs/schematics",
  "sourceRoot": "src",
  "compilerOptions": {
    "assets": ["mail/templates/**/*"], // πŸ‘ˆ  or "**/*.hbs" all files ending with .hbs
    "watchAssets": true // πŸ€– copy assets in watch mode
  }
}

Build your Nest application again and now your template files are included in the build output.

handlebars templates included in compilation output

Sending Mail

Add MailerService to your own MailService and implement your mailing logic here. Let's send a user confirmation email using the template confirmation.hbs. You need to provide {{ name }} and {{ url }} under the context key. Read the Handlebars documentation for more background like Nested input objects.

import { MailerService } from '@nestjs-modules/mailer';
import { Injectable } from '@nestjs/common';
import { User } from './../user/user.entity';

@Injectable()
export class MailService {
  constructor(private mailerService: MailerService) {}

  async sendUserConfirmation(user: User, token: string) {
    const url = `example.com/auth/confirm?token=${token}`;

    await this.mailerService.sendMail({
      to: user.email,
      // from: '"Support Team" <support@example.com>', // override default from
      subject: 'Welcome to Nice App! Confirm your Email',
      template: './confirmation', // `.hbs` extension is appended automatically
      context: { // ✏️ filling curly brackets with content
        name: user.name,
        url,
      },
    });
  }
}
// ./../user/user.entity
export interface User {
  email: string;
  name: string;
}

Using Mail Service

Add the MailModule to the imports list of your modules which need to use the MailService.

import { Module } from '@nestjs/common';
import { AuthController } from './auth.controller';
import { AuthService } from './auth.service';
import { MailModule } from './mail/mail.module';

@Module({
  imports: [MailModule], // πŸ“§
  controllers: [AuthController],
  providers: [AuthService],
})
export class AuthModule {}

Now you can add MailService to the constructor of your controllers, resolvers and services

import { Injectable } from '@nestjs/common';
import { MailService } from './../mail/mail.service';
import { User } from './../user/user.entity';

@Injectable()
export class AuthService {
  constructor(private mailService: MailService) {}

  async signUp(user: User) {
    const token = Math.floor(1000 + Math.random() * 9000).toString();
    // create user in db
    // ...
    // send confirmation mail
    await this.mailService.sendUserConfirmation(user, token);
  }
}

Move configurations to dotenv file

Currently, the mail server configurations are hardcoded in to the MailModule. Nest provides a configuration module which enables you to load your configurations and credentials from .env files.

Install the @nestjs/config dependency.

# config 
npm i --save @nestjs/config

Add the ConfigModule to the imports list of your AppModule.

import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { AuthModule } from './auth/auth.module';

@Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true, // no need to import into other modules
    }),
    AuthModule,
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

Create a .env file in your root directory and don't forget to add in your .gitingore file.

# mail
MAIL_HOST=smtp.example.com
MAIL_USER=user@example.com
MAIL_PASSWORD=topsecret
MAIL_FROM=noreply@example.com

# optional
MAIL_TRANSPORT=smtp://${MAIL_USER}:${MAIL_PASSWORD}@${MAIL_HOST}

Reopen MailModule and change MailerModule.forRoot to MailerModule.forRootAsync, this allows you to inject and use the ConfigService.

import { MailerModule } from '@nestjs-modules/mailer';
import { HandlebarsAdapter } from '@nestjs-modules/mailer/dist/adapters/handlebars.adapter';
import { Module } from '@nestjs/common';
import { MailService } from './mail.service';
import { join } from 'path';
import { ConfigModule, ConfigService } from '@nestjs/config';

@Module({
  imports: [
    MailerModule.forRootAsync({
      // imports: [ConfigModule], // import module if not enabled globally
      useFactory: async (config: ConfigService) => ({
        // transport: config.get("MAIL_TRANSPORT"),
        // or
        transport: {
          host: config.get('MAIL_HOST'),
          secure: false,
          auth: {
            user: config.get('MAIL_USER'),
            pass: config.get('MAIL_PASSWORD'),
          },
        },
        defaults: {
          from: `"No Reply" <${config.get('MAIL_FROM')}>`,
        },
        template: {
          dir: join(__dirname, 'templates'),
          adapter: new HandlebarsAdapter(),
          options: {
            strict: true,
          },
        },
      }),
      inject: [ConfigService],
    }),
  ],
  providers: [MailService],
  exports: [MailService],
})
export class MailModule {}

Time to add your own mail server configuration, start Nest and send your first mails πŸ“§ to your users.

Global Mail Module

You might need to send mails throughout the entire Nest application. Make the MailModule a global module by adding the @Global() decorator to MailModule and only import it once in your AppModule. All modules can now inject the MailService without forgetting to import the MailModule.

import { MailerModule } from '@nestjs-modules/mailer';
import { HandlebarsAdapter } from '@nestjs-modules/mailer/dist/adapters/handlebars.adapter';
import { Global, Module } from '@nestjs/common';
import { MailService } from './mail.service';
import { join } from 'path';
import { ConfigModule, ConfigService } from '@nestjs/config';

@Global() // πŸ‘ˆ global module
@Module({
  imports: [
    MailerModule.forRootAsync({
      // imports: [ConfigModule], // import module if not enabled globally
      useFactory: async (config: ConfigService) => ({
        // transport: config.get("MAIL_TRANSPORT"),
        // or
        transport: {
          host: config.get('MAIL_HOST'),
          secure: false,
          auth: {
            user: config.get('MAIL_USER'),
            pass: config.get('MAIL_PASSWORD'),
          },
        },
        defaults: {
          from: `"No Reply" <${config.get('MAIL_FROM')}>`,
        },
        template: {
          dir: join(__dirname, 'templates'),
          adapter: new HandlebarsAdapter(),
          options: {
            strict: true,
          },
        },
      }),
      inject: [ConfigService],
    }),
  ],
  providers: [MailService],
  exports: [MailService],
})
export class MailModule {}

Breaking Changes

The latest version of nest-modules/mailer v1.6.0 contains a breaking change how templates are looked up.

You need to add ./ to your template names when using v1.6.0, see example below.

await this.mailerService.sendMail({
  to: user.email,
  subject: 'Welcome to Nice App! Confirm your Email',
- template: 'confirmation', // ❌ template not found in v1.6.0, works fine in v1.5.x
+ template: './confirmation', // βœ… template found again in v1.6.0
  context: { 
    name: user.name,
    url,
  },
});

Sponsor us

Did you find this post useful? We at notiz.dev write about our experiences developing Apps, Websites and APIs and develop Open Source tools. Your support would mean a lot to us πŸ™. Receive a reward by sponsoring us on Patreon or start with a one-time donation on GitHub Sponsors.

Table of Contents

Top of Page Comments Related Articles

Related Posts

Find more posts like this one.

Authors
Marc Stammerjohann
October 17, 2022

Codegen REST API types and requests for Angular

Automatic code generation from OpenAPI 3 for Angular
Angular NestJS Read More
Authors
Marc Stammerjohann
July 27, 2022

Downloading files with NestJS

Setup type-safe endpoints for downloading files in your NestJS application.
NestJS Read More
Authors
Marc Stammerjohann
July 08, 2022

Maizzle: Craft beautiful HTML emails with Tailwind CSS

Send beautiful HTML emails via NestJS crafted with Maizzle and Tailwind CSS
Maizzle Tailwind CSS NestJS Read More
Authors
Marc Stammerjohann
August 26, 2021

NestJS: Type-safe File Uploads

Learn how to apply Swagger decorators for type-safe file upload endpoints.
NestJS Read More
Authors
Marc Stammerjohann
July 27, 2022

OpenAPI for your REST APIs in NestJS

Setup Swagger to generate an OpenAPI documentation for your REST endpoints.
NestJS Read More
Authors
Marc Stammerjohann
September 12, 2022

Introducing NestJS Prisma Library and Schematics

Library and schematics to add Prisma integration to a NestJS application
NestJS Prisma Read More
Authors
Marc Stammerjohann
November 09, 2021

Dockerizing a NestJS app with Prisma and PostgreSQL

How to dockerize a NestJS application with Prisma and PostgreSQL.
NestJS Prisma Docker Read More
Authors
Marc Stammerjohann
April 07, 2020

GraphQL Code-First Approach with NestJS 7

Create a GraphQL API using Code-First Approach with NestJS 7.
NestJS GraphQL Prisma Read More

Sign up for our newsletter

Sign up for our newsletter to stay up to date. Sent every other week.

We care about the protection of your data. Read our Privacy Policy.