Migrate from AWS SES
Step-by-step guide to moving from Amazon Simple Email Service (SES) to Sendbound. SDK replacement, IAM cleanup, SNS notification migration, and SMTP relay removal.
This guide covers everything specific to migrating from Amazon Simple Email Service (SES).
Before starting, read the general migration guide for the parallel-run strategy and DNS playbook.
What's different
| AWS SES | Sendbound | |
|---|---|---|
| Auth | IAM credentials (access key + secret) or SMTP password | Bearer token (sk_live_xxx) |
| SDK | @aws-sdk/client-ses or SMTP relay | Plain fetch — no SDK |
| Region config | Required (us-east-1, etc.) | Not required |
| Templates | SES Email Templates (limited) or app-side rendering | Full template editor with live preview |
| Event notifications | SNS topics → SQS / Lambda | HTTPS webhook endpoint |
| Suppression | Account-level suppression list | Per-project suppression list |
| Sandbox mode | SES sandbox restricts recipients | No sandbox — verify domain and send freely |
If you're on SES sandbox, you're restricted to verified email addresses only. With Sendbound you can send to any address as soon as your domain is verified — no sandbox to escape.
Export from AWS SES
Document verified identities
aws ses list-identities --identity-type Domain
aws ses list-identities --identity-type EmailAddressNote which domains and addresses you send from — you'll verify the same domains in Sendbound.
Export SES templates
# List all templates
aws ses list-templates
# Export each template
aws ses get-template --template-name YOUR_TEMPLATE_NAMEThe response includes HtmlPart and TextPart — save these as HTML files to import into Sendbound's template editor.
Export the account-level suppression list
aws sesv2 list-suppressed-destinations \
--query 'SuppressedDestinationSummaries[*].EmailAddress' \
--output text \
> suppressions.txtDocument SNS notification configuration
For each verified identity:
aws ses get-identity-notification-attributes \
--identities yourdomain.comNote the SNS topic ARNs for bounces, complaints, and deliveries. You'll replace these with Sendbound webhooks.
API migration
Sending a transactional email
import { SESClient, SendEmailCommand } from '@aws-sdk/client-ses';
const ses = new SESClient({ region: 'us-east-1' });
await ses.send(new SendEmailCommand({
Source: 'hello@yourdomain.com',
Destination: {
ToAddresses: ['user@example.com'],
},
Message: {
Subject: { Data: 'Welcome', Charset: 'UTF-8' },
Body: {
Html: { Data: '<p>Welcome aboard!</p>', Charset: 'UTF-8' },
Text: { Data: 'Welcome aboard!', Charset: 'UTF-8' },
},
},
}));import nodemailer from 'nodemailer';
const transporter = nodemailer.createTransport({
host: 'email-smtp.us-east-1.amazonaws.com',
port: 587,
auth: {
user: process.env.SES_SMTP_USERNAME,
pass: process.env.SES_SMTP_PASSWORD,
},
});
await transporter.sendMail({
from: 'hello@yourdomain.com',
to: 'user@example.com',
subject: 'Welcome',
html: '<p>Welcome aboard!</p>',
});// No SDK, no SMTP config, no region
await fetch('https://api.sendbound.com/v1/send', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.SENDBOUND_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
to: 'user@example.com',
from: 'hello@yourdomain.com',
subject: 'Welcome',
html: '<p>Welcome aboard!</p>',
text: 'Welcome aboard!',
}),
});Using a stored template
import { SendTemplatedEmailCommand } from '@aws-sdk/client-ses';
await ses.send(new SendTemplatedEmailCommand({
Source: 'hello@yourdomain.com',
Destination: { ToAddresses: ['user@example.com'] },
Template: 'WelcomeEmail',
TemplateData: JSON.stringify({
name: 'Jane',
confirm_url: 'https://app.example.com/confirm',
}),
}));await fetch('https://api.sendbound.com/v1/send', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.SENDBOUND_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
to: 'user@example.com',
from: 'hello@yourdomain.com',
templateId: 'tmpl_your_id',
variables: {
name: 'Jane',
confirm_url: 'https://app.example.com/confirm',
},
}),
});SNS notification migration
SES uses SNS topics to deliver bounce, complaint, and delivery notifications to your application. Sendbound replaces this entire stack with a single HTTPS webhook.
What you're replacing
SES → SNS Topic → SQS Queue → Lambda/Worker → Your handlerWhat you get with Sendbound
Sendbound → HTTPS POST → Your endpointUpdate your bounce/complaint handler
// Lambda function triggered by SNS → SQS
export async function handler(event: SQSEvent) {
for (const record of event.Records) {
const snsMessage = JSON.parse(record.body);
const sesNotification = JSON.parse(snsMessage.Message);
if (sesNotification.notificationType === 'Bounce') {
const emails = sesNotification.bounce.bouncedRecipients.map(r => r.emailAddress);
await markAsBounced(emails);
}
if (sesNotification.notificationType === 'Complaint') {
const emails = sesNotification.complaint.complainedRecipients.map(r => r.emailAddress);
await markAsComplained(emails);
}
}
}// Express handler at your webhook endpoint
import crypto from 'crypto';
app.post('/webhooks/sendbound', express.raw({ type: 'application/json' }), (req, res) => {
const sig = req.headers['x-sendbound-signature'] as string;
const expected = crypto.createHmac('sha256', process.env.SENDBOUND_WEBHOOK_SECRET!)
.update(req.body).digest('hex');
if (!crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected))) {
return res.status(401).send('Unauthorized');
}
const event = JSON.parse(req.body.toString());
if (event.type === 'email.bounced') {
await markAsBounced([event.data.to]);
}
if (event.type === 'email.complained') {
await markAsComplained([event.data.to]);
}
res.status(200).send('ok');
});IAM cleanup
After cutover, clean up the SES-specific AWS resources:
# Remove SES send permissions from the IAM role/user
aws iam detach-user-policy \
--user-name your-ses-user \
--policy-arn arn:aws:iam::aws:policy/AmazonSESFullAccess
# Delete the SMTP credentials (IAM user with ses:SendRawEmail)
aws iam delete-access-key \
--user-name your-ses-smtp-user \
--access-key-id AKIAIOSFODNN7EXAMPLE
# Delete SNS topics used for SES notifications
aws sns delete-topic --topic-arn arn:aws:sns:us-east-1:123456789:ses-bounces
aws sns delete-topic --topic-arn arn:aws:sns:us-east-1:123456789:ses-complaintsChecklist
- Documented all verified SES identities/domains
- Exported SES templates via AWS CLI
- Exported account-level suppression list
- Documented SNS topic ARNs + Lambda handlers
- Added and verified sending domain in Sendbound
- Imported suppression list
- Imported templates into Sendbound template editor
- Replaced AWS SDK / SMTP with
fetchto Sendbound API - Set
SENDBOUND_API_KEYin environment - Added Sendbound webhook endpoint and updated handler (replacing SNS/SQS/Lambda)
- Ran parallel test (SES + Sendbound simultaneously)
- Cut over to Sendbound only
- Deleted SNS topics and SQS queues
- Removed IAM permissions and SMTP credentials
- Uninstalled
@aws-sdk/client-sesfrom package.json
Migrate from Postmark
Step-by-step guide to moving from Postmark to Sendbound. API mapping, message stream mapping, template porting, and webhook translation.
DNS Setup Guide
How to add DKIM, SPF, and DMARC records for your sending domain. Step-by-step for non-engineers, covering all major DNS providers.

