Configure AWS SES Event Destinations

The aws:ses/eventDestination:EventDestination resource, part of the Pulumi AWS provider, routes SES email events (bounces, sends, complaints) to CloudWatch, Kinesis Firehose, or SNS. This guide focuses on three capabilities: CloudWatch metrics integration, Kinesis Firehose streaming, and SNS topic publishing.

Event destinations belong to SES configuration sets and route events to existing CloudWatch namespaces, Kinesis streams, or SNS topics. The examples are intentionally small. Combine them with your own configuration sets and destination infrastructure.

Send email events to CloudWatch for metrics

Teams monitoring email delivery route SES events to CloudWatch to track bounce rates, send volumes, and delivery patterns as custom metrics.

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

const cloudwatch = new aws.ses.EventDestination("cloudwatch", {
    name: "event-destination-cloudwatch",
    configurationSetName: example.name,
    enabled: true,
    matchingTypes: [
        "bounce",
        "send",
    ],
    cloudwatchDestinations: [{
        defaultValue: "default",
        dimensionName: "dimension",
        valueSource: "emailHeader",
    }],
});
import pulumi
import pulumi_aws as aws

cloudwatch = aws.ses.EventDestination("cloudwatch",
    name="event-destination-cloudwatch",
    configuration_set_name=example["name"],
    enabled=True,
    matching_types=[
        "bounce",
        "send",
    ],
    cloudwatch_destinations=[{
        "default_value": "default",
        "dimension_name": "dimension",
        "value_source": "emailHeader",
    }])
package main

