SES Observability with CDK + TS
Tags: aws, cdk, typescript
Published: 2024-01-31 03:49:15

The startup I work for has been experiencing issues with our email communications to clients. Some customers have reported missing confirmation codes and critical messages, as a result, our support team has been inundated with requests to analyze logs to determine how many emails were sent, how many remain, and what type of error may have occurred with those messages.

Our stack uses **Amazon SES**, and because of this, we had some lack of observability, so my CTO asked me to create a way to track it by alarms, in other words, I was asked to add **Observability** to it

After a brief search, I found this [article here](https://www.plum.io/tech-blog/logging-and-monitoring-aws-ses-email-delivery-metrics)

> What Impacts Transactional Email Deliverability?
There are a variety of scenarios that can cause your email list to become polluted. If that happens it can result in your platform sending emails to recipients who either can't receive your messages or don't want them. When that happens, your domain's reputation can be hurt, and as we discussed, that could start that nasty avalanche where your email provider cut and then you can't send any emails.


It's give to me the idea to find a way to integrate SES directly with the **cloudwatch metrics** and then create some **alarms** for it, and looking for it in CDK documentation I found the [Configuration Set](https://docs.aws.amazon.com/ses/latest/dg/using-configuration-sets.html), basically: 

> Configuration sets in Amazon SES are groups of rules applied to verified identities like domains or email addresses, allowing for event destination publishing and IP pool management, enhancing email sending metrics and sender reputation isolation. They can be associated with verified identities via email headers or as default sets during identity creation or editing.

## Get something off the drawing board 
![man saying do it](https://media2.giphy.com/media/v1.Y2lkPTc5MGI3NjExeTF6cGtuNDB5MDh2b3F0NXhnd3Z4YnFzcmV4Y2wwa21nZXZ6aGoyYSZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/8KqTg3Yeh0mwI8A2Ic/giphy.gif)

_With SES email delivery notifications, you're like the captain of your own ship — configuring identities, creating snazzy config sets, and setting up CloudWatch alarms like a boss. It's like having a superhero sidekick that alerts you whenever things go haywire!_

### Creating the ConfigurationSet
```typescript
private createConfigurationSet(topic: ITopic): void {
    const configSet = new ConfigurationSet(this, 'email-delivery-configuration-set', {
      sendingEnabled: true,
      configurationSetName: `${functionName}-notifications`,
      reputationMetrics: true,
    })

    configSet.addEventDestination('sns', {
      destination: EventDestination.snsTopic(topic),
      configurationSetEventDestinationName: `${functionName}-sns`,
      enabled: true,
      events: [
        EmailSendingEvent.SEND,
        EmailSendingEvent.REJECT,
        EmailSendingEvent.BOUNCE,
        EmailSendingEvent.COMPLAINT,
        EmailSendingEvent.DELIVERY,
        EmailSendingEvent.OPEN,
        EmailSendingEvent.RENDERING_FAILURE,
        EmailSendingEvent.CLICK,
      ],
    })
  }
```
SES emits these events; you can thoroughly look [here](https://docs.aws.amazon.com/pt_br/ses/latest/dg/monitor-sending-activity.html).

### Creating the alarm

```typescript
private createAlarms(
    alarmTopic: ITopic,
    eventsToAlarmWithThreshold: Array<{ event: string; threshold: number }>,
  ): void {
    for (const eventToAlarm of eventsToAlarmWithThreshold) {
      const { event, threshold } = eventToAlarm
      const alarm = new Alarm(this, `ses-${event.toLowerCase()}-alarm`, {
        metric: new Metric({
          namespace: 'AWS/SES',
          metricName: event,
          statistic: 'Sum',
        }),
        threshold,
        evaluationPeriods: 5,
        comparisonOperator: ComparisonOperator.GREATER_THAN_THRESHOLD,
        actionsEnabled: true,
        alarmDescription: `SES Account ${event} Alarm`,
        treatMissingData: TreatMissingData.NOT_BREACHING,
        alarmName: `ses-account-${event.toLowerCase()}-alarm`,
        datapointsToAlarm: 1,
      })

      alarm.addAlarmAction(new SnsAction(alarmTopic))
    }
  }
```

I'm just simply specifying the event I want to be alarmed for. [You know a lot of triggered alarms for things that aren't actually problems, and it can be annoying and a waste of time](https://unsworks.unsw.edu.au/fapi/datastream/unsworks:13701/bind4e26dc2-95d3-40c4-9bd5-f812664666bc?view=true), we all want to avoid [alert fatigue](https://granulate.io/blog/dont-be-alarmed-by-alert-fatigue/)


### Merging everything 
```typescript
 const emailDeliveryTopic = Topic.fromTopicArn(
      this,
      `${functionName}-notification-topic`,
      Fn.importValue(`infrastructure::topic::${functionName}::arn`),
    )

    const slackTopic = Topic.fromTopicArn(
      this,
      `${functionName}-slack-topic`,
      Fn.importValue(`infrastructure::topic::slack-topic::arn`),
    )

    
    new EmailIdentity(this, 'Identity', {
      identity: Identity.publicHostedZone(myHostedZone),
      mailFromDomain: 'mail.cdk.dev',
      configurationSet: this.createConfigurationSet(emailDeliveryTopic),
    });

    this.createAlarms(slackTopic, [
      {
        event: 'Complaint',
        threshold: 1,
      },
      {
        event: 'Reject',
        threshold: 0,
      },
      {
        event: 'RenderingFailure',
        threshold: 0,
      },
    ])
  }
```
In order to associate your ConfigurationSet to an Identity, you will need to create and associate it using the console. Unfortunately, CDK does not currently support importing an existing Identity. If you are in a similar situation to mine, please keep this in mind.


## Wrapping Up
Implementing SES email delivery notifications and embracing observability in AWS is key to building resilient and reliable cloud applications. By leveraging real-time insights and proactive monitoring, you can optimize performance, detect faults, and ensure seamless operation of your email delivery infrastructure.

In summary, observability isn't just a buzzword — it's a fundamental principle for modern cloud environments. Embrace it, implement it, and empower your applications to thrive in the ever-evolving landscape of cloud computing. Happy monitoring!

![cheers](https://i.giphy.com/URTRF60r6MPYI.webp)