← Back to docs

Migrate from Amazon SES

Move transactional email from Amazon SES to EuroMail: API differences, code before/after, SMTP cutover, DNS, event mapping, and a zero-downtime checklist.

What changes, what doesn't

This is the biggest jump of the four migration guides, because SES isn't a conventional API-key product. It's an AWS service authenticated with IAM and wired into SNS for events, not webhooks. What survives: the SMTP path, if that's how you send today, changes the least. What doesn't: request signing, event delivery, and (usually) scheduling all work differently enough that "find and replace the endpoint" won't get you there. The honest comparison, including where SES remains the better choice (raw per-email cost, if you already accept AWS as your data processor), is at euromail vs Amazon SES.

Verify your domain on EuroMail first, run both in parallel, then cut over.

API mapping

Amazon SES (v2)EuroMail
EndpointPOST https://email.{region}.amazonaws.com/v2/email/outbound-emailsPOST https://api.euromail.dev/v1/emails, no region in the path
AuthAWS Signature Version 4. IAM credentials, not a bearer tokenAuthorization: Bearer em_live_... header (X-EuroMail-Api-Key: em_live_... also works)
Sender/recipientFromEmailAddress, Destination.ToAddresses/CcAddresses/BccAddresses (arrays)from, to/cc/bcc
HTML bodyContent.Simple.Body.Html.Datahtml_body
Text bodyContent.Simple.Body.Text.Datatext_body
AttachmentsContent.Simple.Attachments[].RawContent (added to the Simple content type in 2024; raw MIME was previously required for attachments)attachments: [{ filename, content, content_type }] (base64)
GroupingEmailTags (name/value pairs)tags (array of strings) + metadata (object)
SchedulingNo native delivery-time parameter. Build it yourself (EventBridge Scheduler, SQS delay queue, or a cron that calls SendEmail at the right time)send_at (RFC 3339). Built in, no extra infrastructure
IdempotencyNone at the SES API levelidempotency_key body field. Same key always returns the original result
Success response200 with { "MessageId": "..." }202 Accepted with { "id", "status": "queued", ... }

Auth is the real migration work here. SES requests are signed with AWS SigV4 (normally handled by an AWS SDK, not written by hand) while EuroMail is a single static header. If your code currently calls SESv2Client from an AWS SDK, replace the client entirely rather than trying to preserve the signing logic.

Code before and after

TypeScript (SigV4 signing makes a raw curl comparison misleading. The SDK swap is the realistic before/after):

// Before: Amazon SES (AWS SDK v3)
import { SESv2Client, SendEmailCommand } from "@aws-sdk/client-sesv2";
const ses = new SESv2Client({ region: "eu-west-1" });

await ses.send(new SendEmailCommand({
  FromEmailAddress: "[email protected]",
  Destination: { ToAddresses: ["[email protected]"] },
  Content: {
    Simple: {
      Subject: { Data: "Welcome aboard" },
      Body: { Html: { Data: "<h1>Welcome!</h1>" } },
    },
  },
}));

// After: EuroMail
import { EuroMail } from "@euromail/sdk";
const client = new EuroMail({ apiKey: process.env.EUROMAIL_API_KEY! });

await client.sendEmail({
  from: "[email protected]",
  to: "[email protected]",
  subject: "Welcome aboard",
  html_body: "<h1>Welcome!</h1>",
});

No IAM role, no region config, no SDK-specific client. An API key and one HTTP call. Python, Rust, and Go SDKs follow the same pattern.

SMTP cutover

If you send through SES's SMTP interface rather than the API, this is the smallest-effort path. SES SMTP is already a username/password credential, not IAM-signed:

SettingAmazon SESEuroMail
Hostemail-smtp.{region}.amazonaws.com (region-specific)smtp.euromail.dev (single global endpoint)
Port587, 465, or 25587 (STARTTLS) or 465 (implicit TLS)
UsernameSES-specific SMTP username (generated in the console, derived from IAM, not your access key directly)apikey (literal string)
PasswordSES-specific SMTP password (also console-generated, one per AWS Region)your EuroMail API key (em_live_...)

Two simplifications: EuroMail has no per-region endpoint (one host, everywhere) and no separate credential-generation step (the API key you already have is the password). Full walkthroughs in Send Email via SMTP.

DNS: verify your domain before switching

  1. Add your domain in the EuroMail dashboard and publish its DKIM, SPF, and DMARC records. Domain Verification.
  2. Keep SES's records in place during the dual-run. Different DKIM selectors and stacked SPF include:s let both providers verify at once.
  3. Remove SES's records after cutover, on your own schedule.

Webhook event mapping (SES uses SNS, not webhooks)

SES doesn't have webhooks in the conventional sense. It publishes JSON event records to an SNS topic, which you then subscribe to (commonly via an HTTP/S endpoint, which behaves like a webhook once wired up, or via SQS). EuroMail delivers events as direct HTTP webhooks with no message-bus configuration required.

