# 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](/docs/migrate/) first for the overall order and how to run both providers in parallel.

## Concepts

| Amazon SES | Emailit |
| --- | --- |
| AWS account in a region | [Workspace](/docs/workspaces/) |
| Verified identities: domains and email addresses | [Sending domains](/docs/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](/docs/workspaces/production-access/). In sandbox mode you can send to workspace members' account emails. |
| Sending quota and maximum send rate | [Sending limits](/docs/limits/): emails per second and per day, reset at midnight UTC |
| IAM credentials and SigV4 signing | [API keys](/docs/developers/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](/docs/webhooks/) that post signed JSON to your HTTPS endpoint |
| Account-level suppression list | Workspace [suppression list](/docs/suppressions/) |
| Email templates | [Templates](/docs/templates/) with an alias and versions |
| Receipt rules | [Inbound email](/docs/inbound/) with the `email.received` webhook |
| Email tags | `meta` |
| Dedicated IPs | [Dedicated IPs](/docs/deliverability/dedicated-ips/) on request |
| Contact lists | [Contacts](/docs/contacts/), [audiences](/docs/audiences/) and [campaigns](/docs/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](/docs/sdks/) for your language. In Node.js:

```javascript title="Before: Amazon SES (AWS SDK v3)"

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>' },
      },
    },
  },
}));
```

```javascript title="After: Emailit"

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](/docs/smtp/), 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](/docs/email-api/send-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](/docs/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}`](/docs/api-reference/emails/get/) |

The payload changes too:

- Each Emailit request is a JSON array of up to 100 events, with the name in `type` and the email in `data.object`.
- Use `data.object.id`, the `em_` ID from the send response, instead of the SES `messageId`. Your `meta` values are in `data.object.meta`.
- Instead of checking SNS message signatures, verify `X-Emailit-Signature` against `X-Emailit-Timestamp` and your `whsec_` secret. See [Request signature](/docs/webhooks/request-signature/).

```javascript
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

1. Export the SES account-level suppression list with the AWS CLI. If the output includes a `NextToken`, repeat the command with `--next-token` until you have every page.

```bash
aws sesv2 list-suppressed-destinations --output json \
  | jq -r '(["email","type","reason"] | @csv),
           (.SuppressedDestinationSummaries[] | [.EmailAddress, "recipient", ("ses " + .Reason)] | @csv)' \
  > suppressions.csv
```

   This writes a CSV with the columns `email,type,reason`. Every row uses the type `recipient`, which blocks API, SMTP and campaign sends to the address.

2. If you keep your own list of bounces and complaints from SNS notifications, add those addresses too.

3. In **Email API → Suppressions**, 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](/docs/suppressions/manage/).

## Move templates

Get each template with `aws sesv2 get-email-template --template-name <name>`, then import its HTML in **Email Marketing → Templates** or create it with the [Templates API](/docs/api-reference/templates/create/). 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](/docs/templates/temple/).

## Change DNS

Add your domain in **Email API → Domains** 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](/docs/domains/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](/docs/inbound/set-up/) first.

## Next steps

- [Go-live checklist](/docs/get-started/go-live/)
- [Set up webhooks](/docs/webhooks/set-up/)
- [Priority migration](/docs/programs/priority-migration/): let Emailit engineers do the move with you

---
Source: https://emailit.com/docs/migrate/amazon-ses/
