Configure AWS SES Event Destinations

The aws:ses/eventDestination:EventDestination resource, part of the Pulumi AWS provider, routes SES email events (bounces, deliveries, opens, clicks) 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 processes emails, it evaluates the matchingTypes filter and sends matching events to CloudWatch. The cloudwatchDestinations property defines custom metric dimensions; dimensionName sets the metric dimension name, and valueSource specifies where to extract the dimension value (here, from email headers). This enables dashboards and alarms based on email behavior.

Stream email events to Kinesis Firehose

Applications that need to analyze email events at scale or archive them to S3 route events through Kinesis Firehose.

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 stream for transformation and delivery. The streamArn identifies the destination stream, and roleArn grants SES permission to write records. Firehose can then deliver events to S3, Redshift, or other analytics systems.

Publish email events to SNS topics

Systems that need real-time notifications publish to SNS topics, enabling Lambda functions, SQS queues, or HTTP endpoints to react immediately.

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. The topicArn identifies the destination topic. Subscribers receive event payloads in real time, enabling immediate processing of bounces, complaints, or delivery confirmations.

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 streams, or SNS topics, and IAM roles with appropriate permissions. 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 conditional routing)
  • Multiple destinations per event type (mutually exclusive)
  • Destination-specific error handling
  • Event payload transformation

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
What destination types are available for SES event destinations?
You can send SES events to three destination types: CloudWatch (cloudwatchDestinations), Kinesis Firehose (kinesisDestination), or SNS (snsDestination).
Can I configure both CloudWatch and Kinesis destinations on the same event destination?
No, you can specify either cloudwatchDestinations or kinesisDestination, but not both. SNS destinations can be configured independently.
When should I use CloudWatch vs. Kinesis vs. SNS destinations?
CloudWatch is for metrics and monitoring with custom dimensions. Kinesis Firehose is for streaming events to data lakes or analytics. SNS is for real-time notifications to subscribers.
Event Types & Matching
What email event types can I track with matchingTypes?
You can track eight event types: send, reject, bounce, complaint, delivery, open, click, and renderingFailure.
Immutability & Limitations
What properties can't be changed after creating an event destination?
The following properties are immutable and require resource replacement if changed: configurationSetName, matchingTypes, name, enabled, and all destination configurations (cloudwatchDestinations, kinesisDestination, snsDestination).

Using a different cloud?

Explore monitoring guides for other cloud providers: