Send Emails with NestJS
Create Email Templates and send them with nodemailer from your Nest application
- Authors
- 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
).
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.
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.