All articles

How to Separate Transactional and Marketing Email in Amazon SES Using Configuration Sets and Dedicated IP Pools

2nd July, 2026

How to Separate Transactional and Marketing Email in Amazon SES Using Configuration Sets and Dedicated IP Pools

Why Mixing Email Streams in One SES Identity Is a Hidden Reputation Risk

Most teams that build on Amazon SES start the same way: one verified domain, one set of credentials, and every type of email going out through the same path. Password resets, order confirmations, weekly newsletters, re-engagement campaigns, and product announcements all share a single sending lane. This approach is simple to set up and works well enough at low volumes, but it creates a structural reputation problem that tends to surface at exactly the wrong moment.

SES reputation operates at multiple levels simultaneously, and damage in one email stream can suppress delivery in another. A single marketing campaign sent to a stale or poorly maintained list can push your account-level bounce or complaint rate above the thresholds SES uses to take enforcement action, affecting every email your application sends, including the password-reset and payment-confirmation messages your users genuinely need. This article shows you exactly how to architect SES so that each email stream carries its own reputation, monitored and alarmed independently, so a problem in one lane never reaches another.

How a Single Campaign Can Suppress Your Password Resets

Consider a SaaS product that sends order confirmations, a weekly digest newsletter, and system alerts. All three go through the same configuration set and the same IP addresses. A marketing manager schedules a re-engagement campaign to a list that has not been cleaned in eighteen months. The campaign generates a spike in hard bounces and complaint reports. SES counts those events at the account level, and excessive bounces and complaints can cause Amazon SES to place your account under review or, in severe cases, pause sending entirely. The order confirmations going out in the same hour now carry that damaged reputation, and some will not reach the inbox.

The problem is compounded by the global suppression list. When any SES customer sends an email that results in a hard bounce, SES adds that address to the global suppression list, which applies across all SES customers. An address can remain on the list for up to fourteen days, with the duration increasing each time the same address produces a hard bounce. Your marketing campaign's bounces can, in effect, pollute the pool of addresses you are able to reach with critical transactional mail.

The SES Reputation Model: Account, Domain, and IP Levels

Understanding the three layers of SES reputation clarifies why isolation matters. At the account level, SES tracks your overall bounce rate and complaint rate as aggregated metrics. These are the figures that determine whether your account remains in good standing and retains its sending quota. At the domain level, mailbox providers such as Gmail and Outlook maintain their own reputation signals tied to the domain in your From header and the domain used to sign your DKIM records. At the IP level, each sending address builds a reputation based on historical volume, engagement, and complaint signals.

When you use shared IP addresses, your sender reputation is determined at the account level and cannot be isolated between streams. With dedicated IP addresses, whether standard or managed, you can isolate your sender reputation for different components within your email programme by creating dedicated IP pools: groups of dedicated IP addresses used for sending specific types of email. This distinction is the architectural foundation of stream isolation.

Step 1: Design Your Stream Taxonomy

Before touching the AWS console or CLI, decide how many sending streams you actually need. For most SaaS products, three streams cover every use case cleanly. The first is transactional email: anything a user explicitly triggers or expects to receive, including password resets, purchase confirmations, account alerts, and two-factor authentication codes. The second is marketing email: newsletters, promotional campaigns, product announcements, and re-engagement sequences. The third, which many teams overlook, is bulk notification email: system-generated messages that are neither user-triggered nor promotional, such as weekly usage digests, scheduled reports, or platform status summaries.

Keeping these three categories distinct at the architecture level makes every downstream decision easier. Each stream will get its own configuration set, its own bounce and complaint tracking, and its own suppression behaviour. The taxonomy you define here becomes the naming convention for every resource you create.

Step 2: Create a Configuration Set Per Stream

A configuration set is a named group of rules that you apply when sending email. When you apply a configuration set to an email, all of the rules in that configuration set are applied to that message. By assigning each stream its own configuration set, you gain independent control over event publishing, IP pool assignment, and suppression behaviour for every email type.

Create three configuration sets using the AWS CLI with the SES v2 API. The names below follow a clear convention that makes IAM policies, CloudWatch dimensions, and alarm names readable at a glance.

aws sesv2 create-configuration-set \
  --configuration-set-name transactional

aws sesv2 create-configuration-set \
  --configuration-set-name marketing

