Sending Emails in NestJS with Sendgrid

Sending Emails in NestJS with Sendgrid

This guide provides a comprehensive walkthrough on seamlessly integrating the SendGrid mail service into your NestJS application.

Step 1: NestJS CLI Installation
If you don't have an existing NestJS application, proceed to generate a new NestJS app. Install the NestJS CLI globally by utilizing npm:

npm install -g @nestjs/cli

Step 2: Create a New NestJS Project
Use the NestJS CLI to create a new project. Name it whatever you like; for this article, we’ll use “sendgrid-email-nestjs.”

nest new sendgrid-email-nestjs
cd sendgrid-email-nestjs

Step 3: Install SendGrid Package
Install the @sendgrid/mail package:

npm install @sendgrid/mail

Step 4: Set up SendGrid API Key
Retrieve your SendGrid API key from the SendGrid dashboard, and securely store it either in your environment variables or utilize a configuration service for enhanced security and flexibility.

SENDGRID_API_KEY=your-sendgrid-api-key

Step 5: Create Email Module
Create an email module that will contain our implementations.

nest generate module email

Step 6: Create SendGrid Client class
Establish the SendGridClient class within the sendgrid-client.ts file located in the email folder. This class is designed to utilize the API key retrieved from the configuration, enabling seamless interaction with the SendGrid API through the @sendgrid/mail package.

nest generate class email/sendgrid-client --flat --no-spec

Step 7: Update SendGrid Client
Update sendgrid-client.ts to use the API key from the configuration:

// src/email/sendgrid-client.ts

import { Injectable } from '@nestjs/common';
import { MailDataRequired, setApiKey, send } from '@sendgrid/mail';

@Injectable()
export class SendGridClient {
  constructor() {
    // Set the API key from the config service or environment variable
    setApiKey(process.env.SENDGRID_API_KEY);
  }

  /**
   * Send an email using SendGrid.
   * @param mail - The email data, including recipient, sender, subject, and content.
   * @returns Promise<void>
   */
  async send(mail: MailDataRequired): Promise<void> {
    try {
      // Send the email using the SendGrid API
      await send(mail);
      console.log(`Email successfully sent to ${mail.to as string}`);
    } catch (error) {
      // Handle errors during the email sending process
      console.error('Error while sending email', error);
      throw error;
    }
  }
}

Note:
Ensure "esModuleInterop" is set to true in your tsconfig.json file to enable the use of default imports.

// Example tsconfig.json snippet:
/*
{
  "compilerOptions": {
    "esModuleInterop": true,
    // ... other options
  },
  // ... other configurations
}
*/

Step 8: Create Email Service
Generate the email.service.ts file to serve as an abstraction layer for the SendGridClient. This facilitates the implementation of distinct methods tailored for specific email use cases, promoting a modular and organized approach to handling email functionality within your NestJS application.

nest generate service email/email --flat --no-spec

Step 9: Update Email Service
Our email service class will consist of 2 methods: “sendTestEmail” and “sendTestEmailWithTemplate”. The former will be a method to send a test email and the latter will be a method to send an email with a preconfigured sendgrid email template. The template ID will be retrieved from the Sendgrid dashboard: dynamic template.

// src/email/email.service.ts

import { Injectable } from '@nestjs/common';
import { MailDataRequired } from '@sendgrid/mail';
import { SendGridClient } from './sendgrid-client';

@Injectable()
export class EmailService {
  constructor(private readonly sendGridClient: SendGridClient) {}

  /**
   * Sends a test email with a simple body content.
   * @param recipient - Email address of the recipient.
   * @param body - Body content of the email (default is 'This is a test mail').
   */
  async sendTestEmail(recipient: string, body = 'This is a test mail'): Promise<void> {
    const mail: MailDataRequired = {
      to: recipient,
      from: 'noreply@domain.com', // Approved sender ID in Sendgrid
      subject: 'Test email',
      content: [{ type: 'text/plain', value: body }],
    };
    await this.sendGridClient.send(mail);
  }

  /**
   * Sends a test email using a SendGrid template.
   * @param recipient - Email address of the recipient.
   * @param body - Dynamic content for the template.
   */
  async sendTestEmailWithTemplate(recipient: string, body: string): Promise<void> {
    const mail: MailDataRequired = {
      to: recipient,
      cc: 'example@mail.com', // Assuming you want to send a copy to this email
      from: 'noreply@domain.com', // Approved sender ID in Sendgrid
      templateId: 'Sendgrid_template_ID', // Retrieve from config service or environment variable
      dynamicTemplateData: { body, subject: 'Send Email with template' }, // Data to be used in the template
    };
    await this.sendGridClient.send(mail);
  }
}

Step 10: Update Email Module
Update the email module by adding the SendGridClient class as a provider and exporting the email service so that it is accessible to any module that imports the Email module.

// src/email/email.module.ts

import { Module } from '@nestjs/common';
import { EmailService } from './email.service';
import { SendGridClient } from './sendgrid-client';

@Module({
  providers: [EmailService, SendGridClient],
  exports: [EmailService],
})
export class EmailModule {}

Step 11: Use Email Service in Controller
Import the email service and make it available in the appropriate controller. We will be using the app controller. Update the app.controller.ts file to provide an endpoint to test the email service.

// src/app.controller.ts

import { Body, Controller, Get, Post } from '@nestjs/common';
import { AppService } from './app.service';
import { EmailService } from './email/email.service';

@Controller()
export class AppController {
  constructor(
    private readonly appService: AppService,
    private readonly emailService: EmailService,
  ) {}

  /**
   * Handles GET requests to the root endpoint.
   * @returns Hello message from AppService.
   */
  @Get()
  getHello(): string {
    return this.appService.getHello();
  }

  /**
   * Handles POST requests to send a test email.
   * @param sendEmailDTO - Data for sending a test email.
   */
  @Post('send-test-email')
  async sendEmail(
    @Body() sendEmailDTO: { recipient: string; body: string },
  ): Promise<void> {
    try {
      // Delegate the task of sending the test email to the EmailService.
      await this.emailService.sendTestEmail(
        sendEmailDTO.recipient,
        sendEmailDTO.body,
      );
    } catch (error) {
      // Handle and log any errors that may occur during email sending.
      console.error('Error sending test email:', error);
      throw error;
    }
  }
}

Step 12: Run Your NestJS Application
Run your NestJS application:

npm run start:dev

To test the email functionality, initiate a POST request to http://localhost:3000/send-test-email using an API testing tool like Postman. Provide the recipient email and, if desired, the optional body parameters in the request body as a JSON payload.

Ensure to replace placeholders such as 'Sendgrid_template_ID', 'SENDGRID_API_KEY', etc., with your specific and valid values before making the request.

Conclusion
In conclusion, this guide has walked you through the seamless integration of the SendGrid mail service into your NestJS application. By following the outlined steps, you've successfully established the project, configured the SendGrid API key, and crafted essential modules and classes. With a well-organized modular structure and detailed instructions, you now possess a robust framework for seamlessly incorporating efficient email functionality into your NestJS projects. Should you have any questions or encounter issues, feel free to seek assistance. Happy coding!