How to Customize Emails in AWS Cognito


Borislav Hadzhiev

Last updated: Apr 12, 2022


Photo from Unsplash

Customizing Emails in AWS Cognito #

The default email AWS Cognito sends to our users looks like:

default cognito email

When a user signs up, requests an email change or a password reset, we have to send them an email. Since the default email is very plain, is quite common to have to provide a custom implementation.

In order to customize the default email messages provided by cognito is by creating a Custom message lambda trigger.

custom message trigger

Implementing the Custom Message Trigger #

Let's implement the entry point of our Custom Message trigger function:

import {Callback, Context} from 'aws-lambda'; import CustomMessage from './custom-message'; interface Event { triggerSource: string; request: { codeParameter: string; userAttributes: { 'cognito:user_status': string; given_name: string; family_name: string; email: string; }; usernameParameter: string; }; response: { emailSubject: string; emailMessage: string; }; } export function main( event: Event, _context: Context, callback: Callback, ): void { // 👇 in this case DOMAIN_NAME is provided via ENV var const {DOMAIN_NAME} = process.env; const { triggerSource, request: {codeParameter, userAttributes, usernameParameter}, } = event; const customMessage = new CustomMessage({ domainName: DOMAIN_NAME, userAttributes, codeParameter, usernameParameter, }); /* eslint-disable no-param-reassign */ if ( triggerSource === 'CustomMessage_SignUp' && userAttributes['cognito:user_status'] === 'UNCONFIRMED' ) { event.response = customMessage.sendCodePostSignUp(); } else if (triggerSource === 'CustomMessage_ForgotPassword') { event.response = customMessage.sendCodeForgotPassword(); } else if (triggerSource === 'CustomMessage_UpdateUserAttribute') { event.response = customMessage.sendCodeVerifyNewEmail(); } else if (triggerSource === 'CustomMessage_AdminCreateUser') { event.response = customMessage.sendTemporaryPassword(); } else if (triggerSource === 'CustomMessage_ResendCode') { event.response = customMessage.resendConfirmationCode(); } // Return to Amazon Cognito callback(null, event); }

Note that the given_name and family_name cognito attributes might not be present in your user pool. If you haven't set them as required for when users register, you will have to provide a more generic email message.

This entry function just calls a method of the CustomMessage class depending on the event trigger source. Let's now look at the CustomMessage class:

interface CustomMessageProps { domainName: string; userAttributes: { given_name: string; family_name: string; email: string; }; codeParameter: string; usernameParameter: string; } interface CustomMessage extends CustomMessageProps {} interface CustomMessageMethod { emailSubject: string; emailMessage: string; } class CustomMessage { constructor(kwargs: CustomMessageProps) { Object.assign(this, kwargs); } sendCodePostSignUp(): CustomMessageMethod { return { emailSubject: `Validate your account for ${ this.domainName } | ${new Date().toLocaleString()}`, emailMessage: `Hi <b>${this.userAttributes.given_name} ${this.userAttributes.family_name}</b>! <br>Thank you for signing up. <br /> Please click on the link to activate your account: <a href="${this.domainName}/complete-registration?code=${this.codeParameter}&email=${}">${this.domainName}</a>. `, }; } sendCodeForgotPassword(): CustomMessageMethod { return { emailSubject: `Reset your password for ${ this.domainName } | ${new Date().toLocaleString()}`, emailMessage: `Hi <b>${this.userAttributes.given_name} ${this.userAttributes.family_name}</b>! <br /> Please click on the link to update your password: <a href="${this.domainName}/complete-password-reset?code=${this.codeParameter}&email=${}">${this.domainName}</a>. `, }; } sendCodeVerifyNewEmail(): CustomMessageMethod { return { emailSubject: `Validate your new email for ${ this.domainName } | ${new Date().toLocaleString()}`, emailMessage: `Hi <b>${this.userAttributes.given_name} ${this.userAttributes.family_name}</b>! <br /> Please click on the link to update your email address: <a href="${this.domainName}/complete-email-change?code=${this.codeParameter}">${this.domainName}</a>. `, }; } sendTemporaryPassword(): CustomMessageMethod { return { emailSubject: `Your account for ${ this.domainName } | ${new Date().toLocaleString()}`, emailMessage: `Hi User!<br>An administrator has created your credentials for ${this.domainName}. <br>Your username is <b>${this.usernameParameter}</b> and your temporary password is <b>${this.codeParameter}</b> <br>You can paste them in the form at <a href=${this.domainName}/login>${this.domainName}</a> in order to log in.`, }; } resendConfirmationCode(): CustomMessageMethod { return { emailSubject: `Your sign-up confirmation link for ${ this.domainName } | ${new Date().toLocaleString()}`, emailMessage: `Hi <b>${this.userAttributes.given_name} ${this.userAttributes.family_name}</b>!<br>Thank you for signing up. <br /> Please click on the link to activate your account: <a href="${this.domainName}/complete-registration?code=${this.codeParameter}&email=${}">${this.domainName}</a>.`, }; } } export default CustomMessage;

The function has to have access to our domain name in order to compose a link for the user to click on.

In the methods we handle the scenarios where the user:

  1. Has signed up and we have to send them an account activation link
  2. Has forgotten their password and we have to send them a link where they can reset their password
  3. Has requested an email change and we have to send them a link to confirm their new email
  4. Has been created an account from an administrator and we have to send them their credentials
  5. Hasn't received an email with the account activation link upon registration and we have to resend the link
After we've defined the lambda function, we have to attach it as a Custom Message trigger in our Cognito User Pool.

Custom Emails in AWS Cognito - Discussion #

An important caveat is that the maximum length of the email messages is 20,000 UTF-8 characters - docs

Should you exceed this limit of 20,000 UTF-8 characters, the default behavior is to send the one liner plain email of type "Your code is 123", which can be very confusing.

In the above implementation of the lambda function, we build links, which we send to the user. These links include the code and sometimes the email of the user in the query parameters, so we can invoke the necessary APIs on the frontend.

An alternative would be to just send the code to the user, like in the default email, but then the user would have to copy and paste the code into a form, which is less intuitive than clicking on a link.

Further Reading #

I wrote a book in which I share everything I know about how to become a better, more efficient programmer.
book cover
You can use the search field on my Home Page to filter through all of my articles.