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.
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 linesAWS 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.
| Event | Action |
|---|---|
DELIVERY | Mark delivered |
BOUNCE (Permanent) | Suppress address |
BOUNCE (Transient) | Log, monitor |
COMPLAINT | Suppress immediately |
REJECT | Log — SES rejected before sending |
RENDERING_FAILURE | Fix 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
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.
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.
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 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.
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.
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.