Borislav Hadzhiev
Tue Apr 12 2022·4 min read
Photo by Daniel Mingook Kim
Updated - Tue Apr 12 2022
The default email AWS Cognito sends to our users looks like:
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.
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.userAttributes.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.userAttributes.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.userAttributes.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:
Custom Message
trigger in our Cognito User Pool.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.