Amazon SES template example + tutorial

Amazon SES template example + tutorial

In this tutorial, we'll take a static HTML email template, convert it to an Amazon SES template, and then send it using the Amazon SES API.

HTML email template example

For this example, let's use an email template that tends to be one of the first emails setup while creating a new application – the reset/forgot password email.

To get started, we'll need the HTML for the static email template. Luckily, Waypoint provides a template gallery with free responsive HTML email templates.

Waypoint's template gallery with free HTML email templates.


In this case, we'll use the 'Reset password email notification' email template and grab the HTML.

The free HTML email template we'll use.

Setting up an Amazon SES template

Now that we have the static template HTML, let's turn it into a dynamic email template on Amazon SES. To do this, we'll need to write a bit of code.

Start by installing the npm library for Amazon SES.

npm i @aws-sdk/client-sesv2


Now let's write the code and command to create the HTML template on Amazon SES. Notes:

  • Update the code with your own AWS credentials and configurations.

  • Copy and paste the static template HTML into the htmlBody variable.

  • Set a dynamic variable for a secret token on the "Change my password" link that will be passed into the template while sending. Eg. http://example.com/change_password/{{token}}. Amazon SES uses Handlebars templating for these variables.

const { CreateEmailTemplateCommand, SendEmailCommand, SESv2Client } = require('@aws-sdk/client-sesv2');

// Replace these variables with your AWS credentials and SES configuration
const awsAccessKey = AWS_ACCESS_KEY;
const awsSecretKey = AWS_SECRET_KEY;
const region = AWS_REGION;

// Content used to create the HTML Template
const templateName = 'ResetPassword';
const subject = 'Reset your password';
const htmlBody = `
  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
  <html lang="en" dir="ltr" style="-webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; box-sizing: border-box; -webkit-text-size-adjust: 100%;">
  
    <head>
      <meta http-equiv="Content-Type" content="text/html charset=UTF-8">
      <style data-emotion="css-global o6gwfi">
  @media print {
    body {
      background-color: #fff;
    }
  }
  </style>
      <style>
  @media screen and (max-width: 599.95px) {
    .block-mobile {
      display: block !important;
    }
  
    .block-desktop {
      display: none !important;
    }
  }
  </style>
    </head>
  
    <body style="box-sizing: inherit; margin: 0; color: rgba(0, 0, 0, 0.87); font-family: 'Roboto', 'Helvetica', 'Arial', sans-serif; font-weight: 400; font-size: 1rem; line-height: 1.5; letter-spacing: 0.00938em; background-color: #f2f5f7;">
      
      <div class="MuiBox-root css-29x6sm" style="box-sizing: inherit; font-weight: 400; font-size: 16px; padding: 32px 0; margin: 0; letter-spacing: 0.15008px; line-height: 1.5; background-color: #f2f5f7; font-family: 'Helvetica Neue', 'Arial Nova', 'Nimbus Sans', Arial, sans-serif; color: #242424;">
        <div style="box-sizing: inherit; display: none; overflow: hidden; line-height: 1px; opacity: 0; max-height: 0; max-width: 0;">If you didn&#x27;t request a reset, don&#x27;t worry. You can safely ignore this email.<div style="box-sizing: inherit;"> </div>
        </div>
        <table align="center" width="100%" style="box-sizing: inherit; background-color: #FFFFFF; max-width: 600px; min-height: 48px;" role="presentation" cellspacing="0" cellpadding="0" border="0" bgcolor="#FFFFFF">
          <tbody style="box-sizing: inherit;">
            <tr style="box-sizing: inherit; width: 100%;">
              <td style="box-sizing: inherit;">
                <div style="padding: 24px 24px 8px 24px; text-align: left; max-width: 100%; box-sizing: border-box;"><img src="https://d1iiu589g39o6c.cloudfront.net/live/platforms/platform_A9wwKSL6EV6orh6f/images/wptemplateimage_w9brVHZobEBhsJSK/marketbase.png" style="box-sizing: inherit; display: inline-block; outline: none; border: none; text-decoration: none; vertical-align: middle; max-width: 100%; height: 40px;" height="40"></div>
                <div style="font-family: inherit; font-weight: bold; padding: 32px 24px 0px 24px; text-align: left; max-width: 100%; box-sizing: border-box;">
                  <h3 style="box-sizing: inherit; margin-top: 40px; margin-bottom: 16px; font-weight: inherit; margin: 0; font-size: 20px;">Reset your password?</h3>
                </div>
                <div style="color: #474849; font-family: inherit; font-size: 14px; font-weight: normal; padding: 8px 24px 16px 24px; text-align: left; max-width: 100%; box-sizing: border-box;">
                  
                  <div class="MuiBox-root css-vii0ua" style="box-sizing: inherit;">
                    <div style="box-sizing: inherit;">
                      <p style="box-sizing: inherit; margin-top: 0px; margin-bottom: 0px;">If you didn't request a reset, don't worry. You can safely ignore this email.</p>
                    </div>
                  </div>
                </div>
                <div style="font-family: inherit; font-size: 14px; font-weight: bold; padding: 12px 24px 32px 24px; text-align: left; max-width: 100%; box-sizing: border-box;"><a href="https://www.example.com/reset_password?token={{token}}" target="_blank" style="box-sizing: inherit; background-color: #2458AF; color: #FFFFFF; padding: 0px 0px; border-radius: 64px; width: auto; display: inline-block; line-height: 100%; text-decoration: none; max-width: 100%;"><span style="box-sizing: inherit;"><!--[if mso]><i style="letter-spacing: undefinedpx;mso-font-width:-100%;mso-text-raise:0" hidden>&nbsp;</i><![endif]--></span><span style="box-sizing: inherit; background-color: #2458AF; color: #FFFFFF; padding: 12px 20px; border-radius: 64px; width: auto; display: inline-block; max-width: 100%; line-height: 120%; text-decoration: none; text-transform: none; mso-padding-alt: 0px; mso-text-raise: 0;">Change my password</span><span style="box-sizing: inherit;"><!--[if mso]><i style="letter-spacing: undefinedpx;mso-font-width:-100%" hidden>&nbsp;</i><![endif]--></span></a></div>
                <div style="padding: 16px 24px 16px 24px; max-width: 100%; box-sizing: border-box;">
                  <hr style="box-sizing: inherit; margin: 0px; width: 100%; border: none; border-top: 1px solid #eaeaea; border-color: rgb(233, 233, 233); border-top-width: 1px;">
                </div>
                <div style="color: #474849; font-family: inherit; font-size: 12px; font-weight: normal; padding: 4px 24px 24px 24px; text-align: left; max-width: 100%; box-sizing: border-box;">
                  <div class="MuiBox-root css-vii0ua" style="box-sizing: inherit;">
                    <div style="box-sizing: inherit;">
                      <p style="box-sizing: inherit; margin-top: 0px; margin-bottom: 0px;">Need help? Just reply to this email to contact support.</p>
                    </div>
                  </div>
                </div>
              </td>
            </tr>
          </tbody>
        </table>
      </div>
    </body>
  
  </html>
`;

