Amazon SES CloudWatch Monitoring: Metrics, Alarms, and Dashboards

Isometric teal pillar with a monitor-gauge face beside faded chart icons and a purple alarm bell, captioned SES CloudWatch.

Without CloudWatch monitoring you will not know your Amazon SES bounce rate is climbing until AWS sends you a warning email. By the time that warning arrives, your account is already under review and your sender reputation is leaking. CloudWatch wires SES into the same alerting stack you already use for the rest of AWS - account-level metrics arrive automatically under the AWS/SES namespace, configuration sets let you slice by campaign or tenant, and alarms can page you the moment a bounce rate crosses 2%.

This guide walks through the production monitoring stack: which metrics SES emits for free, when to add a configuration set, how to write alarms that fire before AWS notices, a working dashboard JSON you can paste into the console, and how to fan alerts out to Slack or PagerDuty via SNS.

What CloudWatch metrics SES emits automatically

Amazon SES publishes account-level metrics to the AWS/SES CloudWatch namespace automatically, with no configuration set required. According to the AWS SES monitoring documentation, available metrics include sends, deliveries, opens, clicks, bounces, bounce rate, complaints, complaint rate, rejects, rendering failures, and blacklisted IPs. The catch: metrics only appear after the first matching event occurs, so a fresh account will show no Bounce metric in CloudWatch until at least one email bounces or you trigger a bounce through the SES mailbox simulator.

Metric Namespace What it counts Typical alarm
Send AWS/SES Successful SendEmail / SendRawEmail calls Anomaly detection on drop-off
Delivery AWS/SES Messages accepted by the recipient MX Ratio vs Send < 95%
Bounce AWS/SES Hard bounces (and exhausted soft bounces) Count spike
Reputation.BounceRate AWS/SES Rolling bounce rate AWS uses for enforcement > 2% for 15 min
Reputation.ComplaintRate AWS/SES Rolling complaint rate AWS uses for enforcement > 0.05% for 15 min
Complaint AWS/SES Recipients hitting "Mark as spam" Count spike
Reject AWS/SES SES rejected the message (virus, policy) Any non-zero in 5 min
Rendering Failure AWS/SES Template variable mismatches Any non-zero
Source: AWS SES monitoring documentation. Reputation.BounceRate and Reputation.ComplaintRate are the metrics AWS itself uses to decide whether to pause your account.

The two metrics that matter most for staying in production are Reputation.BounceRate and Reputation.ComplaintRate. Per the SES reputation metrics docs, AWS places accounts under review at 5% bounce / 0.1% complaint and pauses sending at 10% bounce / 0.5% complaint. Alarm at roughly half those thresholds so you have time to investigate before AWS does.

Configuration sets for per-campaign metrics

Account-level metrics tell you the SES account is healthy. They cannot tell you which campaign or which tenant is responsible for a bounce spike. To get that breakdown you attach a configuration set with a CloudWatch event destination, and SES will publish per-event metrics dimensioned by the message tags you choose - see our deeper write-up on SES event publishing for the data-pipeline view.

Create the configuration set and event destination with the AWS CLI:

# 1. Create a configuration set
aws sesv2 create-configuration-set \
  --configuration-set-name marketing-prod

# 2. Add a CloudWatch event destination that fans out the key events
aws sesv2 create-configuration-set-event-destination \
  --configuration-set-name marketing-prod \
  --event-destination-name cw-prod \
  --event-destination '{
    "Enabled": true,
    "MatchingEventTypes": ["SEND","DELIVERY","BOUNCE","COMPLAINT","REJECT","RENDERING_FAILURE"],
    "CloudWatchDestination": {
      "DimensionConfigurations": [
        {
          "DimensionName": "ses:configuration-set",
          "DimensionValueSource": "MESSAGE_TAG",
          "DefaultDimensionValue": "marketing-prod"
        },
        {
          "DimensionName": "campaign",
          "DimensionValueSource": "MESSAGE_TAG",
          "DefaultDimensionValue": "none"
        }
      ]
    }
  }'

Then tag each send with the campaign name:

import boto3
ses = boto3.client("sesv2")

ses.send_email(
    FromEmailAddress="news@example.com",
    Destination={"ToAddresses": ["user@example.com"]},
    ConfigurationSetName="marketing-prod",
    EmailTags=[{"Name": "campaign", "Value": "october-newsletter"}],
    Content={"Simple": {
        "Subject": {"Data": "Hello"},
        "Body": {"Text": {"Data": "Hi there"}}
    }},
)

A word of warning on cost. Each unique combination of metric name and dimension values is billed as a separate CloudWatch custom metric. Tagging by campaign with a few dozen values per month is fine. Tagging by messageId or recipient is how you accidentally publish a million custom metrics and a five-figure CloudWatch bill. Keep dimensions low-cardinality.

CloudWatch alarms for bounce and complaint thresholds

The single most valuable alarm you can create on SES watches Reputation.BounceRate. Below is the CLI call that fires when the bounce rate sits above 2% for three consecutive five-minute periods - well below the 5% AWS review threshold so you have time to react.

