Migrate from Amazon SES to emailr_
Replace raw Amazon SES with emailr_: keep SES-grade delivery, drop SNS plumbing and configuration sets, and get templates, webhooks and an inbox.
Amazon SES is a solid sending engine, but on its own it leaves you to build event pipelines out of configuration sets and SNS, manage sandbox limits and bounce handling, and write your own templates, logs and inbound processing.
emailr_ sends through Amazon SES across multiple regions, so your delivery path stays familiar. What you drop is the glue code: one SDK call to send, signed webhooks for events, a dashboard for logs and an inbox for replies.
The code change
A typical Amazon SES send call next to its emailr_ equivalent.
import { SESv2Client, SendEmailCommand } from '@aws-sdk/client-sesv2'
const ses = new SESv2Client({ region: 'us-east-1' })
await ses.send(new SendEmailCommand({
FromEmailAddress: '[email protected]',
Destination: { ToAddresses: ['[email protected]'] },
Content: {
Simple: {
Subject: { Data: 'Your receipt' },
Body: { Html: { Data: '<p>Thanks for your order.</p>' } },
},
},
EmailTags: [{ Name: 'category', Value: 'receipts' }],
}))import { Emailr } from 'emailr'
const emailr = new Emailr({ apiKey: process.env.EMAILR_API_KEY! })
await emailr.emails.send({
from: '[email protected]',
to: '[email protected]',
subject: 'Your receipt',
html: '<p>Thanks for your order.</p>',
tags: { category: 'receipts' },
})Amazon SES concepts in emailr_
Use this table to find where each feature you rely on lives in emailr_.
| Amazon SES | emailr_ |
|---|---|
| IAM credentials + region | One API key; regions handled for you |
| Verified identity (Easy DKIM CNAMEs) | Domain (DKIM TXT, SPF, send. MAIL FROM) |
| Custom MAIL FROM domain | Configured by default on the send. subdomain |
| Configuration sets + event destinations (SNS / EventBridge) | Webhooks |
EmailTags / message tags | tags object |
SES templates (SendTemplatedEmail) | Templates (template_id + template_data) |
| Account-level suppression list | Contacts with subscribed: false |
| Receipt rules (S3 / Lambda) | Inboxes and email.received webhooks |
How to migrate from Amazon SES
- 01
Create an API key
Create a key under API keys in the dashboard. Keys start with
et_live_and are scoped to one domain workspace. Store it asEMAILR_API_KEYin your secret manager or environment, never in client-side code. - 02
Add and verify your sending domain
In the dashboard, open Domains and add the domain you send from. A subdomain such as
mail.yourcompany.comkeeps transactional reputation separate from your corporate mailbox.Emailr generates the DNS records: a DKIM TXT record on the
emailr._domainkeyselector, an SPF TXT record, and an MX + TXT pair on thesendsubdomain used as the MAIL FROM / Return-Path. A DMARC record is optional but recommended.SES Easy DKIM publishes three CNAMEs under
_domainkey. Emailr's DKIM key lives on its ownemailr._domainkeyselector, so the two don't clash.Emailr's DKIM selector and
sendsubdomain don't collide with other providers' records, so you can add them before cutting over and send from both providers during the migration. A domain can only have one SPF record: if one already exists, merge Emailr'sinclude:into it instead of adding a second record. - 03
Replace SendEmailCommand
Install the SDK and replace
SendEmailCommandwithemailr.emails.send(). The nestedDestination/Content.Simplestructure flattens intoto,cc,bcc,subject,htmlandtext.ReplyToAddressesbecomesreply_to_email.Raw MIME (
Content.Raw) isn't needed for attachments: passattachments: [{ filename, content, contentType }]with base64 content.Terminalbashnpm install emailr - 04
Replace SNS event plumbing
Instead of a configuration set publishing to an SNS topic that triggers a Lambda that writes to your database, create one webhook pointing at your API. Delete the SNS subscriptions after cutover.
- 05
Move templates and suppressions
Recreate SES templates in emailr_ and send them with
template_idandtemplate_data. Emailr templates substitute flat{{variable}}tokens fromtemplate_data. There are no loops or conditionals, so pre-render lists and optional blocks in your code and pass the result as a variable.Export your account-level suppression list (
ListSuppressedDestinationsor the SES console) and import those addresses as contacts withsubscribed: false. - 06
Recreate your event webhooks
Map SES event types to emailr_ events using the table above.
Create a webhook of type
transactional(delivery and engagement events),domain(verification status) orreceiving(inbound email). Each webhook has its own signing secret. Every request carries anX-Emailr-Signatureheader: a hex-encoded HMAC-SHA256 of the raw request body. Verify it before trusting the payload.verify-webhook.tstsimport { createHmac, timingSafeEqual } from 'node:crypto' // rawBody must be the exact request body string, before JSON.parse export function isValidEmailrWebhook(rawBody: string, signature: string | null) { if (!signature) return false const expected = createHmac('sha256', process.env.EMAILR_WEBHOOK_SECRET!) .update(rawBody) .digest('hex') const a = Buffer.from(expected) const b = Buffer.from(signature) return a.length === b.length && timingSafeEqual(a, b) } // const ok = isValidEmailrWebhook(body, req.headers.get('x-emailr-signature')) // const { event, data } = JSON.parse(body) // e.g. event === 'email.bounced' - 07
Replace receipt rules
If you receive mail through SES receipt rules into S3 or Lambda, create an emailr_ inbox instead. Messages are parsed, threaded and available in the dashboard, and a
receivingwebhook deliversemail.receivedevents to your code.
Webhook event mapping
emailr_ webhooks post one JSON event per request with an event name and a data object. Translate event names in your handler with this table.
| Amazon SES event | emailr_ event |
|---|---|
| Send | email.sent |
| Delivery | email.delivered |
| Bounce | email.bounced |
| Complaint | email.complained |
| Reject / RenderingFailure | email.failed |
| Open | email.opened |
| Click | email.clicked |
Things to watch for
- Sending limits come from your emailr_ plan instead of SES sending quotas, so check them before moving high-volume traffic.
- You won't see SES message IDs in your code anymore; store emailr's
message_idfrom the send response instead. - If your IAM policies or VPC endpoints existed only for SES, remove them after cutover.
Frequently asked questions
Does emailr_ use Amazon SES under the hood?
Yes. emailr_ sends through Amazon SES across multiple AWS regions, which is why its SPF record includes amazonses.com. You get the same sending infrastructure without managing IAM, configuration sets or SNS.
Do I need an AWS account to use emailr_?
No. You only need an emailr_ API key. AWS accounts, regions, sandbox limits and sending quotas are handled by emailr_.
How do SES configuration set events map to emailr_?
Create a transactional webhook. SES Send, Delivery, Bounce, Complaint, Open and Click map to email.sent, email.delivered, email.bounced, email.complained, email.opened and email.clicked.
Can I keep my custom MAIL FROM domain?
emailr_ sets up a MAIL FROM on the send subdomain of your sending domain automatically, so SPF aligns with your from address for DMARC.
What about inbound email from SES receipt rules?
Create an inbox in emailr_. Incoming mail is parsed and threaded, and you can subscribe a receiving webhook to email.received to process it in code.
Keep going
Documentation
Related guides
- Migrate from SendGridMove from SendGrid to emailr_: map dynamic templates, categories, suppressions and Event Webhook events, swap the SDK and cut over with zero downtime.
- Migrate from PostmarkMove from Postmark to emailr_: map servers, message streams, templates, tags and metadata, translate webhooks and import suppressions step by step.
- Migrate from SparkPostMove from SparkPost to emailr_: replace transmissions, map substitution data, campaigns, metadata, suppressions and webhook events step by step.
Ready to leave Amazon SES?
Create a free account, verify your domain and send your first email through emailr_ in minutes. No credit card required.