- Published on
Implementing Email Service in NestJS: A Step-by-Step Guide 📬
- Authors
- Name
Hello fellow developers! If you’ve been exploring the realms of JS, TS, and popular frameworks like I have, you’ll be excited about our topic today. NestJS has been a personal favorite of mine, and today, we’ll delve into one of its nifty features — sending emails.
By the end of this guide, you’ll not only understand the process of sending emails with NestJS, but you’ll also have an overview of crafting beautiful email templates using EJS. So, let’s dive in!
👉 How emergence of new tech changes the software development landscape:
1. Setting Up Our Nest Project 🛠️
Start by creating a fresh NestJS project with the following commands:
nest new nestjs-email
cd nestjs-email
2. Installing the Necessary Packages 📦
Before we embark on our email journey, let’s equip ourselves with the right tools.
npm i @nestjs-modules/mailer
And for our EJS templates:
npm i ejs
3. Setting Up Our Email Resource 💌
To create the foundation for our emails, initiate the email resource:
nest g resource email
4. Configuring the Email Module 🖇️
Hop into the email module, and update the imports section as follows:
@Module({
imports: [
MailerModule.forRoot({
transport: {
host: '<host>',
port: Number('<port>'),
secure: false,
auth: {
user: '<username>',
pass: '<password>',
},
},
defaults: {
from: '"From Name" <from@example.com>',
},
template: {
dir: join(__dirname, 'templates'),
adapter: new EjsAdapter(),
options: {
strict: true,
},
},
}),
],
controllers: [EmailController],
providers: [EmailService],
})
Ensure you’ve imported the EjsAdapter:
import { EjsAdapter } from '@nestjs-modules/mailer/dist/adapters/ejs.adapter';
Do not forget to add this in nest-cli.json
{
"collection": "@nestjs/schematics",
"sourceRoot": "src",
"compilerOptions": {
"assets": ["mail/templates/**/*"], // Treat templates as assets
"watchAssets": true // Enable real-time template copying
}
}
5. Crafting the Email Sending Function 📤
Lastly, within our service, we’ll implement a function to send the welcome email:
import { MailerService } from '@nestjs-modules/mailer';
interface Email {
to: string;
data: any;
}
export class EmailService {
constructor(private readonly mailerService: MailerService) {}
async welcomeEmail(data) {
const { email, name } = data;
const subject = `Welcome to Company: ${name}`;
await this.mailerService.sendMail({
to: email,
subject,
template: './welcome',
context: {
name,
},
});
}
}
Note: welcome
refers to the welcome.ejs
template.
You can stop here and send the email by directly calling the welcomeEmail
function on the service. However, if you're like me and you appreciate the beauty of a more robust and modular system, let's dive a little deeper and introduce the event emitter in our application.
6. Triggering the Email Using Event Emitters 🚀
Integrate an event-driven approach to make our application even more robust. Let’s say we want the welcome email to be sent out when a new user registers. We can achieve this by emitting an event post-registration and having our EmailService
listen to that event.
First install the required package:
npm i --save @nestjs/event-emitter
Don't forget to import the EventEmitterModule
in app.module.ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { EmailModule } from './modules/email/email.module';
import { UserModule } from './modules/user/user.module';
import { EventEmitterModule } from '@nestjs/event-emitter';
@Module({
imports: [
EmailModule,
UserModule,
EventEmitterModule.forRoot()
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
The create a module called user
nest g resource user
In user.controller.ts
:
import { Body, Controller, Post } from '@nestjs/common';
import { TypedEventEmitter } from '@/event-emitter/typed-event-emitter.class';
@Controller('user')
export class UserController {
constructor(private readonly eventEmitter: TypedEventEmitter) {}
@Post('sign-up')
async create(@Body() body) {
this.eventEmitter.emit('user.welcome', {
name: 'Bhagyajit Jagdev',
email: body.email,
});
this.eventEmitter.emit('user.verify-email', {
name: 'Bhagyajit Jagdev',
email: body.email,
otp: '****', // generate a random OTP
});
// Add your user to the database
}
}
Next, in your EmailService
, listen for those event:
import { EventPayloads } from '@/interface/event-types.interface';
import { MailerService } from '@nestjs-modules/mailer';
import { Injectable } from '@nestjs/common';
import { OnEvent } from '@nestjs/event-emitter';
@Injectable()
export class EmailService {
constructor(private readonly mailerService: MailerService) {}
@OnEvent('user.welcome')
async welcomeEmail(data: EventPayloads['user.welcome']) {
const { email, name } = data;
const subject = `Welcome to Company: ${name}`;
await this.mailerService.sendMail({
to: email,
subject,
template: './welcome',
context: {
name,
},
});
}
@OnEvent('user.reset-password')
async forgotPasswordEmail(data: EventPayloads['user.reset-password']) {
const { name, email, link } = data;
const subject = `Company: Reset Password`;
await this.mailerService.sendMail({
to: email,
subject,
template: './forgot-password',
context: {
link,
name,
},
});
}
@OnEvent('user.verify-email')
async verifyEmail(data: EventPayloads['user.verify-email']) {
const { name, email, otp } = data;
const subject = `Company: OTP To Verify Email`;
await this.mailerService.sendMail({
to: email,
subject,
template: './verify-email',
context: {
otp,
name,
},
});
}
}
This event-driven approach keeps our application modular, with each segment focused on its core function.
7. Type Checking Event Emitter Payload
To make the event payload type safe we can create a wrapper around the EventEmitter2
:
// src\event-emitter\typed-event-emitter.class.ts
import { Injectable } from '@nestjs/common';
import { EventPayloads } from '@/interface/event-types.interface';
import { EventEmitter2 } from '@nestjs/event-emitter';
@Injectable()
export class TypedEventEmitter {
constructor(private readonly eventEmitter: EventEmitter2) {}
emit<K extends keyof EventPayloads>(
event: K,
payload: EventPayloads[K],
): boolean {
return this.eventEmitter.emit(event, payload);
}
}
Then create a module and make it @Global
make sure to import TypedEventEmitterModule
in app.module.ts
imports
import { Global, Module } from '@nestjs/common';
import { TypedEventEmitter } from './typed-event-emitter.class';
@Global()
@Module({
providers: [TypedEventEmitter],
exports: [TypedEventEmitter],
})
export class TypedEventEmitterModule {}
Then import it in the user.controller.ts
:
export class UserController {
constructor(private readonly eventEmitter: TypedEventEmitter) {}
}
Now you can create an interface for all the payload types and type safe it:
// src\interface\event-types.interface.ts
export interface EventPayloads {
'user.welcome': { name: string; email: string };
'user.reset-password': { name: string; email: string; link: string };
'user.verify-email': { name: string; email: string; otp: string };
}
Conclusion 🌟
And there you have it! With these steps, you’re now equipped to send beautifully templated emails using NestJS and EJS. Plus, with the power of event emitters, you can ensure your application is reactive and modular. As you integrate this into your projects, I believe you’ll find it to be a game-changer. For more insights into web development and other intriguing topics, feel free to explore my previous works over at my Medium profile.
GitHub Code: Find the working code on my GitHub repository. Feel free to explore, fork, and adapt it to your needs.
Happy coding! 🚀