import (
	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/ses"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_, err := ses.NewEventDestination(ctx, "cloudwatch", &ses.EventDestinationArgs{
			Name:                 pulumi.String("event-destination-cloudwatch"),
			ConfigurationSetName: pulumi.Any(example.Name),
			Enabled:              pulumi.Bool(true),
			MatchingTypes: pulumi.StringArray{
				pulumi.String("bounce"),
				pulumi.String("send"),
			},
			CloudwatchDestinations: ses.EventDestinationCloudwatchDestinationArray{
				&ses.EventDestinationCloudwatchDestinationArgs{
					DefaultValue:  pulumi.String("default"),
					DimensionName: pulumi.String("dimension"),
					ValueSource:   pulumi.String("emailHeader"),
				},
			},
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Aws = Pulumi.Aws;

return await Deployment.RunAsync(() => 
{
    var cloudwatch = new Aws.Ses.EventDestination("cloudwatch", new()
    {
        Name = "event-destination-cloudwatch",
        ConfigurationSetName = example.Name,
        Enabled = true,
        MatchingTypes = new[]
        {
            "bounce",
            "send",
        },
        CloudwatchDestinations = new[]
        {
            new Aws.Ses.Inputs.EventDestinationCloudwatchDestinationArgs
            {
                DefaultValue = "default",
                DimensionName = "dimension",
                ValueSource = "emailHeader",
            },
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.ses.EventDestination;
import com.pulumi.aws.ses.EventDestinationArgs;
import com.pulumi.aws.ses.inputs.EventDestinationCloudwatchDestinationArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        var cloudwatch = new EventDestination("cloudwatch", EventDestinationArgs.builder()
            .name("event-destination-cloudwatch")
            .configurationSetName(example.name())
            .enabled(true)
            .matchingTypes(            
                "bounce",
                "send")
            .cloudwatchDestinations(EventDestinationCloudwatchDestinationArgs.builder()
                .defaultValue("default")
                .dimensionName("dimension")
                .valueSource("emailHeader")
                .build())
            .build());

    }
}
resources:
  cloudwatch:
    type: aws:ses:EventDestination
    properties:
      name: event-destination-cloudwatch
      configurationSetName: ${example.name}
      enabled: true
      matchingTypes:
        - bounce
        - send
      cloudwatchDestinations:
        - defaultValue: default
          dimensionName: dimension
          valueSource: emailHeader

When SES generates events matching the types in matchingTypes, it publishes them to CloudWatch with the specified dimension. The valueSource property controls where the dimension value comes from (email headers, message tags, or a default). The dimensionName becomes the metric dimension in CloudWatch, allowing you to filter and aggregate metrics by that attribute.

Stream email events to Kinesis Firehose

Applications that analyze email events in data warehouses or S3 stream SES events through Kinesis Firehose for transformation and delivery.

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

const kinesis = new aws.ses.EventDestination("kinesis", {
    name: "event-destination-kinesis",
    configurationSetName: exampleAwsSesConfigurationSet.name,
    enabled: true,
    matchingTypes: [
        "bounce",
        "send",
    ],
    kinesisDestination: {
        streamArn: exampleAwsKinesisFirehoseDeliveryStream.arn,
        roleArn: example.arn,
    },
});
import pulumi
import pulumi_aws as aws

kinesis = aws.ses.EventDestination("kinesis",
    name="event-destination-kinesis",
    configuration_set_name=example_aws_ses_configuration_set["name"],
    enabled=True,
    matching_types=[
        "bounce",
        "send",
    ],
    kinesis_destination={
        "stream_arn": example_aws_kinesis_firehose_delivery_stream["arn"],
        "role_arn": example["arn"],
    })
package main

import (
	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/ses"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_, err := ses.NewEventDestination(ctx, "kinesis", &ses.EventDestinationArgs{
			Name:                 pulumi.String("event-destination-kinesis"),
			ConfigurationSetName: pulumi.Any(exampleAwsSesConfigurationSet.Name),
			Enabled:              pulumi.Bool(true),
			MatchingTypes: pulumi.StringArray{
				pulumi.String("bounce"),
				pulumi.String("send"),
			},
			KinesisDestination: &ses.EventDestinationKinesisDestinationArgs{
				StreamArn: pulumi.Any(exampleAwsKinesisFirehoseDeliveryStream.Arn),
				RoleArn:   pulumi.Any(example.Arn),
			},
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Aws = Pulumi.Aws;

return await Deployment.RunAsync(() => 
{
    var kinesis = new Aws.Ses.EventDestination("kinesis", new()
    {
        Name = "event-destination-kinesis",
        ConfigurationSetName = exampleAwsSesConfigurationSet.Name,
        Enabled = true,
        MatchingTypes = new[]
        {
            "bounce",
            "send",
        },
        KinesisDestination = new Aws.Ses.Inputs.EventDestinationKinesisDestinationArgs
        {
            StreamArn = exampleAwsKinesisFirehoseDeliveryStream.Arn,
            RoleArn = example.Arn,
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.ses.EventDestination;
import com.pulumi.aws.ses.EventDestinationArgs;
import com.pulumi.aws.ses.inputs.EventDestinationKinesisDestinationArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        var kinesis = new EventDestination("kinesis", EventDestinationArgs.builder()
            .name("event-destination-kinesis")
            .configurationSetName(exampleAwsSesConfigurationSet.name())
            .enabled(true)
            .matchingTypes(            
                "bounce",
                "send")
            .kinesisDestination(EventDestinationKinesisDestinationArgs.builder()
                .streamArn(exampleAwsKinesisFirehoseDeliveryStream.arn())
                .roleArn(example.arn())
                .build())
            .build());

    }
}
resources:
  kinesis:
    type: aws:ses:EventDestination
    properties:
      name: event-destination-kinesis
      configurationSetName: ${exampleAwsSesConfigurationSet.name}
      enabled: true
      matchingTypes:
        - bounce
        - send
      kinesisDestination:
        streamArn: ${exampleAwsKinesisFirehoseDeliveryStream.arn}
        roleArn: ${example.arn}

The kinesisDestination property sends events to a Firehose delivery stream, which can transform and route them to S3, Redshift, or other destinations. The roleArn grants SES permission to write to the stream. Note that you can specify either cloudwatchDestinations or kinesisDestination, but not both.

Publish email events to SNS topics

Real-time notification systems subscribe to SES events via SNS, triggering Lambda functions or webhooks when bounces or complaints occur.

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

const sns = new aws.ses.EventDestination("sns", {
    name: "event-destination-sns",
    configurationSetName: exampleAwsSesConfigurationSet.name,
    enabled: true,
    matchingTypes: [
        "bounce",
        "send",
    ],
    snsDestination: {
        topicArn: example.arn,
    },
});
import pulumi
import pulumi_aws as aws

sns = aws.ses.EventDestination("sns",
    name="event-destination-sns",
    configuration_set_name=example_aws_ses_configuration_set["name"],
    enabled=True,
    matching_types=[
        "bounce",
        "send",
    ],
    sns_destination={
        "topic_arn": example["arn"],
    })
package main

import (
	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/ses"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_, err := ses.NewEventDestination(ctx, "sns", &ses.EventDestinationArgs{
			Name:                 pulumi.String("event-destination-sns"),
			ConfigurationSetName: pulumi.Any(exampleAwsSesConfigurationSet.Name),
			Enabled:              pulumi.Bool(true),
			MatchingTypes: pulumi.StringArray{
				pulumi.String("bounce"),
				pulumi.String("send"),
			},
			SnsDestination: &ses.EventDestinationSnsDestinationArgs{
				TopicArn: pulumi.Any(example.Arn),
			},
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Aws = Pulumi.Aws;

return await Deployment.RunAsync(() => 
{
    var sns = new Aws.Ses.EventDestination("sns", new()
    {
        Name = "event-destination-sns",
        ConfigurationSetName = exampleAwsSesConfigurationSet.Name,
        Enabled = true,
        MatchingTypes = new[]
        {
            "bounce",
            "send",
        },
        SnsDestination = new Aws.Ses.Inputs.EventDestinationSnsDestinationArgs
        {
            TopicArn = example.Arn,
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.ses.EventDestination;
import com.pulumi.aws.ses.EventDestinationArgs;
import com.pulumi.aws.ses.inputs.EventDestinationSnsDestinationArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        var sns = new EventDestination("sns", EventDestinationArgs.builder()
            .name("event-destination-sns")
            .configurationSetName(exampleAwsSesConfigurationSet.name())
            .enabled(true)
            .matchingTypes(            
                "bounce",
                "send")
            .snsDestination(EventDestinationSnsDestinationArgs.builder()
                .topicArn(example.arn())
                .build())
            .build());

    }
}
resources:
  sns:
    type: aws:ses:EventDestination
    properties:
      name: event-destination-sns
      configurationSetName: ${exampleAwsSesConfigurationSet.name}
      enabled: true
      matchingTypes:
        - bounce
        - send
      snsDestination:
        topicArn: ${example.arn}

The snsDestination property publishes events to an SNS topic. Subscribers receive event payloads in real time, enabling immediate response to delivery issues or user actions. The topicArn points to the SNS topic that will receive the events.

Beyond these examples

These snippets focus on specific event destination features: CloudWatch metrics integration, Kinesis Firehose streaming, and SNS topic publishing. They’re intentionally minimal rather than full email monitoring solutions.

The examples reference pre-existing infrastructure such as SES configuration sets, CloudWatch metric namespaces, Kinesis Firehose delivery streams and IAM roles, and SNS topics. They focus on routing configuration rather than provisioning the destination infrastructure.

To keep things focused, common event destination patterns are omitted, including:

  • Event filtering beyond matchingTypes (no property-level filters)
  • Multiple destinations per event type (mutually exclusive: CloudWatch OR Kinesis)
  • Destination-specific error handling or retry configuration
  • Event payload transformation before delivery

These omissions are intentional: the goal is to illustrate how each destination type is wired, not provide drop-in monitoring modules. See the SES EventDestination resource reference for all available configuration options.

Let's configure AWS SES Event Destinations

Get started with Pulumi Cloud, then follow our quick setup guide to deploy this infrastructure.

Try Pulumi Cloud for FREE

Frequently Asked Questions

Destination Configuration
Can I use both CloudWatch and Kinesis destinations for the same event destination?
No, you can only specify either cloudwatchDestinations or kinesisDestination, not both. SNS destinations can be used independently of this restriction.
What destination types are available for SES event destinations?
You have three options: CloudWatch (for metrics with cloudwatchDestinations), Kinesis Firehose (for streaming with kinesisDestination), and SNS (for notifications with snsDestination).
Event Types & Matching
What event types can I track with matchingTypes?
Valid values are: send, reject, bounce, complaint, delivery, open, click, and renderingFailure.
What's the difference between bounce and reject events?
Both are valid matchingTypes values. Bounces occur when delivery fails after acceptance, while rejects happen when SES refuses the email before sending.
Immutability & Updates
What properties can't be changed after creating an event destination?
The configurationSetName, name, matchingTypes, enabled flag, and all destination configurations (cloudwatchDestinations, kinesisDestination, snsDestination) are immutable. Changing any of these forces resource replacement.
How do I import an existing SES event destination?
Use the format configuration-set-name/event-destination-name, for example: pulumi import aws:ses/eventDestination:EventDestination sns some-configuration-set-test/event-destination-sns

Using a different cloud?

Explore monitoring guides for other cloud providers: