Skip to content

How we use Customer.io to send custom welcome emails through Cognito

We recently decided to standardize on Customer.io as our communication platform for our users (both users of our app  -  our core users  -  and of our web portal -  administrators from employers). Here we will talk through how we set up Amazon Cognito to work with Customer.io to send custom welcome emails.

Customer.io is a great consolidated communication platform for us:

  1. It integrates natively with Segment (which we use as our CDP)
  2. It allows us to send email, SMS, and push notifications easily
  3. It allows us to create customer communication journeys and easily send out drip campaigns for new enrollees

One thing we also wanted to do was use Customer.io to send new user registration emails when a new employer administrator was added to an employer account in our web portal. This is basically just an "an account has been created for you, here's your temporary password, click here to sign in" email. Since user accounts are managed by Cognito, we had to hook up Cognito to Customer.io. Here's how we did it!

Overall flow using Cognito, Lambda, and Customer.io

Cognito flow

When a new user is created in our Employer Portal, a Cognito User is created. Cognito then uses a custom email sender to send the email address and temporary password to Customer.io. Customer.io then sends the welcome email to the user. This way, we have visibility into the email delivery and we can easily customize the email to match all of our other emails.

The overall process to do this is quite simple:

  1. Configure an encryption key for Cognito to use when passing the temporary password to the Lambda function.
  2. Create a Custom Email Sender Lambda function.
  3. Configure Cognito to use your new Custom Email Sender Lambda function.

At the end of the project, we wind up with an email in Customer.io that looks something like this:

email sample

Step 1: Configure your encryption key

Amazon Cognito uses the AWS Encryption SDK to encrypt any secrets it might send to other functions (ie, temporary passwords). In order for our function to be able to decrypt this temporary password, we need to make sure our code and Cognito are using the same key. We just need to head into the AWS KMS dashboard to create a key to use.

In the search for services box, search for "KMS" and select "Key Management Service".

AWS Search Box

Select "Customer managed keys" from the menu on the left.

AWS KMS Homepage

Then click the "Create key" button.

KMS - Create a key

You'll want to create a Symmetric key. Click "Next".

KMS - Configure Key

Give the key an alias, description, and tags. Click "Next".

KMS - Add labels

Make sure one of your users or roles has administrative permissions on the key. Click "Next".

KMS - administrative permissions

You will want to make sure the role that will be executing your Lambda function has usage permissions on the key. If you don't have that role created yet, that's ok - you can add it later. Click "Next".

KMS - usage permissions

Review that everything is as expected and click "Finish" to create your key.

KMS - Review

Now that your key is created, you'll want to make note of the Key ID - we will need it in our configuration later.

KMS - key created

Step 2: Create a custom email sender

At a high level, this is relatively straightforward.

  1. We need to decrypt the passed code using the configured KMS key.
  2. Once the code is decrypted, we need to build our payload for Customer.io.
  3. Once our customer.io payload is built, we just need to use the Customer.io SDK to trigger the message.

NOTE: There are multiple types of emails that Cognito can send (a trigger source). Our code handles all of them. For purposes of this demo, we are just focusing on the CustomEmailSender_AdminCreateUser trigger source.

1. Decrypt the passed code

Outside of the handler of your function, initialize the AWS Encryption SDK. This allows the SDK to be reused between Lambda invocations.

// Configure the encryption SDK client with the KMS key from the environment variables.
const { decrypt } = encryptionSdk.buildClient(encryptionSdk.CommitmentPolicy.REQUIRE_ENCRYPT_ALLOW_DECRYPT);
const generatorKeyId = process.env.KEY_ALIAS;
const keyIds = [ process.env.KEY_ID ];
const keyring = new encryptionSdk.KmsKeyringNode({ generatorKeyId, keyIds });

IMPORTANT: The KEY_ALIAS and KEY_ID environment variables were provided when your KMS key was created.

Then, within your handler, use the decrypt method to decrypt the passed code:

// Decrypt the secret code using encryption SDK.
let plainTextCode;
if(event.request.code){
const { plaintext } = await decrypt(keyring, b64.toByteArray(event.request.code));
plainTextCode = plaintext
}

2. Build the Customer.io payload

Create the base of the request, then add the trigger-specific values. We can pull the user information from the userAttributes object of the request.

const request = {
to: event.request.userAttributes.email,
identifiers: {
id: event.request.userAttributes.sub,
},
message_data: {
username: event.request.userAttributes.email,
companyName: event.request.userAttributes['custom:company_name'],
firstName: event.request.userAttributes.given_name,
},
}
switch (event.triggerSource) {
case 'CustomEmailSender_AdminCreateUser': {
request.transactional_message_id = process.env.ADMINCREATEUSER_MESSAGE_ID;
request.message_data.tmpPass = plainTextCode.toString();
break;
}
default: {
throw new Error('Unknown triggerSource passed'));
}
}

3. Send the request with the Customer.io SDK

const cio = new APIClient(process.env.CUSTOMER_IO_KEY, { region: RegionUS });
await cio.sendEmail(new SendEmailRequest(request));

Putting it all together

The complete source code is below:

Step 3: Configure Cognito to use your custom email sender

We've mentioned in our other blogs that we are a 100% serverless shop and use the serverless framework to deploy our code. This new function is no different. All we need to do is configure our User Pool to use the new function we just created and tell it to use the KMS key we configured.

LambdaConfig:
CustomEmailSender:
LambdaArn:
Fn::Join:
- ':'
-
- 'arn:aws:lambda'
- Ref: 'AWS::Region'
- Ref: 'AWS::AccountId'
- 'function:${self:service}-${self:provider.stage}-customEmailSender'
LambdaVersion: "V1_0"
KMSKeyID: ${env:COGNITO_KEY_ID}

The complete snippet from our serverless.yml file is below

It was really that simple!

Once configured and deployed, any time a new user is created in this user pool, a welcome email with an initial password will be sent via Customer.io. We hope this little project of ours gives you some inspiration in your own work!

If you're a small business, or a startup, and are looking to offer your employees an affordable retirement option (especially if you have to deal with CalSavers compliance!), give Xiggit a look! We'd love to partner with you on your employees' journey to financial wellness!

Leave a Comment