Skip to main content
Technology & EngineeringEmail Services242 lines

AWS Ses

Send email at scale with Amazon SES (Simple Email Service). Use this skill when the project needs to integrate AWS SES for transactional email, bulk sending, email receiving, configuration sets, event publishing, dedicated IPs, custom MAIL FROM domains, or cost-optimized high-volume email. Covers the SES v2 API, templates, configuration sets, event destinations, and deliverability dashboard.

Quick Summary26 lines
You are an email operations specialist who integrates Amazon SES into projects. SES
is the most cost-effective option for high-volume sending, offering raw infrastructure
with full control over deliverability, reputation, and compliance.

## Key Points

- Can only send to verified addresses
- Daily limit of 200 emails, 1/second rate
- Must request production access through AWS console
- Always use configuration sets — one for transactional, one for marketing
- Enable account-level suppression list to prevent sending to known bad addresses
- Use dedicated IPs only if sending 100K+ emails/day consistently
- Set up a custom MAIL FROM domain for full authentication alignment
- Publish events to SNS for real-time processing and to Firehose for analytics
- Monitor SES reputation dashboard and set CloudWatch alarms on bounce/complaint rates
- Use SES v2 API — v1 is legacy
- Sending without a configuration set — no tracking or suppression
- Not handling SNS subscription confirmation — events silently fail

## Quick Example

```bash
npm install @aws-sdk/client-sesv2
```
skilldb get email-services-skills/aws-sesFull skill: 242 lines
Paste into your CLAUDE.md or agent config

AWS SES Email Integration

You are an email operations specialist who integrates Amazon SES into projects. SES is the most cost-effective option for high-volume sending, offering raw infrastructure with full control over deliverability, reputation, and compliance.

Core Philosophy

Infrastructure, not abstraction

SES gives you email-sending infrastructure, not a product. You build the abstractions: template management, suppression handling, analytics, and compliance. This is its strength for teams that need full control and its weakness for teams that want a turnkey solution.

Configuration sets are non-negotiable

Every email should be sent through a configuration set. They control tracking, event publishing, suppression, and sending identity. Skipping them means flying blind.

Cost scales linearly

SES charges $0.10 per 1,000 emails. At volume, this is 10-50x cheaper than Resend, SendGrid, or Postmark. But you pay in engineering time for features those services include by default.

Setup

Install

npm install @aws-sdk/client-sesv2

Initialize

import { SESv2Client, SendEmailCommand } from '@aws-sdk/client-sesv2';

const ses = new SESv2Client({
  region: process.env.AWS_REGION || 'us-east-1',
  credentials: {
    accessKeyId: process.env.AWS_ACCESS_KEY_ID,
    secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
  },
});

Key Techniques

Simple send

await ses.send(new SendEmailCommand({
  FromEmailAddress: 'App <noreply@yourdomain.com>',
  Destination: {
    ToAddresses: ['user@example.com'],
  },
  Content: {
    Simple: {
      Subject: { Data: 'Your password reset link' },
      Body: {
        Html: { Data: '<p>Click <a href="...">here</a></p>' },
        Text: { Data: 'Reset: https://...' },
      },
    },
  },
  ConfigurationSetName: 'transactional',
  EmailTags: [
    { Name: 'category', Value: 'security' },
    { Name: 'workflow', Value: 'password_reset' },
  ],
}));

Template send

Create templates in SES, then send with template data.

import { CreateEmailTemplateCommand } from '@aws-sdk/client-sesv2';

// Create template (one-time)
await ses.send(new CreateEmailTemplateCommand({
  TemplateName: 'PasswordReset',
  TemplateContent: {
    Subject: 'Reset your password',
    Html: '<p>Hi {{name}}, click <a href="{{resetUrl}}">here</a> to reset.</p>',
    Text: 'Hi {{name}}, reset your password: {{resetUrl}}',
  },
}));

// Send with template
import { SendEmailCommand } from '@aws-sdk/client-sesv2';

await ses.send(new SendEmailCommand({
  FromEmailAddress: 'App <noreply@yourdomain.com>',
  Destination: { ToAddresses: ['user@example.com'] },
  Content: {
    Template: {
      TemplateName: 'PasswordReset',
      TemplateData: JSON.stringify({ name: 'Alice', resetUrl: 'https://...' }),
    },
  },
  ConfigurationSetName: 'transactional',
}));

Bulk template send

Send personalized email to many recipients efficiently.

import { SendBulkEmailCommand } from '@aws-sdk/client-sesv2';

await ses.send(new SendBulkEmailCommand({
  FromEmailAddress: 'App <noreply@yourdomain.com>',
  DefaultContent: {
    Template: {
      TemplateName: 'WeeklySummary',
      TemplateData: JSON.stringify({ defaultGreeting: 'Hello' }),
    },
  },
  BulkEmailEntries: [
    {
      Destination: { ToAddresses: ['user1@example.com'] },
      ReplacementEmailContent: {
        ReplacementTemplate: {
          ReplacementTemplateData: JSON.stringify({ name: 'Alice' }),
        },
      },
    },
    // ... more entries (up to 50 per call)
  ],
  ConfigurationSetName: 'bulk',
}));

Configuration sets

import { CreateConfigurationSetCommand } from '@aws-sdk/client-sesv2';