aws sesv2 create-configuration-set \
  --configuration-set-name bulk-notifications

Once the configuration sets exist, attach event destinations to each one. SES can publish email sending events to Amazon CloudWatch, Amazon Data Firehose, Amazon SNS, or Amazon EventBridge. Trackable event types include sends, deliveries, opens, clicks, bounces, complaints, rejections, rendering failures, and delivery delays. For stream-level monitoring, a CloudWatch destination on each configuration set gives you the metrics you need to build per-stream dashboards and alarms. An SNS destination on the transactional configuration set provides real-time notifications that you can route to an on-call channel when something goes wrong.

# Add a CloudWatch event destination to the transactional configuration set
aws sesv2 create-configuration-set-event-destination \
  --configuration-set-name transactional \
  --event-destination-name transactional-cw \
  --event-destination '{
    "Enabled": true,
    "MatchingEventTypes": ["SEND","DELIVERY","BOUNCE","COMPLAINT","REJECT"],
    "CloudWatchDestination": {
      "DimensionConfigurations": [
        {
          "DimensionName": "stream",
          "DimensionValueSource": "MESSAGE_TAG",
          "DefaultDimensionValue": "transactional"
        }
      ]
    }
  }'

Repeat this for the marketing and bulk-notifications configuration sets, adjusting the dimension default value accordingly. The stream dimension lets you filter CloudWatch metrics by sending lane within a single namespace.

In Python using the boto3 SDK, the equivalent looks like this:

import boto3

ses = boto3.client('sesv2', region_name='eu-west-1')

ses.create_configuration_set_event_destination(
    ConfigurationSetName='marketing',
    EventDestinationName='marketing-sns',
    EventDestination={
        'Enabled': True,
        'MatchingEventTypes': ['BOUNCE', 'COMPLAINT'],
        'SnsDestination': {
            'TopicArn': 'arn:aws:sns:eu-west-1:123456789012:marketing-bounce-complaint'
        }
    }
)

When sending email, pass the configuration set name in the request. Using the SES v2 API, include it as the ConfigurationSetName parameter. You can also set a default configuration set on a verified identity so that emails sent from that identity always use the correct configuration set even if the application code does not specify one explicitly. This provides a useful safety net, but it should not replace explicit configuration set assignment in your sending code.

Step 3: Choose the Right IP Strategy Per Stream

Not every stream needs dedicated IP addresses, and provisioning dedicated IPs for low-volume sending can actually harm your reputation by keeping the addresses under-warmed. Shared IP addresses are best suited to senders whose volume does not follow a predictable pattern and who send at relatively low scale. An IP address with a consistent history of sending email has a stronger reputation than one that suddenly starts sending large volumes with no prior history.

For most SaaS products, the right strategy is: transactional email on the SES shared pool, which offers predictable low volume and benefits from AWS's careful management of shared IP reputation; marketing email on a dedicated or managed dedicated pool, which provides isolation for high-volume, variable-content sending; and bulk notifications on either the shared pool or a separate dedicated pool, depending on volume.

SES offers two dedicated IP options. Standard dedicated IPs require a service quota increase request submitted through the AWS Support Centre, after which you manage warmup and pool membership manually. Managed dedicated IPs are allocated automatically when you create a managed pool: SES determines how many dedicated IPs you require based on your sending patterns, provisions them for you, and scales them as your requirements change. Managed dedicated IPs also handle the warmup process automatically, which removes the most significant operational overhead of running dedicated addresses. For most teams, managed dedicated IPs are the better starting point.

Step 4: Create Dedicated IP Pools and Assign Configuration Sets

Create the managed dedicated pool for your marketing stream from the console or via the CLI:

aws sesv2 create-dedicated-ip-pool \
  --pool-name marketing-pool \
  --scaling-mode MANAGED

Then associate the marketing configuration set with that pool:

aws sesv2 put-configuration-set-sending-options \
  --configuration-set-name marketing \
  --sending-pool-name marketing-pool

Leave the transactional configuration set without a pool assignment. When no dedicated pool is assigned, SES routes email through the shared pool, which is appropriate for low-volume transactional sending. If your transactional volume is high enough to justify dedicated IPs, create a separate standard pool and assign it to the transactional configuration set, keeping it completely separate from the marketing pool.

