How to use AWS SES with Nodemailer

Bjorn Krols

Bjorn Krols

Published on
21 June 2021

In this article you will learn how to use Nodemailer to send your AWS SES emails.

I recommend this approach because the Nodemailer API

  • gives you a platform-agnostic interface to build on top of, reducing vendor lock-in
  • is less verbose than the AWS SDK API
  • supports more advanced features such as attachments, out of the box

Compare this AWS SDK snippet:

import AWS from "aws-sdk";

async function sendSignInLink({
  to,
  signInLink,
  expiresInDays,
}: {
  to: string;
  signInLink: string;
  expiresInDays: number;
}) {
  const params = {
    Source: `Authentication <noreply@example.com>`,
    Destination: {
      ToAddresses: [to],
    },
    Message: {
      Subject: {
        Charset: "UTF-8",
        Data: "Your Sign-In Link",
      },
      Body: {
        Text: {
          Charset: "UTF-8",
          Data: `Hi there,

This is your sign-in link: ${signInLink}

This link will expire in ${expiresInDays} days.

For security reasons, you shouldn't reply to this email.`,
        },
      },
    },
  };
  await new AWS.SES().sendEmail(params).promise();
}

With its Nodemailer equivalent:

import AWS from "aws-sdk";
import nodemailer from "nodemailer";

async function sendSignInLink({
  to,
  signInLink,
  expiresInDays,
}: {
  to: string;
  signInLink: string;
  expiresInDays: number;
}) {
  const mailer = nodemailer.createTransport({
    SES: new AWS.SES(),
  });
  await mailer.sendMail({
    from: `Authentication <noreply@example.com>`,
    to: to,
    subject: "Your Sign-In Link",
    text: `Hi there,

This is your sign-in link: ${signInLink}

This link will expire in ${expiresInDays} days.

For security reasons, you shouldn't reply to this email.`,
  });
}

Installation

npm install aws-sdk nodemailer

If you're using TypeScript:

npm install --save-dev @types/nodemailer

At some point you may need to specify your AWS Region:

AWS.config.update({ region: process.env.AWS_REGION_ID });

Creating the Nodemailer instance

const mailer = nodemailer.createTransport({
  SES: new AWS.SES(),
});

Attachments example

This is one of the main reasons I like using Nodemailer, it provides a clean facade for trickier use cases such as attachments.

const args = {
  // ...
};

const mailer = nodemailer.createTransport({
  SES: new AWS.SES(),
});

await mailer.sendMail({
  from: `${args.fromName} <${args.fromEmail}>`,
  to: args.toEmail,
  cc: args.ccEmails,
  subject: args.subject,
  text: args.text,
  html: `<div>${args.text}</div>`,
  attachments: [
    {
      filename: args.fileName,
      content: fs.createReadStream(args.filePath),
    },
  ],
});

Switching transport based on environment

let mailer;

if (process.env.NODE_ENV === "production") {
  mailer = nodemailer.createTransport({
    SES: new AWS.SES(),
  });
} else {
  mailer = nodemailer.createTransport({
    port: 1025,
  });
}

Subscribe to our newsletter

The latest news, articles, and resources, sent to your inbox weekly.

More like this