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 FREEFrequently Asked Questions
Destination Configuration
cloudwatchDestinations or kinesisDestination, not both. SNS destinations can be used independently of this restriction.cloudwatchDestinations), Kinesis Firehose (for streaming with kinesisDestination), and SNS (for notifications with snsDestination).Event Types & Matching
send, reject, bounce, complaint, delivery, open, click, and renderingFailure.matchingTypes values. Bounces occur when delivery fails after acceptance, while rejects happen when SES refuses the email before sending.Immutability & Updates
configurationSetName, name, matchingTypes, enabled flag, and all destination configurations (cloudwatchDestinations, kinesisDestination, snsDestination) are immutable. Changing any of these forces resource replacement.configuration-set-name/event-destination-name, for example: pulumi import aws:ses/eventDestination:EventDestination sns some-configuration-set-test/event-destination-snsUsing a different cloud?
Explore monitoring guides for other cloud providers: