Wraps Logo
Production Guide

AWS SES Production Architecture Guide

Everything you need to deploy SES at scale: dedicated IPs, bounce handling, rate limiting, and the patterns that protect your sender reputation—and keep your emails out of spam folders.

15 min readLast updated January 2026Wraps Team

The Production SES Stack

Amazon SES is deceptively simple to start with—verify a domain, call the API, emails flow. But production deployments need supporting infrastructure: EventBridge routes events, SQS ensures durability, Lambda processes data, and CloudWatch monitors health. Without these, you're flying blind when deliverability drops.

Here's what a production-ready SES architecture looks like—and what Wraps deploys to your AWS account:

Production SES Architecture (What Wraps Deploys)

Your application sends through SES using the @wraps.dev/email SDK, which routes emails through your configured IP pool. All 10 SES event types flow to EventBridge for processing, enabling real-time tracking and reputation monitoring. The configuration set wraps-email-tracking ties it all together.

What Wraps Deploys
One command (wraps email init) deploys this entire architecture to your AWS account: IAM roles with OIDC authentication, SES configuration set with all 10 event types, EventBridge rules, SQS queues with DLQ, Lambda processor, and DynamoDB for event history. All tagged with ManagedBy: wraps-cli.

Before you start: Make sure your domain has proper email authentication configured. Check your DMARC policy and verify your DNS records are correct before deploying production infrastructure.

Dedicated IPs: When & Why

SES offers three IP strategies with distinct economics. Shared IPs work for most startups, but high-volume senders need dedicated IPs to isolate their reputation. On shared IPs, if another sender gets flagged for spam, your deliverability suffers too—even if you did nothing wrong.

StrategyCostBest ForWarmup Required
Shared IPsFree<100K emails/day, variable volumeNone
Dedicated Standard$24.95/IP/monthConsistent high volume45 days auto-warmup
Dedicated Managed$15/mo + tieredVariable volume, multiple ISPsAutomatic per-ISP

See our pricing page for full cost breakdowns including Wraps platform features.

IP Warming Reality Check
New dedicated IPs typically need consistent daily volume per major ISP to maintain reputation—AWS recommends ramping gradually over 45 days. Drop below your established volume and the IP cools off, requiring re-warming. Managed IPs solve this by auto-routing overflow through shared pools.

Warming Schedule

Standard dedicated IPs warm automatically over 45 days. Here's the typical progression:

WeekDaily Volume per ISPTotal Daily
Week 150-200~1,000
Week 2200-1,000~4,000
Week 31,000-5,000~20,000
Week 45,000-20,000~80,000
Week 5-620,000-50,000~200,000

Configuration Set Architecture

Configuration sets let you separate email types so they don't affect each other's reputation. A promotional campaign with high unsubscribes won't hurt your password reset deliverability. Each set controls its own event tracking, IP routing, and suppression behavior. Large deployments use 3-6 configuration sets for different email types.

Configuration Set → IP Pool Mapping

Wraps Configuration Set
Wraps creates a single configuration set wraps-email-tracking that captures all 10 event types. It includes: EventBridge destination for event routing, bounce & complaint suppression at the config set level, optional TLS enforcement, and optional custom tracking domain for branded click/open tracking URLs.
textWraps configuration set structure
├── wraps-email-tracking       # Wraps default config set
│   └── Event Destination: EventBridge (all 10 event types)
│   └── Suppression: Bounces + Complaints (config set level)
│   └── TLS: Required (optional)
│   └── Tracking Domain: track.yourdomain.com (optional)
│
# For advanced use cases, you can create additional sets:
├── transactional-critical    # Password resets, 2FA codes
│   └── IP Pool: dedicated-transactional
│
└── marketing-campaigns       # Newsletters, promotions
    └── IP Pool: dedicated-marketing
Suppression Override Gotcha
Configuration set suppression settings are overrides, not separate lists. Setting "disable suppression" doesn't give you a clean slate—it bypasses your entire account suppression list, potentially sending to known-bad addresses.

Event Processing Pipeline

AWS pauses sending at 10% bounce rate or 0.5% complaint rate. Cross these thresholds and your account goes under review—emails stop flowing while you scramble to fix it. A robust event processing pipeline catches problems early through real-time analysis and automatic suppression.

Event Processing Pipeline (Wraps Architecture)

Wraps Event Processing
Wraps captures all 10 SES event types: SEND, DELIVERY, OPEN, CLICK, BOUNCE, COMPLAINT, REJECT, RENDERING_FAILURE, DELIVERY_DELAY, and SUBSCRIPTION. Events are stored in DynamoDB with configurable retention (default 90 days) and automatic TTL cleanup. Suppression is handled at the configuration set level—bounced and complained addresses are automatically blocked.
MetricTargetWarning AlertCritical AlertAWS Review
Bounce Rate<2%2%4%5%
Complaint Rate<0.05%0.05%0.08%0.1%

How Wraps Processes Events

The Lambda processor receives batches of 10 events from SQS, parses the EventBridge envelope, and stores normalized data in DynamoDB. Here's the key processing logic:

javascriptevent-processor.js
// Wraps Lambda Event Processor (simplified)
export async function handler(event) {
  const results = [];

  for (const record of event.Records) {
    try {
      const eventBridgeEvent = JSON.parse(record.body);
      const sesEvent = eventBridgeEvent.detail;

      // Extract common fields
      const messageId = sesEvent.mail.messageId;
      const eventType = sesEvent.eventType;
      const timestamp = new Date(sesEvent.mail.timestamp).getTime();

      // Normalize event data based on type
      const eventData = {
        messageId,
        sentAt: timestamp,
        accountId: process.env.AWS_ACCOUNT_ID,
        from: sesEvent.mail.source,
        to: sesEvent.mail.destination,
        subject: sesEvent.mail.commonHeaders?.subject,
        eventType: normalizeEventType(sesEvent),
        eventData: sesEvent,
        expiresAt: computeTTL(timestamp, RETENTION_DAYS),
      };

      await dynamodb.put({
        TableName: 'wraps-email-history',
        Item: eventData,
      });

      results.push({ itemIdentifier: record.messageId });
    } catch (error) {
      // Failed items go to DLQ after 3 retries
      console.error('Failed to process:', error);
    }
  }

  return { batchItemFailures: results };
}

Rate Limiting Architecture

SES quotas use rolling 24-hour windows—your limit resets continuously, not at midnight. Default production quotas start around 50,000 emails/day and 14 emails/second. Without a buffer, a sudden spike in signups exhausts your quota and delays critical emails like password resets. Queue-based architectures absorb bursts so SES processes them at a sustainable rate.

Queue-Based Rate Limiting

SQS + Lambda Configuration

yamlserverless.yml
functions:
  emailSender:
    handler: sender.handler
    reservedConcurrency: 5  # SES rate / batch size / safety factor
    events:
      - sqs:
          arn: !GetAtt EmailQueue.Arn
          batchSize: 10
          functionResponseTypes:
            - ReportBatchItemFailures

resources:
  Resources:
    EmailQueue:
      Type: AWS::SQS::Queue
      Properties:
        VisibilityTimeout: 180  # 6x Lambda timeout
        MessageRetentionPeriod: 1209600  # 14 days
        RedrivePolicy:
          deadLetterTargetArn: !GetAtt EmailDLQ.Arn
          maxReceiveCount: 5
Concurrency Math
If your SES rate limit is 14/sec and you process 10 emails per Lambda invocation, set reservedConcurrency to (14 / 10) * 0.8 ≈ 1-2. The 0.8 factor prevents hitting limits during retry bursts.

CloudWatch Monitoring

Essential alarms catch reputation problems before AWS intervenes. These Terraform configurations create early-warning alerts:

hclses-alarms.tf
resource "aws_cloudwatch_metric_alarm" "ses_bounce_rate" {
  alarm_name          = "ses-bounce-rate-warning"
  comparison_operator = "GreaterThanOrEqualToThreshold"
  evaluation_periods  = 1
  metric_name         = "Reputation.BounceRate"
  namespace           = "AWS/SES"
  period              = 300
  statistic           = "Maximum"
  threshold           = 0.02  # 2% - warns before AWS review (5%)
  alarm_actions       = [aws_sns_topic.alerts.arn]
  treat_missing_data  = "notBreaching"
}

resource "aws_cloudwatch_metric_alarm" "ses_complaint_rate" {
  alarm_name          = "ses-complaint-rate-warning"
  comparison_operator = "GreaterThanOrEqualToThreshold"
  evaluation_periods  = 1
  metric_name         = "Reputation.ComplaintRate"
  namespace           = "AWS/SES"
  period              = 300
  statistic           = "Maximum"
  threshold           = 0.0005  # 0.05% - warns before AWS (0.1%) and Gmail (0.3%)
  alarm_actions       = [aws_sns_topic.alerts.arn]
  treat_missing_data  = "notBreaching"
}
Or configure with Wraps CLI
Skip the Terraform—enable these same alerts in one command: wraps email upgrade → "Enable reputation alerts". Wraps deploys 5 CloudWatch alarms with thresholds that warn before AWS takes action: bounce rate at 2%/4% (vs AWS 5%/10%), complaint rate at 0.05%/0.08% (vs AWS 0.1%/0.5%), plus DLQ monitoring. You'll get email notifications when your reputation needs attention.

Dashboard Metrics

WidgetMetricsPurpose
ReputationBounceRate, ComplaintRateAccount health at a glance
Delivery FunnelSend, Delivery, Bounce, ComplaintConversion through stages
ThroughputSend rate over timeDetect throttling patterns
ISP BreakdownVDM metrics by domainPer-provider performance

Common Mistakes That Kill Deliverability

These mistakes can wreck months of careful reputation building. We've seen them tank otherwise healthy sender accounts:

Related: Why your DMARC policy is useless covers SPF/DKIM alignment in detail, and our SPF configuration guide explains the include mechanism.

Missing Custom MAIL FROM

SPF alignment fails without it, causing DMARC failures even with valid SPF records. See our DMARC guide for proper alignment.

Testing with real addresses

Use bounce@simulator.amazonses.com for testing—real bounces hurt your reputation. Learn proper testing in our sandbox guide.

Ignoring soft bounces

Repeatedly sending to soft-bouncing addresses signals poor list hygiene to ISPs.

Sudden volume spikes

ISPs interpret sudden increases as spam behavior. Scale gradually—doubling overnight is a red flag.

Region confusion

SES credentials, quotas, and domains are region-specific. Verified in us-east-1 ≠ verified in eu-west-1.

Continue Learning

Skip the infrastructure headaches

wraps email init deploys 7 AWS resources to your account: IAM roles, SES configuration, EventBridge rules, SQS queues, Lambda processor, and DynamoDB storage. All wired together and ready to send.

You own everything—no vendor lock-in. Pay only AWS pricing (~$0.10 per 1,000 emails). Don't like it? wraps email destroy removes everything cleanly.

You'll need: AWS credentials and a verified domain. That's it.