const fromEmail = FROM_EMAIL;
const toEmail = TO_EMAIL;

const client = new SESv2Client({
  region: region,
  credentials: {
    accessKeyId: awsAccessKey,
    secretAccessKey: awsSecretKey,
  },
});

const createTemplateCommand = new CreateEmailTemplateCommand({
  TemplateName: templateName,
  TemplateContent: {
    Subject: subject,
    Html: htmlBody,
  },
});

await client.send(createTemplateCommand);

Sending the email

When we're ready to send an email, we'll use SendEmailCommand to pass data to our template. The data is just the variables that our template is expecting in order to populate the template. In this simple example, it's just the token for the reset password link.

const sendCommand = new SendEmailCommand({
  FromEmailAddress: fromEmail,
  Destination: {
    ToAddresses: [toEmail],
  },
  Content: {
    Template: {
      TemplateName: "ResetPassword",
      TemplateData: '{"token": "1091948120948"}', // Replace with your dynamic content
    },
  },
});

await client.send(sendCommand);


Assuming everything was configured properly, after running the code, our email should be sent via Amazon SES with the template data populated. This should look exactly as the static template above but the 'Change my password' link should include the passed in token.

Looking for an easier way?

While Amazon SES is a very cost effective approach to sending emails, the real cost is associated with the time spend to configure AWS for templates and logging. This is why some collaborative teams opt for a new approach – using an email API with a built-in template builder like Waypoint.

Waypoint provides a powerful template builder:

Screenshot of Waypoint’s powerful transactional email template builder


As well as great observability:

Screenshot of Waypoint’s logs on a single email.

Wrap up

As mentioned in the previous post, Amazon SES is hard to beat on price and is great for teams sending high volumes of email. However, for teams that want to save on the endless back-and-forths when building and maintaining product emails, it's worth considering alternative approach.