Email Templating With AWS SDK and EJS in a Node backend

Templating SES emails in a more controlled and manageable manner

Amazon SES is a cloud email service provider that can integrate into any application for bulk email sending. Almost every application today needs to send emails, and AWS SES is often a good option especially when you are looking for a scalable solution. Unfortunately, creating personalized emails with AWS SES templates is often a daunting task because of the process involved in creating and managing them. EJS on the other hand is a simple templating language that offers the possibility of generating HTML markup with plain JavaScript. Since the SES SDK allows us to send an email as HTML, we can use EJS to generate the HTML and send it with SES SDK.

To demonstrate how to template SES emails with EJS, we will send a welcome message to a user with the user’s name. For the rest of this tutorial, we assume you already have a node project setup and you are familiar with sending emails with SES.

Installing the necessary packages

For this tutorial, we would need to install SES and EJS. We used pnpm here. Some other node package managers can be used.

pnpm add @aws-sdk/client-ses // to install the SES client
pnpm install ejs // to install ejs

Create a folder called email in a location where you would want your email module to live. This folder would contain both the templates and the service responsible for sending the emails. In the email folder, create a file templates/welcome.ejs with the following content. The templates could be placed anywhere, in this case, we want the templates to live in the templates folder.

templates/welcome.ejs

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width" />
  </head>
  <body>
    Welcome <%= name %>, it's nice to have you on board.
  </body>
</html>

Next, we will create a service class that uses AWS SES SDK v3 client to send the welcome message. Let’s start with the constructor:

import { join } from 'path';
import { SendEmailCommand, SESClient } from '@aws-sdk/client-ses';
import { renderFile } from 'ejs';

export class EmailService {
  private readonly templatePath = join(__dirname, 'templates');
  private readonly client: SESClient;

  constructor() {
    this.client = new SESClient({
      region: 'my-aws-region',
      credentials: {
        accessKeyId: 'my-access-key-id',
        secretAccessKey: 'my-access-key-id',
      },
    });
  }

  // more code to follow
}

The templatePath is the directory where the templates will be located. It is the folder we created earlier. Next, in the constructor we create an instance of the SES v3 client. Typically, you would store the secretAccessKey, accessKeyId, and region in a config file, and validate them when the app boots before accessing them here. For simplicity, we will just write them out here.

We will then define the send method, which wraps the SDK’s send method. Let’s start with the interface for the send method. For simplicity we have omitted several features from the interface, for instance, we are not supporting carbon or blind copies to name a few.

interface Template {
  name: string;
  data?: Record<string, any>;
}

interface EmailOption {
  to: string;
  subject: string;
  from: string;
  template?: Template;
}

Next, we will create a method that generates the HTML template:

async generateTemplate({ name, data }: Template) {
  return renderFile(
    join(this.templatePath, `${name}.ejs`),
    data,
  );
}

Finally, let’s define the send method itself.

async send({
    to
    from,
    subject,
    text,
    template,
  }: EmailOption) {
    const sendEmailCommand = new SendEmailCommand({
      Destination: {
        To: to,
      },
      Message: {
        Body: {
          ...(template && {
            Html: {
              Charset: 'UTF-8',
              Data: this.generateTemplate(template),
            },
          }),
        },
        Subject: {
          Charset: 'UTF-8',
          Data: subject,
        },
      },
      Source: from,
      ReplyToAddresses: [],
    });

    try {
      await this.client.send(sendEmailCommand);
    } catch (error) {
      throw new Error(error);
    }
  }
Using the email service to send a customized email

At this point, everything is set up and we can start sending customized emails. The part <%= name %> in welcome.ejs will get substituted by John Doe.

const emailService = new EmailService();

await emailService.send({
  destination: {
    To: 'john_doe@example.com',
  },
  from: 'noreply@dm.com',
  subject: 'Welcome to Email templating with EJS',
  template: {
    name: 'welcome',
    data: {
      name: 'John Doe',
    },
  },
});

In this tutorial, we used SES v3 and EJS, we could have used v3 and any other templating engine like handlebars and the process would almost be identical to the one described here. As an improvement to this setup, one could create a layout for the emails, this way all your emails have a similar look and feel.

The complete code for this tutorial can be found here.

We have scratched the surface of EJS and SES, you can check out their documentation to find out what is possible.

If you need any help with this configuration or if you would like to request a development consultancy you can Contact us or you can check our Github Sponsorship page directly.

References: