Catch AWS SES bounces in Laravel 8

Alex Renoki
FAUN — Developer Community 🐾
5 min readAug 4, 2020

--

“Stolen” from this place.

I honestly think that, as far as I worked with AWS, there is no other thing that is more annoying and kafkaesque than having to comply with the best practices of catching bounces or complaints, and set up a nice environment to catch them, while using AWS’s Simple Email Service.

Not only you cannot get out of the Sandbox if you do not comply with the best practices to avoid spamming and bad reputation when sending mails, but it takes a bit until the support team makes sure you got a nice catching way for bounces or complaints so that they can grant you the right of sending emails to anyone.

I stumbled upon this many times, and I think it’s imperious necessary to have a modular way of solving this, without too much coding.

Meet Laravel AWS Webhooks, a Laravel package that comes with controllers with built-in business logic that helps you write less code to catch SNS messages and write code that matters — like unsubscribing your users.

In this post, you will learn how to do that with Laravel, without having to write your logic to catch the SNS messages, focusing more on the relevant code you need to implement to unsubscribe your users.

There is enough documentation on renoki-co/laravel-aws-webhooks on how to install the package and set up the controller, so we will focus on how to set up an SNS topic, a subscription to the webhook controller, and how to add a configuration set to the emails sent through SES.

Creating a new controller and extending the one that comes with the package is all that you need:

use RenokiCo\AwsWebhooks\Http\Controllers\SesWebhook;class MySesController extends SesWebhook
{
/**
* Handle the Bounce event.
*
* @param array $message
* @param array $originalMessage
* @param \Illuminate\Http\Request $request
* @return void
*/
protected function onBounce(array $message, array $originalMessage, Request $request)
{
//
}
}

Just that! You will also have to set up a route on which you want to receive the messages:

Route::any('/aws/sns/ses', [MySesController::class, 'handle']);

Make sure you also add the route to exceptions in VerifyCsrfToken.php! The route path is an arbitrary set, but it can be anything as long as you call the right controller class and the handle method.

There is already an AWS documentation post on how to create an SNS topic. When creating it, just fill in the necessary namings and just go through all the installation process with Next — Next — Next — etc.

When creating the HTTP(S) subscription, just add the link that points straight to the link whose path was defined earlier and make sure to NOT tick Raw Message Delivery.

The subscription will auto-confirm, so you won’t need to write the logic for that too.

Now that you are ready, with the SNS enabled, you will have to define a configuration set for your domain in SES.

A configuration set is a set of rules to tell what the mails should do when they get sent or when they interact with the mail servers. For example, you can tell your Cloudwatch console to display live metrics regarding the mail sending or, in our case, to ping the SNS topic with a message about a specific recipient’s mail status.

You find on AWS’s docs page a post about how to set up a configuration set for SES with SNS.

When you are defining the configuration settings, you can choose any kind of events to be sent to SNS, either it’s Bounce, Open, or Click. The package supports the following methods, paired with the specific event:

class MySesController extends SesWebhook
{
/**
* Handle the Bounce event.
*
* @param array $message
* @param array $originalMessage
* @param \Illuminate\Http\Request $request
* @return void
*/
protected function onComplaint(array $message, array $originalMessage, Request $request)
{
//
}
protected function onDelivery(array $message, array $originalMessage, Request $request)
{
//
}
protected function onSend(array $message, array $originalMessage, Request $request)
{
//
}
protected function onReject(array $message, array $originalMessage, Request $request)
{
//
}
protected function onOpen(array $message, array $originalMessage, Request $request)
{
//
}
protected function onClick(array $message, array $originalMessage, Request $request)
{
//
}
protected function onRenderingFailure(array $message, array $originalMessage, Request $request)
{
//
}
protected function onDeliveryDelay(array $message, array $originalMessage, Request $request)
{
//
}
}

The last nitpick is to define a configuration set in config/services.php for the SES configuration:

'ses' => [
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
'options' => [
'ConfigurationSetName' => 'your-configuration-set',
],
],

Now you’re done! You are now able to handle Bounce complaints in Laravel with small effort:

use RenokiCo\AwsWebhooks\Http\Controllers\SesWebhook;class MySesController extends SesWebhook
{
/**
* Handle the Bounce event.
*
* @param array $message
* @param array $originalMessage
* @param \Illuminate\Http\Request $request
* @return void
*/
protected function onBounce(array $message, array $originalMessage, Request $request)
{
// Unsubscribe the user from newsletter in case of bounce.
foreach ($message['bounce']['bouncedRecipients'] as $recipient) {
if ($user = User::whereEmail($recipient['emailAddress'])->first()) {
$user->update([
'subscribed' => false,
]);
}
}
}
}

This package can also catch Cloudwatch Alarms status change, but it’s a really different story, for another time perhaps. It relies on renoki-co/laravel-sns-events to catch the SNS messages and transpile them into easy-to-implement methods. You can also check if you wish to catch the raw SNS events and process them yourself.

💸 Sponsorship

Hi, I’m Alex, the founder of Renoki Co.. I’m thankful for taking your time to read this article, and I hope that it helped you. Developing and maintaining packages and delivering good articles about Laravel, Kubernetes and AWS take a lot of time, but I believe it’s a time well spent.

If you support more helpful articles, or you are using one or more Renoki Co. open-source packages in your production apps, in presentation demos, hobby projects, school projects or so, sponsor our work with Github Sponsors. 📦

Subscribe to FAUN topics and get your weekly curated email of the must-read tech stories, news, and tutorials 🗞️

Follow us on Twitter 🐦 and Facebook 👥 and Instagram 📷 and join our Facebook and Linkedin Groups 💬

If this post was helpful, please click the clap 👏 button below a few times to show your support for the author! ⬇

--

--

Minimalist Laravel developer. Full stacking with AWS and Vue.js. Sometimes I deploy to Kubernetes. 🚢