When you assign a dedicated IP pool to a configuration set, emails that use that configuration set are sent only through the IP addresses that belong to that pool. This means a reputation problem building on your marketing pool has no path to affect your transactional sending.

One important point about the ses-default-dedicated-pool: this pool contains all dedicated IP addresses in your account that do not already belong to a named pool. If you have dedicated IPs and do not explicitly assign your configuration sets to a named pool, those emails may be routed through this default pool, which undermines isolation. Always assign configuration sets to an explicitly named pool or to the shared pool. Never leave the assignment to chance.

Step 5: Configure Suppression Lists Per Stream

SES maintains two suppression mechanisms that interact with each other. The global suppression list is managed by SES across all customers: when any customer's email results in a hard bounce, that address is added to the global list, and SES will not attempt delivery from any account during the suppression period. The account-level suppression list is yours to manage, and it automatically adds addresses that produce hard bounces or complaints depending on which suppression reasons you enable.

The critical feature for stream isolation is configuration set-level suppression overrides. This allows you to apply customised suppression settings to individual email streams. For example, your account-level suppression list might suppress on both bounces and complaints, but your transactional configuration set can override that to suppress on bounces only, ensuring that a user who marked a marketing email as spam can still receive a password-reset email.

Configure the override via the CLI:

# Transactional stream: suppress on hard bounces only, allow complaint-listed addresses
aws sesv2 put-configuration-set-suppression-options \
  --configuration-set-name transactional \
  --suppressed-reasons BOUNCE

# Marketing stream: suppress on both bounces and complaints
aws sesv2 put-configuration-set-suppression-options \
  --configuration-set-name marketing \
  --suppressed-reasons BOUNCE COMPLAINT

Note that configuration set-level suppression is not a separate list in storage; it is a mechanism that overrides how your account-level suppression list is applied for emails sent using that configuration set. Also be aware that these override settings only take effect when you send using the SES v2 API. If any part of your application still sends via the v1 API, the configuration set suppression override is ignored and account-level settings apply.

Step 6: Set Independent CloudWatch Alarms Per Stream

With separate CloudWatch metric dimensions per stream, you can set alarm thresholds that reflect the different risk tolerance of each email type. The thresholds below are designed to give you actionable early warning well before SES's own enforcement boundaries are approached. For reference, SES places accounts under review when the bounce rate reaches 5% and may pause sending at 10%; for complaints, the review trigger is 0.1% and the pause threshold is 0.5%. Your alarms should fire significantly before those levels are reached.

For transactional email, alarm at a bounce rate above 1% and a complaint rate above 0.05%. These conservative thresholds give you early warning well below the SES account-level review triggers. For marketing email, alarm at a bounce rate above 2% and a complaint rate above 0.08%. This still provides substantial headroom below the 0.1% complaint rate that triggers an automatic account review. For bulk notifications, set thresholds similar to those for marketing, adjusting slightly based on whether recipients have given explicit opt-in consent.

# Example: Transactional bounce rate alarm
aws cloudwatch put-metric-alarm \
  --alarm-name transactional-bounce-rate-high \
  --namespace AWS/SES \
  --metric-name Reputation.BounceRate \
  --dimensions Name=stream,Value=transactional \
  --statistic Average \
  --period 300 \
  --threshold 0.01 \
  --comparison-operator GreaterThanThreshold \
  --evaluation-periods 2 \
  --alarm-actions arn:aws:sns:eu-west-1:123456789012:ops-alerts \
  --treat-missing-data notBreaching

Route transactional alarms to your engineering on-call channel with high urgency. Route marketing alarms to a deliverability channel where the team can investigate without waking anyone at 2 am.

Step 7: Monitor Both Streams in Real Time

CloudWatch alarms tell you when a threshold has been crossed. Real-time monitoring tells you the trajectory before it gets there. Because SES aggregates metrics rather than producing per-transaction logs natively, your monitoring layer needs to consume the event data published by your configuration set destinations and surface it as stream-level rates over meaningful time windows.

SES captures information on sends, deliveries, opens, clicks, bounces, complaints, and rejections. This data can be delivered to Amazon SNS for real-time notifications or streamed via Amazon Data Firehose to Amazon S3 for long-term storage and analysis. A Data Firehose destination on each configuration set, writing to a stream-partitioned S3 bucket, provides the raw event log you need for post-incident analysis and trend reporting.