# Create an SNS topic for paging
TOPIC_ARN=$(aws sns create-topic --name ses-alarms \
  --query TopicArn --output text)

# Bounce-rate alarm at 2%
aws cloudwatch put-metric-alarm \
  --alarm-name ses-bounce-rate-high \
  --alarm-description "SES bounce rate above 2% - AWS reviews at 5%" \
  --metric-name Reputation.BounceRate \
  --namespace AWS/SES \
  --statistic Average \
  --period 300 \
  --evaluation-periods 3 \
  --threshold 0.02 \
  --comparison-operator GreaterThanThreshold \
  --treat-missing-data notBreaching \
  --alarm-actions "$TOPIC_ARN" \
  --ok-actions "$TOPIC_ARN"

# Complaint-rate alarm at 0.05%
aws cloudwatch put-metric-alarm \
  --alarm-name ses-complaint-rate-high \
  --alarm-description "SES complaint rate above 0.05% - AWS reviews at 0.1%" \
  --metric-name Reputation.ComplaintRate \
  --namespace AWS/SES \
  --statistic Average \
  --period 300 \
  --evaluation-periods 3 \
  --threshold 0.0005 \
  --comparison-operator GreaterThanThreshold \
  --treat-missing-data notBreaching \
  --alarm-actions "$TOPIC_ARN"

If a bounce-rate alarm does fire, the account suspended recovery playbook walks through what AWS expects to see in your appeal.

A small but important detail: set --treat-missing-data notBreaching. SES does not emit reputation metrics during low-volume periods, and the default missing behaviour will treat that silence as an unknown state and flap your alarm.

For the lower-level event metrics (Bounce, Complaint, Reject), prefer count-based alarms with anomaly detection rather than fixed thresholds. A two-bounce spike means nothing if you sent 50, and everything if you sent 50,000.

A working CloudWatch dashboard for SES

The dashboard JSON below renders a four-widget board: the two reputation rates as gauges on top, a stacked area of send vs delivery vs bounce vs complaint below them, and a count strip across the bottom. Save it to dashboard.json and create the dashboard with one CLI call.

{
  "widgets": [
    {
      "type": "metric", "x": 0, "y": 0, "width": 12, "height": 6,
      "properties": {
        "title": "Reputation - Bounce rate",
        "metrics": [
          ["AWS/SES", "Reputation.BounceRate", {"label": "Bounce rate"}]
        ],
        "view": "timeSeries", "stat": "Average", "period": 300,
        "yAxis": {"left": {"min": 0, "max": 0.05}},
        "annotations": {"horizontal": [
          {"value": 0.02, "color": "#F59E0B", "label": "Warn 2%"},
          {"value": 0.05, "color": "#EF4444", "label": "AWS review 5%"}
        ]}
      }
    },
    {
      "type": "metric", "x": 12, "y": 0, "width": 12, "height": 6,
      "properties": {
        "title": "Reputation - Complaint rate",
        "metrics": [
          ["AWS/SES", "Reputation.ComplaintRate", {"label": "Complaint rate"}]
        ],
        "view": "timeSeries", "stat": "Average", "period": 300,
        "annotations": {"horizontal": [
          {"value": 0.0005, "color": "#F59E0B", "label": "Warn 0.05%"},
          {"value": 0.001,  "color": "#EF4444", "label": "AWS review 0.1%"}
        ]}
      }
    },
    {
      "type": "metric", "x": 0, "y": 6, "width": 24, "height": 6,
      "properties": {
        "title": "Send funnel",
        "metrics": [
          ["AWS/SES", "Send"],
          [".", "Delivery"],
          [".", "Bounce"],
          [".", "Complaint"],
          [".", "Reject"]
        ],
        "view": "timeSeries", "stacked": false,
        "stat": "Sum", "period": 300
      }
    }
  ]
}
aws cloudwatch put-dashboard \
  --dashboard-name ses-production \
  --dashboard-body file://dashboard.json

The dashboard is intentionally narrow. Resist the urge to add open and click rates to the same board. Those are engagement metrics and belong on a marketing dashboard - mixing them with deliverability gauges trains the on-call team to ignore the panel.

Recommended SES CloudWatch alarm thresholds vs AWS enforcement Where to set your bounce-rate alarm Alarm well below the AWS review line so you investigate first 0% 2.5% 5% 7.5% 10% 2% Your alarm 5% AWS review 10% AWS pause Bounce-rate thresholds. Source: AWS SES reputation metrics docs.

Wiring CloudWatch alerts to Slack and PagerDuty via SNS

CloudWatch alarms publish a structured JSON payload to an SNS topic, and that topic is the integration boundary for every paging destination. Slack and PagerDuty cannot consume the raw payload directly, so the production pattern is alarm → SNS → translator → destination, where the translator is either PagerDuty's native SNS integration or AWS Chatbot for Slack. AWS documents the SNS payload shape in the CloudWatch alarms guide.

Subscribe PagerDuty directly to the topic - PagerDuty's CloudWatch integration parses SNS payloads natively:

