Back to Home

Add CCs to emails delivered through AWS Pinpoint

I recently started freelancing for an Amazon team that develops an internal Amazon app that helps various Amazon teams manage their product/feature launches. The app delivers email notifications through AWS Pinpoint, a popular cloud service for delivering emails. Many of the app’s email notifications were important (as they required timely user action) and Pinpoint was chosen as it offered email open and link click tracking, multi channel support, segmentation etc..

Earlier this month, while developing a major feature that’d help users meet Compliance requirements for launches, we were asked to add CCs to many email notifications. After an initial glance at Pinpoint’s API documentation, it seemed like there wasn’t a way to add CCs. While AWS SES or AWS Pinpoint Email (which are alternative AWS services for delivering emails with a different set of pros and cons) give users the ability to add CCs to emails, moving to those services wasn’t an option as we’d lose email open and link click tracking.

The Solution

Fortunately, the Pinpoint Support team were very helpful and they shared a handy way to circumvent this problem. Pinpoint internally uses AWS SES to deliver emails and so an easy way to add CCs is to just provide a raw email (that has the required CCs) to Pinpoint’s Send Messages API endpoint. Here’s how that can easily be done in Node JS.

First, add the the popular nodemailer mailer package as a dependency

npm install nodemailer

or if you use yarn as your package manager

yarn add nodemailer

Then, use nodemailer to construct a raw email for you and deliver that email through Pinpoint

AWS_REGION = 'us-east-1'
PINPOINT_APP_ID = 'yourPinpointAppId';
FROM = '[email protected]';

import * as nodemailer from 'nodemailer';
import { Pinpoint } from 'aws-sdk';

async function buildEmail(options = {}): Promise<String> {
  let transporter = nodemailer.createTransport({
    streamTransport: true,
    newline: 'unix',
    buffer: true,
  });
  return new Promise((resolve, reject) => {
    transporter.sendMail(options, (err, info) => {
      if (err) reject(err);
      resolve(info.message.toString());
    });
  });
}

async function run() {
  const to = '[email protected]';
  const cc = ['[email protected]'];
  const pinpointAppId = PINPOINT_APP_ID;

  const emailRaw = await buildEmail({
    from,
    to,
    cc,
    subject: 'Test email. Please ignore.',
    html: '<p>This is a test email. Please ignore it.</p>',
  });

  const pinpoint = new Pinpoint({ region: AWS_REGION });
  const sendMessageParams: Pinpoint.SendMessagesRequest = {
    ApplicationId: pinpointAppId,
    MessageRequest: {
      Addresses: {
        [to]: { ChannelType: 'EMAIL' },
        [cc[0]]: { ChannelType: 'EMAIL' },
      },
      MessageConfiguration: {
        EmailMessage: {
          RawEmail: {
            Data: emailRaw,
          },
        },
      },
    },
  };
  try {
    const result = await pinpoint.sendMessages(sendMessageParams).promise();
    console.log(result);
  } catch (e) {
    const exception = e;
    console.log(exception);
  }
}
run();

In case you’re wondering, every To and CC recipient receives a slightly different email with different tracking pixel and tracking links and Pinpoint’s will track email opens and link clicks the way you’d anticipate it to.

If you’re here, you likely hit upon this same problem and I hope this post saves you time.

Built with Hugo & Notion. Source code is available at GitHub.