Configure AWS SESv2 Event Destinations

The aws:sesv2/configurationSetEventDestination:ConfigurationSetEventDestination resource, part of the Pulumi AWS provider, routes SES email events (sends, bounces, complaints, deliveries) to monitoring and analytics destinations. This guide focuses on three capabilities: CloudWatch metrics with custom dimensions, Kinesis Firehose streaming for archival, and SNS topic publishing for real-time alerts.

Event destinations attach to SESv2 configuration sets and reference existing AWS services. The examples are intentionally small. Combine them with your own configuration sets, IAM roles, and destination infrastructure.

Send email events to CloudWatch for metrics and dashboards

Teams monitoring email delivery route SES events to CloudWatch to build custom metrics and dashboards.

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

const example = new aws.sesv2.ConfigurationSet("example", {configurationSetName: "example"});
const exampleConfigurationSetEventDestination = new aws.sesv2.ConfigurationSetEventDestination("example", {
    configurationSetName: example.configurationSetName,
    eventDestinationName: "example",
    eventDestination: {
        cloudWatchDestination: {
            dimensionConfigurations: [{
                defaultDimensionValue: "example",
                dimensionName: "example",
                dimensionValueSource: "MESSAGE_TAG",
            }],
        },
        enabled: true,
        matchingEventTypes: ["SEND"],
    },
});
import pulumi
import pulumi_aws as aws

example = aws.sesv2.ConfigurationSet("example", configuration_set_name="example")
example_configuration_set_event_destination = aws.sesv2.ConfigurationSetEventDestination("example",
    configuration_set_name=example.configuration_set_name,
    event_destination_name="example",
    event_destination={
        "cloud_watch_destination": {
            "dimension_configurations": [{
                "default_dimension_value": "example",
                "dimension_name": "example",
                "dimension_value_source": "MESSAGE_TAG",
            }],
        },
        "enabled": True,
        "matching_event_types": ["SEND"],
    })