aws sns subscribe \
  --topic-arn "$TOPIC_ARN" \
  --protocol https \
  --notification-endpoint "https://events.pagerduty.com/integration/<integration-key>/enqueue"

For Slack, the cleanest path is the AWS Chatbot service, which subscribes to the same SNS topic and posts formatted alarm cards to a channel without writing any Lambda code. Configure it once in the Chatbot console, point it at your ses-alarms topic, and bounce-rate breaches will land in Slack within seconds.

If you need to fan one alarm out to multiple destinations with filtering - say, only page on Reputation.* alarms but post all others to Slack - use SNS message filtering with a JSON filter policy on each subscription. That avoids the temptation to create five different SNS topics.

Production patterns that prevent false pages

Four patterns consistently show up in SES setups that stay quiet at 3am and still catch real incidents. They mostly amount to splitting traffic classes, replacing fixed thresholds with anomaly bands where the underlying baseline is noisy, and gating page-grade alarms behind a minimum send volume so a 100% bounce rate on three messages does not wake anyone up.

  • Separate configuration sets per traffic class. One for transactional, one for marketing, one for system notifications. Bounce rates and reputation should be measured per traffic class because marketing lists naturally bounce more than transactional, and a single pooled metric hides the signal.
  • Anomaly detection on Send. A sudden drop in Send count usually means your app is broken, not that customers stopped emailing. Wrap it in a CloudWatch anomaly band rather than a fixed threshold.
  • Composite alarms for paging. Bounce-rate alarms should only page if the Send volume is non-trivial - a 100% bounce rate on three messages is noise. Use a composite alarm that ANDs the bounce-rate breach with a minimum send count.
  • Log-based alarms for rendering failures. Rendering failures often correlate with deploys. Cross-reference the CloudWatch alarm timestamp with your deploy log and you will find the bad template in seconds.

When CloudWatch is enough, and when it is not

CloudWatch is the right tool when you want to alert, automate, and integrate with the rest of AWS. It is not the right tool when you need ISP-level deliverability insight - whether Gmail or Yahoo is throttling you, which IP in the SES pool is the problem, or what your engagement looks like by mailbox provider. That data lives in Virtual Deliverability Manager, which has its own dashboard with placement and engagement metrics that CloudWatch does not surface.

A production-grade setup runs both: CloudWatch for alerting and SLO tracking, VDM for diagnostic deep-dives when CloudWatch tells you something is wrong. Treat them as complementary, not exclusive.

How Mailblast handles monitoring for you

Mailblast is a hosted management layer for your own Amazon SES account - the BYO-SES model. The CloudWatch metrics still land in your AWS account, and you keep full ownership of dashboards and alarms. What Mailblast adds on top is a sender-friendly view of the same data inside the product: open rate, click rate, unsubscribe rate, and per-link click tracking, with recipient-level event data on every campaign. You alarm in CloudWatch when the bounce rate climbs, then log into Mailblast to see exactly which list segment is bouncing. The two views answer different questions and they work better together.

FAQ

Does Amazon SES send metrics to CloudWatch automatically?

Yes. SES emits account-level metrics for Send, Delivery, Bounce, Complaint, Reject, and Reputation under the AWS/SES namespace at no extra cost. Metrics only appear once the corresponding event has occurred at least once - a brand new account with zero bounces will not show a Bounce metric in CloudWatch until the first bounce.

What bounce rate triggers an AWS warning?

AWS places your account under review at a 5% bounce rate and may pause sending at 10%. For complaints, review starts at 0.1% and pause at 0.5%. CloudWatch alarms should fire well below those thresholds - most teams alarm at 2% bounce and 0.05% complaint so they can investigate before AWS does.

Do I need a configuration set to use CloudWatch with SES?

No for account-level metrics, yes for per-campaign or per-tenant breakdowns. Account metrics arrive automatically. Configuration sets with a CloudWatch event destination let you tag events with dimensions like ses:configuration-set, ses:caller-identity, or any custom message tag, which is how you separate transactional and marketing traffic on the same SES account.

Does CloudWatch event publishing cost extra?

Yes. Account-level SES metrics are free, but CloudWatch event publishing through a configuration set is billed per custom metric and per API request under standard CloudWatch pricing. Each unique combination of metric name and dimension values counts as a separate custom metric, so high-cardinality dimensions like message ID will blow up the bill.

Should I use CloudWatch or Virtual Deliverability Manager?

Use CloudWatch for alerting, automation, and integration with the rest of your AWS stack. Use Virtual Deliverability Manager when you need ISP-level deliverability insight, engagement scoring, and per-identity recommendations that CloudWatch metrics cannot produce. They are complementary, not exclusive - run both in production.


Disclosure: Mailblast is a hosted management layer for your own Amazon SES account. CloudWatch metrics, alarms, and dashboards described above live in your AWS account; Mailblast surfaces the same delivery, bounce, and engagement signals inside the product so you can move from a CloudWatch alert to the affected campaign or list segment without leaving the dashboard.

Ready to Start Your Email Marketing Journey?

Join thousands of businesses using Mailblast to grow their audience.

← Back to Blog