A dedicated SES monitoring tool such as SES Monitor can surface per-stream bounce rates, complaint rates, and domain-level reputation signals in a single dashboard without requiring you to build and maintain the metrics pipeline yourself. The advantage of stream-level dashboards is that you can spot a gradual climb in marketing complaint rates over several days and act before it reaches an alarm threshold, rather than reacting after the fact. This kind of early visibility is particularly valuable for protecting transactional deliverability: by the time a transactional alarm fires, some users have already missed critical emails.

Practical Example: A SaaS Product Fully Wired Up

Pulling the steps together, here is the complete architecture for a SaaS product with three email streams. The product sends order confirmation and password-reset emails (transactional), a weekly product digest (bulk notifications), and promotional campaign emails (marketing).

Three configuration sets: transactional, bulk-notifications, and marketing. One named IP pool: marketing-pool (managed dedicated), with the marketing configuration set assigned to it. The transactional and bulk-notifications configuration sets use the SES shared pool. Each configuration set has a CloudWatch event destination publishing bounce and complaint events with a stream dimension. The marketing configuration set also has an SNS event destination for real-time complaint notifications. Suppression overrides: transactional suppresses on bounces only; marketing and bulk-notifications suppress on both bounces and complaints.

In application code, every call to the SES v2 send_email API specifies the correct configuration set name:

import boto3

ses = boto3.client('sesv2', region_name='eu-west-1')

# Sending a transactional email
ses.send_email(
    FromEmailAddress='noreply@example.com',
    Destination={'ToAddresses': ['user@customer.com']},
    Content={
        'Simple': {
            'Subject': {'Data': 'Your order has been confirmed'},
            'Body': {'Text': {'Data': 'Order #12345 is confirmed.'}}
        }
    },
    ConfigurationSetName='transactional'
)

The same pattern applies to marketing sends, with ConfigurationSetName='marketing'. Never omit the configuration set name in production sending code. A missing configuration set assignment means SES may route the email through the default pool and apply account-level suppression rules, bypassing all the isolation you have built.

Common Mistakes to Avoid

The most damaging mistake is using a single catch-all configuration set for all email types. This is equivalent to having no isolation at all, and it is often the result of teams adding configuration sets as an afterthought for event logging rather than treating them as the architectural boundary they are meant to be.

The second common mistake is misconfiguring Return-Path domains across IP pools. The Return-Path header determines where bounce notifications are sent and is the domain used to establish SPF alignment for DMARC. If your transactional and marketing streams use different subdomains in the Return-Path, ensure that each subdomain has its own SPF record and that the DKIM signatures on each stream align to the correct domain. A mismatch will cause DMARC failures that damage domain reputation regardless of how well you have isolated your IP pools.

A third mistake is failing to set a default configuration set on your verified identity as a fallback. Any application component that sends through SES without specifying a configuration set will bypass your monitoring and suppression overrides entirely. Setting a default configuration set on the identity catches these cases, though it is not a substitute for explicit assignment in your sending code.

Pre-Launch Checklist

Before enabling stream isolation in production, verify five things. First, every email type in your codebase specifies the correct configuration set name in every SES v2 API call. Second, your marketing and any high-volume configuration sets are assigned to a named dedicated IP pool, and your transactional configuration set is either unassigned (shared pool) or assigned to a clearly named dedicated pool. Third, each configuration set has at least one CloudWatch event destination publishing bounce and complaint events with a distinguishing stream dimension. Fourth, suppression overrides are set correctly per stream, with the transactional stream configured to suppress on bounces only if your product requires complaint-listed addresses to still receive critical emails. Fifth, independent CloudWatch alarms exist for each stream with thresholds appropriate to that stream's risk level, routing to the correct notification channels.

Stream isolation is not a one-time setup task. As your sending volume grows and your product adds new email types, revisit the taxonomy and confirm that new sends are routed to the correct configuration set. A reputation problem that takes weeks to build can be avoided entirely by catching the early signal on one stream before it escalates to the account level. The architecture described here gives you the visibility to do exactly that.

Start protecting your SES reputation today

Connect your AWS SES account in a couple of minutes and get real-time bounce and complaint alerts before a problem becomes a suspension.

No credit card required · 2-minute setup · Cancel anytime