New from emailrHealthCheck Email iconHealthCheck Email
emailr_

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 iconAmazon SESemailr_

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.

Before (AWS SDK v3, SESv2)ts
import { SESv2Client, SendEmailCommand } from '@aws-sdk/client-sesv2'

const ses = new SESv2Client({ region: 'us-east-1' })

await ses.send(new SendEmailCommand({
  FromEmailAddress: 'hello@mail.acme.com',
  Destination: { ToAddresses: ['user@example.com'] },
  Content: {
    Simple: {
      Subject: { Data: 'Your receipt' },
      Body: { Html: { Data: '<p>Thanks for your order.</p>' } },
    },
  },
  EmailTags: [{ Name: 'category', Value: 'receipts' }],
}))
After (emailr_)ts
import { Emailr } from 'emailr'

const emailr = new Emailr({ apiKey: process.env.EMAILR_API_KEY! })

await emailr.emails.send({
  from: 'hello@mail.acme.com',
  to: 'user@example.com',
  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 SESemailr_
IAM credentials + regionOne API key; regions handled for you
Verified identity (Easy DKIM CNAMEs)Domain (DKIM TXT, SPF, send. MAIL FROM)
Custom MAIL FROM domainConfigured by default on the send. subdomain
Configuration sets + event destinations (SNS / EventBridge)Webhooks
EmailTags / message tagstags object
SES templates (SendTemplatedEmail)Templates (template_id + template_data)
Account-level suppression listContacts with subscribed: false
Receipt rules (S3 / Lambda)Inboxes and email.received webhooks

How to migrate from Amazon SES

  1. 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 as EMAILR_API_KEY in your secret manager or environment, never in client-side code.

  2. 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.com keeps transactional reputation separate from your corporate mailbox.

    Emailr generates the DNS records: a DKIM TXT record on the emailr._domainkey selector, an SPF TXT record, and an MX + TXT pair on the send subdomain 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 own emailr._domainkey selector, so the two don't clash.

    Emailr's DKIM selector and send subdomain 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's include: into it instead of adding a second record.

  3. 03

    Replace SendEmailCommand

    Install the SDK and replace SendEmailCommand with emailr.emails.send(). The nested Destination / Content.Simple structure flattens into to, cc, bcc, subject, html and text. ReplyToAddresses becomes reply_to_email.

    Raw MIME (Content.Raw) isn't needed for attachments: pass attachments: [{ filename, content, contentType }] with base64 content.

    Terminalbash
    npm install emailr
  4. 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.

  5. 05

    Move templates and suppressions

    Recreate SES templates in emailr_ and send them with template_id and template_data. Emailr templates substitute flat {{variable}} tokens from template_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 (ListSuppressedDestinations or the SES console) and import those addresses as contacts with subscribed: false.

  6. 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) or receiving (inbound email). Each webhook has its own signing secret. Every request carries an X-Emailr-Signature header: a hex-encoded HMAC-SHA256 of the raw request body. Verify it before trusting the payload.

    verify-webhook.tsts
    import { 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'
  7. 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 receiving webhook delivers email.received events 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 eventemailr_ event
Sendemail.sent
Deliveryemail.delivered
Bounceemail.bounced
Complaintemail.complained
Reject / RenderingFailureemail.failed
Openemail.opened
Clickemail.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_id from 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.

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.