← 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 tokenX-EuroMail-Api-Key: em_live_... header
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.emails.send({
  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
Deliverydelivered
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 over X-EuroMail-Signature, verifiable without any AWS SDK.

Bring your suppression list

SES maintains an account-level suppression list; export it via the ListSuppressedDestinations API or console, then replay entries:

curl -X POST https://api.euromail.dev/v1/suppressions \
  -H "X-EuroMail-Api-Key: em_live_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "email_address": "[email protected]",
    "reason": "migrated from Amazon SES: hard bounce"
  }'

Details in Suppression Lists.

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 and an API key with the emails:send scope (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.
  5. Replace the AWS SDK SES client with the EuroMail SDK (or the two SMTP settings), accepting 202 as success.
  6. Send a staging email end-to-end; confirm the delivered webhook arrives.
  7. Cut production traffic over; watch the first hour in Analytics and Deliverability Insights.
  8. 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 1,000 emails a month. All data stays in Finland | GDPR compliance without the paperwork.

Create free account See live delivery data