package main

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

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		example, err := sesv2.NewConfigurationSet(ctx, "example", &sesv2.ConfigurationSetArgs{
			ConfigurationSetName: pulumi.String("example"),
		})
		if err != nil {
			return err
		}
		_, err = sesv2.NewConfigurationSetEventDestination(ctx, "example", &sesv2.ConfigurationSetEventDestinationArgs{
			ConfigurationSetName: example.ConfigurationSetName,
			EventDestinationName: pulumi.String("example"),
			EventDestination: &sesv2.ConfigurationSetEventDestinationEventDestinationArgs{
				CloudWatchDestination: &sesv2.ConfigurationSetEventDestinationEventDestinationCloudWatchDestinationArgs{
					DimensionConfigurations: sesv2.ConfigurationSetEventDestinationEventDestinationCloudWatchDestinationDimensionConfigurationArray{
						&sesv2.ConfigurationSetEventDestinationEventDestinationCloudWatchDestinationDimensionConfigurationArgs{
							DefaultDimensionValue: pulumi.String("example"),
							DimensionName:         pulumi.String("example"),
							DimensionValueSource:  pulumi.String("MESSAGE_TAG"),
						},
					},
				},
				Enabled: pulumi.Bool(true),
				MatchingEventTypes: pulumi.StringArray{
					pulumi.String("SEND"),
				},
			},
		})
		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 example = new Aws.SesV2.ConfigurationSet("example", new()
    {
        ConfigurationSetName = "example",
    });

    var exampleConfigurationSetEventDestination = new Aws.SesV2.ConfigurationSetEventDestination("example", new()
    {
        ConfigurationSetName = example.ConfigurationSetName,
        EventDestinationName = "example",
        EventDestination = new Aws.SesV2.Inputs.ConfigurationSetEventDestinationEventDestinationArgs
        {
            CloudWatchDestination = new Aws.SesV2.Inputs.ConfigurationSetEventDestinationEventDestinationCloudWatchDestinationArgs
            {
                DimensionConfigurations = new[]
                {
                    new Aws.SesV2.Inputs.ConfigurationSetEventDestinationEventDestinationCloudWatchDestinationDimensionConfigurationArgs
                    {
                        DefaultDimensionValue = "example",
                        DimensionName = "example",
                        DimensionValueSource = "MESSAGE_TAG",
                    },
                },
            },
            Enabled = true,
            MatchingEventTypes = new[]
            {
                "SEND",
            },
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.sesv2.ConfigurationSet;
import com.pulumi.aws.sesv2.ConfigurationSetArgs;
import com.pulumi.aws.sesv2.ConfigurationSetEventDestination;
import com.pulumi.aws.sesv2.ConfigurationSetEventDestinationArgs;
import com.pulumi.aws.sesv2.inputs.ConfigurationSetEventDestinationEventDestinationArgs;
import com.pulumi.aws.sesv2.inputs.ConfigurationSetEventDestinationEventDestinationCloudWatchDestinationArgs;
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 example = new ConfigurationSet("example", ConfigurationSetArgs.builder()
            .configurationSetName("example")
            .build());

        var exampleConfigurationSetEventDestination = new ConfigurationSetEventDestination("exampleConfigurationSetEventDestination", ConfigurationSetEventDestinationArgs.builder()
            .configurationSetName(example.configurationSetName())
            .eventDestinationName("example")
            .eventDestination(ConfigurationSetEventDestinationEventDestinationArgs.builder()
                .cloudWatchDestination(ConfigurationSetEventDestinationEventDestinationCloudWatchDestinationArgs.builder()
                    .dimensionConfigurations(ConfigurationSetEventDestinationEventDestinationCloudWatchDestinationDimensionConfigurationArgs.builder()
                        .defaultDimensionValue("example")
                        .dimensionName("example")
                        .dimensionValueSource("MESSAGE_TAG")
                        .build())
                    .build())
                .enabled(true)
                .matchingEventTypes("SEND")
                .build())
            .build());

    }
}
resources:
  example:
    type: aws:sesv2:ConfigurationSet
    properties:
      configurationSetName: example
  exampleConfigurationSetEventDestination:
    type: aws:sesv2:ConfigurationSetEventDestination
    name: example
    properties:
      configurationSetName: ${example.configurationSetName}
      eventDestinationName: example
      eventDestination:
        cloudWatchDestination:
          dimensionConfigurations:
            - defaultDimensionValue: example
              dimensionName: example
              dimensionValueSource: MESSAGE_TAG
        enabled: true
        matchingEventTypes:
          - SEND

When SES processes emails, it publishes events to CloudWatch based on matchingEventTypes. The dimensionConfigurations array defines how to group metrics; dimensionValueSource determines where dimension values come from (MESSAGE_TAG extracts from email headers). This creates CloudWatch metrics you can query and visualize.

Stream email events to S3 or Redshift via Firehose

Applications needing long-term storage or analytics route email events through Kinesis Firehose.

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

const example = new aws.sesv2.ConfigurationSet("example", {configurationSetName: "example"});
const exampleConfigurationSetEventDestination = new aws.sesv2.ConfigurationSetEventDestination("example", {
    configurationSetName: example.configurationSetName,
    eventDestinationName: "example",
    eventDestination: {
        kinesisFirehoseDestination: {
            deliveryStreamArn: exampleAwsKinesisFirehoseDeliveryStream.arn,
            iamRoleArn: exampleAwsIamRole.arn,
        },
        enabled: true,
        matchingEventTypes: ["SEND"],
    },
});
import pulumi
import pulumi_aws as aws

example = aws.sesv2.ConfigurationSet("example", configuration_set_name="example")
example_configuration_set_event_destination = aws.sesv2.ConfigurationSetEventDestination("example",
    configuration_set_name=example.configuration_set_name,
    event_destination_name="example",
    event_destination={
        "kinesis_firehose_destination": {
            "delivery_stream_arn": example_aws_kinesis_firehose_delivery_stream["arn"],
            "iam_role_arn": example_aws_iam_role["arn"],
        },
        "enabled": True,
        "matching_event_types": ["SEND"],
    })
package main

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

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		example, err := sesv2.NewConfigurationSet(ctx, "example", &sesv2.ConfigurationSetArgs{
			ConfigurationSetName: pulumi.String("example"),
		})
		if err != nil {
			return err
		}
		_, err = sesv2.NewConfigurationSetEventDestination(ctx, "example", &sesv2.ConfigurationSetEventDestinationArgs{
			ConfigurationSetName: example.ConfigurationSetName,
			EventDestinationName: pulumi.String("example"),
			EventDestination: &sesv2.ConfigurationSetEventDestinationEventDestinationArgs{
				KinesisFirehoseDestination: &sesv2.ConfigurationSetEventDestinationEventDestinationKinesisFirehoseDestinationArgs{
					DeliveryStreamArn: pulumi.Any(exampleAwsKinesisFirehoseDeliveryStream.Arn),
					IamRoleArn:        pulumi.Any(exampleAwsIamRole.Arn),
				},
				Enabled: pulumi.Bool(true),
				MatchingEventTypes: pulumi.StringArray{
					pulumi.String("SEND"),
				},
			},
		})
		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 example = new Aws.SesV2.ConfigurationSet("example", new()
    {
        ConfigurationSetName = "example",
    });

    var exampleConfigurationSetEventDestination = new Aws.SesV2.ConfigurationSetEventDestination("example", new()
    {
        ConfigurationSetName = example.ConfigurationSetName,
        EventDestinationName = "example",
        EventDestination = new Aws.SesV2.Inputs.ConfigurationSetEventDestinationEventDestinationArgs
        {
            KinesisFirehoseDestination = new Aws.SesV2.Inputs.ConfigurationSetEventDestinationEventDestinationKinesisFirehoseDestinationArgs
            {
                DeliveryStreamArn = exampleAwsKinesisFirehoseDeliveryStream.Arn,
                IamRoleArn = exampleAwsIamRole.Arn,
            },
            Enabled = true,
            MatchingEventTypes = new[]
            {
                "SEND",
            },
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.sesv2.ConfigurationSet;
import com.pulumi.aws.sesv2.ConfigurationSetArgs;
import com.pulumi.aws.sesv2.ConfigurationSetEventDestination;
import com.pulumi.aws.sesv2.ConfigurationSetEventDestinationArgs;
import com.pulumi.aws.sesv2.inputs.ConfigurationSetEventDestinationEventDestinationArgs;
import com.pulumi.aws.sesv2.inputs.ConfigurationSetEventDestinationEventDestinationKinesisFirehoseDestinationArgs;
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 example = new ConfigurationSet("example", ConfigurationSetArgs.builder()
            .configurationSetName("example")
            .build());

        var exampleConfigurationSetEventDestination = new ConfigurationSetEventDestination("exampleConfigurationSetEventDestination", ConfigurationSetEventDestinationArgs.builder()
            .configurationSetName(example.configurationSetName())
            .eventDestinationName("example")
            .eventDestination(ConfigurationSetEventDestinationEventDestinationArgs.builder()
                .kinesisFirehoseDestination(ConfigurationSetEventDestinationEventDestinationKinesisFirehoseDestinationArgs.builder()
                    .deliveryStreamArn(exampleAwsKinesisFirehoseDeliveryStream.arn())
                    .iamRoleArn(exampleAwsIamRole.arn())
                    .build())
                .enabled(true)
                .matchingEventTypes("SEND")
                .build())
            .build());

    }
}
resources:
  example:
    type: aws:sesv2:ConfigurationSet
    properties:
      configurationSetName: example
  exampleConfigurationSetEventDestination:
    type: aws:sesv2:ConfigurationSetEventDestination
    name: example
    properties:
      configurationSetName: ${example.configurationSetName}
      eventDestinationName: example
      eventDestination:
        kinesisFirehoseDestination:
          deliveryStreamArn: ${exampleAwsKinesisFirehoseDeliveryStream.arn}
          iamRoleArn: ${exampleAwsIamRole.arn}
        enabled: true
        matchingEventTypes:
          - SEND

SES publishes events to the Firehose delivery stream, which batches and delivers them to S3, Redshift, or other destinations. The iamRoleArn grants SES permission to write to Firehose. This pattern supports compliance archival and feeding analytics pipelines.

Publish email events to SNS topics for notifications

Real-time alerting workflows publish SES events to SNS topics for immediate action.

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

const example = new aws.sesv2.ConfigurationSet("example", {configurationSetName: "example"});
const exampleConfigurationSetEventDestination = new aws.sesv2.ConfigurationSetEventDestination("example", {
    configurationSetName: example.configurationSetName,
    eventDestinationName: "example",
    eventDestination: {
        snsDestination: {
            topicArn: exampleAwsSnsTopic.arn,
        },
        enabled: true,
        matchingEventTypes: ["SEND"],
    },
});
import pulumi
import pulumi_aws as aws

example = aws.sesv2.ConfigurationSet("example", configuration_set_name="example")
example_configuration_set_event_destination = aws.sesv2.ConfigurationSetEventDestination("example",
    configuration_set_name=example.configuration_set_name,
    event_destination_name="example",
    event_destination={
        "sns_destination": {
            "topic_arn": example_aws_sns_topic["arn"],
        },
        "enabled": True,
        "matching_event_types": ["SEND"],
    })
package main

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

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		example, err := sesv2.NewConfigurationSet(ctx, "example", &sesv2.ConfigurationSetArgs{
			ConfigurationSetName: pulumi.String("example"),
		})
		if err != nil {
			return err
		}
		_, err = sesv2.NewConfigurationSetEventDestination(ctx, "example", &sesv2.ConfigurationSetEventDestinationArgs{
			ConfigurationSetName: example.ConfigurationSetName,
			EventDestinationName: pulumi.String("example"),
			EventDestination: &sesv2.ConfigurationSetEventDestinationEventDestinationArgs{
				SnsDestination: &sesv2.ConfigurationSetEventDestinationEventDestinationSnsDestinationArgs{
					TopicArn: pulumi.Any(exampleAwsSnsTopic.Arn),
				},
				Enabled: pulumi.Bool(true),
				MatchingEventTypes: pulumi.StringArray{
					pulumi.String("SEND"),
				},
			},
		})
		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 example = new Aws.SesV2.ConfigurationSet("example", new()
    {
        ConfigurationSetName = "example",
    });

    var exampleConfigurationSetEventDestination = new Aws.SesV2.ConfigurationSetEventDestination("example", new()
    {
        ConfigurationSetName = example.ConfigurationSetName,
        EventDestinationName = "example",
        EventDestination = new Aws.SesV2.Inputs.ConfigurationSetEventDestinationEventDestinationArgs
        {
            SnsDestination = new Aws.SesV2.Inputs.ConfigurationSetEventDestinationEventDestinationSnsDestinationArgs
            {
                TopicArn = exampleAwsSnsTopic.Arn,
            },
            Enabled = true,
            MatchingEventTypes = new[]
            {
                "SEND",
            },
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.sesv2.ConfigurationSet;
import com.pulumi.aws.sesv2.ConfigurationSetArgs;
import com.pulumi.aws.sesv2.ConfigurationSetEventDestination;
import com.pulumi.aws.sesv2.ConfigurationSetEventDestinationArgs;
import com.pulumi.aws.sesv2.inputs.ConfigurationSetEventDestinationEventDestinationArgs;
import com.pulumi.aws.sesv2.inputs.ConfigurationSetEventDestinationEventDestinationSnsDestinationArgs;
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 example = new ConfigurationSet("example", ConfigurationSetArgs.builder()
            .configurationSetName("example")
            .build());

        var exampleConfigurationSetEventDestination = new ConfigurationSetEventDestination("exampleConfigurationSetEventDestination", ConfigurationSetEventDestinationArgs.builder()
            .configurationSetName(example.configurationSetName())
            .eventDestinationName("example")
            .eventDestination(ConfigurationSetEventDestinationEventDestinationArgs.builder()
                .snsDestination(ConfigurationSetEventDestinationEventDestinationSnsDestinationArgs.builder()
                    .topicArn(exampleAwsSnsTopic.arn())
                    .build())
                .enabled(true)
                .matchingEventTypes("SEND")
                .build())
            .build());

    }
}
resources:
  example:
    type: aws:sesv2:ConfigurationSet
    properties:
      configurationSetName: example
  exampleConfigurationSetEventDestination:
    type: aws:sesv2:ConfigurationSetEventDestination
    name: example
    properties:
      configurationSetName: ${example.configurationSetName}
      eventDestinationName: example
      eventDestination:
        snsDestination:
          topicArn: ${exampleAwsSnsTopic.arn}
        enabled: true
        matchingEventTypes:
          - SEND

When events match the specified types, SES publishes to the SNS topic. Subscribers (Lambda functions, email endpoints, SQS queues) receive notifications immediately. This enables bounce handling, complaint processing, and delivery confirmations.

Beyond these examples

These snippets focus on specific event destination features: CloudWatch metrics and dimensions, 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 SESv2 configuration sets, CloudWatch event buses, Kinesis Firehose streams, SNS topics, Pinpoint apps, and IAM roles with appropriate permissions. They focus on routing configuration rather than provisioning the destination services.

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

  • Event filtering beyond matchingEventTypes
  • Multiple destinations per configuration set
  • EventBridge and Pinpoint integration patterns
  • Dimension value sources (EMAIL_HEADER, LINK_TAG)

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

Let's configure AWS SESv2 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 Types & Configuration
What destination types are available for SES event tracking?
You can send SES events to five destination types: CloudWatch, EventBridge, Kinesis Firehose, Pinpoint, and SNS. Each destination type requires different configuration properties.
What's the difference between CloudWatch and EventBridge destinations?
CloudWatch destinations use dimensionConfigurations for custom metrics, while EventBridge destinations route events to an event bus using eventBusArn. Choose CloudWatch for metrics and EventBridge for event-driven workflows.
Does Kinesis Firehose destination require special IAM permissions?
Yes, Kinesis Firehose requires both deliveryStreamArn and iamRoleArn. The IAM role must have permissions to write to the delivery stream.
Event Tracking & Monitoring
What event types can I track with event destinations?
The matchingEventTypes property specifies which events to track. All examples show SEND events, but other types like bounces, complaints, and deliveries are also available.
Can I send events to multiple destinations?
Yes, you can create multiple event destinations for the same configuration set by creating separate ConfigurationSetEventDestination resources with different eventDestinationName values.
Resource Management
Can I rename my event destination or move it to a different configuration set?
No, both configurationSetName and eventDestinationName are immutable. Changing either requires destroying and recreating the resource.
How do I import an existing event destination?
Use the format configuration_set_name|event_destination_name for the import ID. For example: pulumi import aws:sesv2/configurationSetEventDestination:ConfigurationSetEventDestination example example_configuration_set|example_event_destination.

Using a different cloud?

Explore integration guides for other cloud providers: