Guide
Migrate from Amazon SES
Move from Amazon SES to Emailit. Map identities, sandbox and quotas, replace SDK calls and SMTP credentials, and turn SNS events into signed webhooks.
This guide maps Amazon SES concepts, API calls, event notifications, suppressions and templates to their Emailit equivalents. Read Migrate to Emailit first for the overall order and how to run both providers in parallel.
Concepts
| Amazon SES | Emailit |
|---|---|
| AWS account in a region | Workspace |
| Verified identities: domains and email addresses | Sending domains. Single email address identities aren’t available. |
| Easy DKIM CNAME records | One DKIM TXT record, emailit._domainkey |
| Custom MAIL FROM domain | The return path emailit.<your domain>, which every domain has |
| Sandbox and production access request | Sandbox mode and production access. In sandbox mode you can send to workspace members’ account emails. |
| Sending quota and maximum send rate | Sending limits: emails per second and per day, reset at midnight UTC |
| IAM credentials and SigV4 signing | API keys in an Authorization: Bearer header |
| SMTP credentials | Your API key, used as the SMTP password |
| Configuration sets and event destinations (SNS, EventBridge, Firehose) | Webhooks that post signed JSON to your HTTPS endpoint |
| Account-level suppression list | Workspace suppression list |
| Email templates | Templates with an alias and versions |
| Receipt rules | Inbound email with the email.received webhook |
| Email tags | meta |
| Dedicated IPs | Dedicated IPs on request |
| Contact lists | Contacts, audiences and campaigns |
Update your API calls
SES calls are signed with your AWS credentials, so you usually make them through an AWS SDK. With Emailit, you send a JSON request with an API key, or use the Emailit SDK for your language. In Node.js:
import { SESv2Client, SendEmailCommand } from '@aws-sdk/client-sesv2';
const ses = new SESv2Client({ region: 'eu-west-1' });
await ses.send(new SendEmailCommand({
FromEmailAddress: 'Acme <hello@acme.com>',
Destination: { ToAddresses: ['ada@example.com'] },
Content: {
Simple: {
Subject: { Data: 'Your receipt' },
Body: {
Text: { Data: 'Thanks for your order.' },
Html: { Data: '<p>Thanks for your order.</p>' },
},
},
},
}));import { Emailit } from '@emailit/node';
const emailit = new Emailit(process.env.EMAILIT_API_KEY);
await emailit.emails.send({
from: 'Acme <hello@acme.com>',
to: 'ada@example.com',
subject: 'Your receipt',
text: 'Thanks for your order.',
html: '<p>Thanks for your order.</p>',
});Amazon SES (API v2 SendEmail) |
Emailit (POST /v2/emails) |
|---|---|
| AWS credentials and SigV4 signature | Authorization: Bearer secret_… |
FromEmailAddress |
from |
Destination.ToAddresses, CcAddresses, BccAddresses |
to, cc, bcc, up to 50 each |
ReplyToAddresses |
reply_to |
Content.Simple.Subject.Data |
subject |
Content.Simple.Body.Html.Data, Text.Data |
html, text |
Content.Template.TemplateName |
template, an alias or ID |
Content.Template.TemplateData (a JSON string) |
variables (a JSON object) |
Content.Raw (a complete MIME message) |
Send the MIME message through the SMTP relay, or rebuild it with html, text and attachments |
EmailTags |
meta, returned in webhook events |
ConfigurationSetName |
Not needed. Events go to your webhooks, and tracking is set per domain or per email with tracking. |
Response MessageId |
200 with id (em_…), message_id, status: "accepted" and ids per recipient |
Emailit also supports scheduling with scheduled_at and safe retries with an Idempotency-Key header, which SES doesn’t offer on send. See Send an email.
Switch SMTP settings
| Setting | Amazon SES | Emailit |
|---|---|---|
| Host | email-smtp.<region>.amazonaws.com |
smtp.emailit.com |
| Port | 587, 2587 or 25 (STARTTLS), 465 or 2465 (TLS) |
587, 2587, 2525 or 25 (STARTTLS), 465 (TLS) |
| Username | Your SES SMTP user name | emailit |
| Password | Your SES SMTP password | Your Emailit API key |
Emailit doesn’t read SES headers such as X-SES-CONFIGURATION-SET. Remove them. See SMTP settings.
Map event notifications
SES publishes events through configuration sets to SNS, EventBridge or Firehose. Emailit sends them directly to your HTTPS endpoint as webhooks, so there’s no topic to subscribe to or confirm.
| SES event type | Emailit event |
|---|---|
Send |
email.accepted (API only) |
Delivery |
email.delivered |
DeliveryDelay |
email.attempted |
Bounce with bounceType Permanent |
email.bounced |
Bounce with bounceType Transient |
email.attempted while Emailit retries, then email.bounced if every retry fails |
Complaint |
email.complained |
Open |
email.loaded |
Click |
email.clicked |
Subscription |
email.unsubscribed, for campaign email only |
Reject, Rendering Failure |
No direct equivalent. Errors in the request, such as a missing template, are returned by the API right away, and messages Emailit won’t deliver get the status held. |
| Receipt rule with an SNS or Lambda action | email.received, then fetch the content with GET /emails/{id} |
The payload changes too:
- Each Emailit request is a JSON array of up to 100 events, with the name in
typeand the email indata.object. - Use
data.object.id, theem_ID from the send response, instead of the SESmessageId. Yourmetavalues are indata.object.meta. - Instead of checking SNS message signatures, verify
X-Emailit-SignatureagainstX-Emailit-Timestampand yourwhsec_secret. See Request signature.
for (const event of req.body) {
const email = event.data.object;
if (event.type === 'email.bounced') markBounced(email.to, email.id);
if (event.type === 'email.complained') unsubscribe(email.to);
}Move suppressions
-
Export the SES account-level suppression list with the AWS CLI. If the output includes a
NextToken, repeat the command with--next-tokenuntil you have every page.aws sesv2 list-suppressed-destinations --output json \ | jq -r '(["email","type","reason"] | @csv), (.SuppressedDestinationSummaries[] | [.EmailAddress, "recipient", ("ses " + .Reason)] | @csv)' \ > suppressions.csvThis writes a CSV with the columns
email,type,reason. Every row uses the typerecipient, which blocks API, SMTP and campaign sends to the address. -
If you keep your own list of bounces and complaints from SNS notifications, add those addresses too.
-
In Email APISuppressions, select Import and upload the file. Each file can have up to 10,000 rows and can be at most 8 MB. Duplicates are skipped.
See Manage suppressions.
Move templates
Get each template with aws sesv2 get-email-template --template-name <name>, then import its HTML in Email MarketingTemplates or create it with the Templates API. Use the SES template name as the Emailit alias, if it fits the alias format of lowercase letters, numbers, - and _.
SES templates use Handlebars-style tags. Temple covers the common parts:
| Amazon SES | Emailit (Temple) |
|---|---|
{{name}}, {{user.name}} |
The same |
{{#if plan}}…{{else}}…{{/if}} |
The same |
{{#each items}}…{{/each}} |
Not supported. Render the list in your code and pass it as one variable. |
TemplateData as a JSON string |
variables as a JSON object |
| No built-in default | {{name|"there"}} adds a fallback |
Temple never escapes HTML, so escape user input before you pass it. See Temple.
Change DNS
Add your domain in Email APIDomains and publish the Emailit records. They use their own names (emailit._domainkey, emailit.<domain>, and optionally go and inbound), so they don’t conflict with the SES Easy DKIM CNAMEs or a custom MAIL FROM subdomain. Keep your DMARC record. See DNS records.
After the cutover, remove the SES DKIM CNAMEs and the custom MAIL FROM records, and delete the identities in SES. If you receive mail with SES receipt rules, move it to an Emailit inbound subdomain first.
Next steps
- Go-live checklist
- Set up webhooks
- Priority migration: let Emailit engineers do the move with you