await ses.send(new CreateConfigurationSetCommand({
  ConfigurationSetName: 'transactional',
  TrackingOptions: { CustomRedirectDomain: 'track.yourdomain.com' },
  DeliveryOptions: { TlsPolicy: 'REQUIRE' },
  ReputationOptions: { ReputationMetricsEnabled: true },
  SendingOptions: { SendingEnabled: true },
  SuppressionOptions: {
    SuppressedReasons: ['BOUNCE', 'COMPLAINT'],
  },
}));

Event destinations (SNS, CloudWatch, Firehose, EventBridge)

import { CreateConfigurationSetEventDestinationCommand } from '@aws-sdk/client-sesv2';

await ses.send(new CreateConfigurationSetEventDestinationCommand({
  ConfigurationSetName: 'transactional',
  EventDestinationName: 'sns-events',
  EventDestination: {
    Enabled: true,
    MatchingEventTypes: ['SEND', 'DELIVERY', 'BOUNCE', 'COMPLAINT', 'REJECT'],
    SnsDestination: { TopicArn: 'arn:aws:sns:us-east-1:123456:ses-events' },
  },
}));

Event Processing

SES publishes events to SNS, CloudWatch, Kinesis Firehose, or EventBridge.

EventAction
DELIVERYMark delivered
BOUNCE (Permanent)Suppress address
BOUNCE (Transient)Log, monitor
COMPLAINTSuppress immediately
REJECTLog — SES rejected before sending
RENDERING_FAILUREFix template — data mismatch

SNS webhook handler

export async function POST(req: Request) {
  const body = await req.json();

  // Handle SNS subscription confirmation
  if (body.Type === 'SubscriptionConfirmation') {
    await fetch(body.SubscribeURL);
    return new Response('OK');
  }

  const message = JSON.parse(body.Message);
  const eventType = message.eventType;
  const recipient = message.mail?.destination?.[0];

  switch (eventType) {
    case 'Bounce':
      if (message.bounce.bounceType === 'Permanent') {
        await suppressAddress(recipient, 'hard_bounce');
      }
      break;
    case 'Complaint':
      await suppressAddress(recipient, 'complaint');
      break;
  }

  return new Response('OK');
}

Sandbox vs Production

New SES accounts start in sandbox mode:

  • Can only send to verified addresses
  • Daily limit of 200 emails, 1/second rate
  • Must request production access through AWS console

Request production access with: sending domain verified, SPF/DKIM configured, bounce/complaint handling implemented, and a clear use case description.

Best Practices

  • Always use configuration sets — one for transactional, one for marketing
  • Enable account-level suppression list to prevent sending to known bad addresses
  • Use dedicated IPs only if sending 100K+ emails/day consistently
  • Set up a custom MAIL FROM domain for full authentication alignment
  • Publish events to SNS for real-time processing and to Firehose for analytics
  • Monitor SES reputation dashboard and set CloudWatch alarms on bounce/complaint rates
  • Use SES v2 API — v1 is legacy

Anti-Patterns

  • Sending without a configuration set — no tracking or suppression
  • Not handling SNS subscription confirmation — events silently fail
  • Using shared IPs for both transactional and marketing at high volume
  • Ignoring the SES sandbox — production access request takes 24-48 hours
  • Not implementing exponential backoff on throttling errors
  • Storing AWS credentials in application code instead of using IAM roles

Install this skill directly: skilldb add email-services-skills

Get CLI access →

Related Skills

Brevo

Send transactional and marketing email with Brevo (formerly Sendinblue). Use this skill when the project needs to integrate Brevo for transactional email, marketing campaigns, SMS, WhatsApp, CRM contacts, automation workflows, or multi-channel messaging. Covers the Brevo API v3, transactional templates, contacts, campaigns, automation, and webhook processing.

Email Services203L

Courier

Send transactional notifications including email with Courier. Use this skill when the project needs to integrate Courier for multi-channel notification orchestration, email delivery through multiple providers, template management with a visual designer, routing rules, user preference management, bulk sends, and delivery tracking. Covers the Send API, automations, user preferences, and provider failover.

Email Services317L

Customerio

Send transactional and marketing email with Customer.io. Use this skill when the project needs to integrate Customer.io for behavioral email automation, transactional messages via the Transactional API, event-driven campaigns, segment-based messaging, webhook handling, and customer data management. Covers the Track API, Transactional API, campaign workflows, and data-driven personalization.

Email Services275L

Email Deliverability

Optimize email deliverability across any provider. Use this skill when the project needs to improve inbox placement, configure SPF, DKIM, and DMARC, manage sender reputation, handle bounces and complaints, warm new IPs or domains, or debug delivery issues. Provider-agnostic best practices for getting email into the inbox.

Email Services182L

Loops

Send transactional and marketing email with Loops. Use this skill when the project needs to integrate Loops for event-driven email, automated sequences, transactional sends, contact management, or product-led email marketing. Covers the Loops API, events, transactional sends, contacts, and audience management.

Email Services177L

Mailchimp Transactional

Send transactional email with Mailchimp Transactional (formerly Mandrill). Use this skill when the project needs to integrate Mandrill/Mailchimp Transactional for reliable transactional email, template-based sending, metadata tracking, subaccounts, webhook processing, or inbound email routing. Covers the Mandrill API, templates, subaccounts, metadata, and webhooks.

Email Services200L