SES SNS event typeEuroMail event
Deliverysent
Bounce (bounceType: "Permanent")bounced
Bounce (bounceType: "Transient")deferred
Complaintcomplained
Openopened
Clickclicked
SendNo equivalent. The 202 API response already confirms acceptance
Reject, Rendering FailureNo single event. Malformed requests are rejected synchronously with a 4xx response
DeliveryDelayReflected as a deferred retry, not a separate event
SubscriptionHandled via suppression lists, not a webhook event

Setup changes from configuring an SNS topic, subscription, and (usually) an SQS queue or Lambda to just registering a URL. Swap your event handler for the one in Webhooks: HMAC-SHA256, verified via the X-Euromail-Signature header, without any AWS SDK.

Bring your suppression list

SES maintains an account-level suppression list; export it via the ListSuppressedDestinations API or console, then import them in bulk:

curl -X POST https://api.euromail.dev/v1/suppressions/import \
  -H "Authorization: Bearer em_live_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "emails": ["[email protected]", "[email protected]"],
    "reason": "migrated from Amazon SES: suppressed"
  }'

One request takes up to 10,000 addresses, so split a bigger export into chunks. The response reports how many were added and lists any address it could not parse. The key needs the contacts:manage scope.

Details in Suppression Lists.

Your first 24 hours

Every new account spends its first 24 hours -- counted from account creation, not from your first send -- inside a probation window with two limits designed to stop day-one spam blasts without starving a real migration:

  • A send-rate cap: 5 emails per minute and 25 per hour on day one, loosening over the first week.
  • A recipient allowance: a token bucket of 20 that refills at 2 per hour, counted per deliverable recipient (to plus every cc and bcc). A settled, non-zero invoice raises the allowance to 1,000 -- a paid plan still inside its trial does not count as a payment. The allowance lifts entirely once the account is 24 hours old.

A refused send returns 429 with a retry-after header (the SMTP relay returns 452, which well-behaved mailers retry). Test sends from a not-yet-verified domain to your own address (sandbox mode) never consume the allowance, so integration testing is free.

The practical consequence for a migration: create your EuroMail account and verify your domain at least a day before cutover, so the recipient allowance has expired by the time production traffic moves. If you must cut over with real volume on day one, settle your first invoice first (1,000 recipients covers a typical day) or keep Amazon SES running in parallel.

After that, the account's age and payments set the pace:

WhenSend rateMonthly volume
First 24 hours5/minute, 25/hour, plus the recipient allowance above3,000
Days 2 to 715/minute, 75/hour3,000 until your first invoice is paid
From day 8100/minute, 500/hourYour plan's volume once an invoice has been paid

A paid plan that is still in its trial counts as unpaid: until a non-zero invoice has been paid, the account is held to the free allowance of 3,000 emails a month whatever plan it is on. Sends over a limit return 429. Plan the cutover around these numbers: if your normal hourly volume is above the rate for the week you cut over in, keep the old provider carrying part of the traffic until it fits.

Honest gaps

  • No infrastructure-as-code parity. If your SES setup is Terraform/CDK (identities, configuration sets, SNS topics, IAM policies), that infrastructure has no EuroMail equivalent to import. Domain verification and webhook registration happen once, through the dashboard or API, and stay there.
  • Sending quotas work differently. SES enforces per-account sending rate and daily quota limits you request increases for; EuroMail's plan tiers define quota instead, with no separate rate-limit negotiation. See Billing & Plans.
  • Cross-account sending authorization (SES's identity policies allowing one AWS account to send as another's verified identity) has no direct analog. Use sub-accounts for the equivalent multi-tenant pattern.
  • Full comparison at euromail vs Amazon SES.

Cutover checklist

  1. Create a EuroMail account at least 24 hours before cutover (probation limits expire with account age) and an API key with the emails:send and contacts:manage scopes (API Keys).
  2. Verify your sending domain; confirm DKIM/SPF/DMARC show green.
  3. Register your webhook endpoint, no SNS topic or subscription needed.
  4. Import suppressions from your SES export with POST /v1/suppressions/import.
  5. Replace the AWS SDK SES client with the EuroMail SDK (or the two SMTP settings), accepting 202 as success.
  6. Check your volume against the limits for the week you cut over in (see above), and pay the first invoice if you need more than 3,000 emails this month. For bulk jobs, send up to 500 messages per request with POST /v1/emails/batch and back off on 429 (wait for retry-after when the response has one).
  7. Send a staging email end-to-end; confirm the sent webhook arrives.
  8. Cut production traffic over; watch the first hour in Analytics and Deliverability Insights.
  9. Keep SES identities verified for a rollback window, then clean up IAM policies and SNS subscriptions.

Send your first email in about 90 seconds

The free tier includes 3,000 emails a month. Email storage and AI processing in Finland, with a DPA on every plan.

Create free account See live delivery data