How to handle bounces and complaints in AWS SES using AWS SNS with NestJS

Mithelan Devanandan
5 min readMar 1, 2024

We need to provide information when we change from sandbox env to production env. This article may help you to setup how to handle bounces and complaints in SES.

To begin, we’ll utilize NestJS as the backend to manage API requests. First, navigate to Amazon Simple Notification Service (SNS) and then proceed to Topics. Within this section, you’ll be able to create a new topic.

Select Standard,

name as : sns-bounces , display as : Bounces

(similarly you can create for compliants as well)

After successfully creating the topic, the next step involves setting up subscriptions for each created topic(bounces & complaints). To accomplish this, we’ll require backend API support, and we’ll leverage NestJS for this purpose.

Select HTTPS , or there are other type of options as well Email,SMS.. For this we are going to use HTTPS because we are manually going to store the bounced emails/complaint emails in our database.

Before creating the subscription we needed to create the backend API for this. If you are using local development environment to test this. You can get a help from ngrok which will create a server for you. For example below mentioned is one of the backend API I created for handling bounces

@Post('/aws/sns/handle-bounce')
bouncedEmail(@Body() body: any) {
console.log(body);
return this.emailService.handleBouncesEmail(body);
}

This is available locally. How can I test this with AWS SNS ?

https://ngrok.com download the ngrok.

after downloading the ngrok open the terminal and paste the following URL

 ngrok http http://localhost:portNo

This will create server for you.

https://de99-2402-e-e--d911-trrrre3-7874-adad.ngrok-free.app this is the server ngrok has created for us. Now with this lets move to the creation of the subscription.

NOTE: Don’t forget to tick ‘Enable raw message delivery’

In the above mentioned endpoint we have to give the ngrok server+endpoint

https://de99-2402-e-e--d911-trrrre3-7874-adad.ngrok-free.app/aws/sns/handle-bounce

Once you have successfully created the subscription. This will make an API call to the endpoint, where in your terminal you can now able to see the list of body from this API.

In the console.log you will be able to receive subscriptionURL or token. Copy that token and paste it on the browser and enter. You will get an unauthorised error, but thats okay. That will make the subscription confirmed. Or copy that URL and

NOTE : Sometimes you might not get any logs in the console.log

It because you might have missed adding

app.use(express.text());

in your main.js. If so then

import * as express from ‘express’;

app.use(express.text());

Once you successfully confirmed the subscription.

Status will show as confirmed.

Now the time is to connect it with our AWS SES. Navigate AWS SES identities. I hope you already have verified emails . Using any of the emails lets try to test email with bounces and com.

Now we need to configure the notifications for our identies.For that click on the identities. Go into the identities

You can able to see Notifications tab inside the identies. Click on the notifications

For each Bounces and Compliant we need to configure the created SNS topics.

From the dropdown select the SNS topic. This will configure our SES with SNS.

All fine. Its time to test whether it is working.

Click on the send message. Select the following as mentioned in the below image.

Once you successfully send a message. This particular bounce will trigger our AWS SNS.
Let’s see how we are going to handle.

 
async handleBouncesEmail(body: any) {
try {
const parsedBody = JSON.parse(body);
//we do get the bounceType
if (parsedBody.bounce.bounceType === 'Permanent') {
const email = parsedBody?.mail?.destination;
if (email.length > 0) {
const blockEmailDto: BlockEmailDto = {
email: email[0],
type: EmailBlockStatusEnum.BLOCKED,
};
//adding the email and type of bounce to the db
const bounce = await this.createBlockEmailRecord(blockEmailDto);
return bounce;
}
}
} catch (err) {
this.logger.error('Error in handling bounces email,', err);
throw err;
}
}

In the body we do get the results like this

"notificationType":"Bounce",
"bounce":{"feedbackId":"0","bounceType":"Permanent",
"bounceSubType":"General",
"bouncedRecipients":[{"emailAddress":"bounce@simulator.amazonses.com",
"action":"failed","status":"","diagnosticCode":""}],"timestamp":"",
"remoteMtaIp":"",
"reportingMTA":""},
"mail":{"timestamp":"",
"source":"test@test.io",
"sourceArn":"",
"sourceIp":"",
"callerIdentity":"",
"sendingAccountId":"",
"messageId":"------",
"destination":["bounce@simulator.amazonses.com"],
"headersTruncated":false,
"headers":[{"name":"From","value":"test@test.io"},
{"name":"To","value":"bounce@simulator.amazonses.com"},
{"name":"Subject","value":"test"},
{"name":"MIME-Version","value":"1.0"},
{"name":"Content-Type",
"value":"multipart/alternative;
boundary=\"----=_Part_831749_529395404.1709107173515\""}]
,"commonHeaders":{"from":["test@test.io"],
"to":["bounce@simulator.amazonses.com"],"subject":"test"}}}

We do have 3 types of bounceType namelyUndetermined, Permanent, or Transient. The Permanent and Transient bounce types can also contain one of several bounce subtypes.

In this particular scenario, I’m going to only check the Permanent bounce which is hard bounce. If that occurs will be saving it to the DB.

You can check for more in here. https://docs.aws.amazon.com/ses/latest/dg/notification-contents.html.

Similarly create for the complaints as well.

I hope this helps you guyz. ❤️

--

--