← Back to docs

Migrate from Resend

Move transactional email from Resend to EuroMail: API field mapping, code before/after, SMTP cutover, DNS, webhooks, and a zero-downtime checklist.

What changes, what doesn't

Both services are REST-first transactional email APIs, so the shape of your integration survives: one HTTP call per email, webhooks for delivery events, domain verification via DNS. What changes is the endpoint, the auth header, two field names, and where your data lives. EuroMail processes and stores everything in Finland. Resend remains the better choice in some situations; the honest comparison is at euromail vs Resend.

Plan for one dual-running window (both providers verified on your domain) and the cutover itself is a config change, not a rewrite.

API mapping

ResendEuroMail
EndpointPOST https://api.resend.com/emailsPOST https://api.euromail.dev/v1/emails
Auth headerAuthorization: Bearer re_...X-EuroMail-Api-Key: em_live_...
HTML bodyhtmlhtml_body
Text bodytexttext_body
Recipientsto, cc, bcc, reply_toSame names. to accepts string or array, cc/bcc are arrays
Attachmentsattachments: [{ filename, content }]Same, plus an explicit content_type per attachment
Schedulingscheduled_atsend_at (RFC 3339). Response returns status: "scheduled"
IdempotencyIdempotency-Key header, 24 h expiryidempotency_key body field. Same key always returns the original result
Tagstags (array of objects)tags (array of strings) + free-form metadata object echoed on webhooks
Success response200 with { "id": ... }202 Accepted with { "id", "status": "queued", ... }

Two behavioural differences worth encoding in your client:

  • 202, not 200. EuroMail acknowledges that the email is queued, not delivered. If your code checks response.status === 200, change it to accept 202 (or any 2xx).
  • Idempotency moves from header to body. Generate the key from your own domain object (order id, invoice id) exactly as you did for Resend, only the transport changes. Details in Sending Emails.

Code before and after

curl:

# Before: Resend
curl -X POST https://api.resend.com/emails \
  -H "Authorization: Bearer re_xxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "from": "[email protected]",
    "to": "[email protected]",
    "subject": "Welcome aboard",
    "html": "<h1>Welcome!</h1>",
    "text": "Welcome!"
  }'

# After: EuroMail
curl -X POST https://api.euromail.dev/v1/emails \
  -H "X-EuroMail-Api-Key: em_live_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "from": "[email protected]",
    "to": "[email protected]",
    "subject": "Welcome aboard",
    "html_body": "<h1>Welcome!</h1>",
    "text_body": "Welcome!"
  }'

TypeScript:

// Before: Resend
import { Resend } from "resend";
const resend = new Resend(process.env.RESEND_API_KEY);

await resend.emails.send({
  from: "[email protected]",
  to: "[email protected]",
  subject: "Welcome aboard",
  html: "<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>",
});

SDKs for Python, Rust, and Go follow the same field names as the REST API.

SMTP cutover

If you send through Resend's SMTP interface, the swap is four settings:

SettingResendEuroMail
Hostsmtp.resend.comsmtp.euromail.dev
Port25 / 465 / 587 / 2465 / 2587587 (STARTTLS) or 465 (implicit TLS)
Usernameresend (literal)apikey (literal)
Passwordyour API keyyour API key (em_live_...)

Most "SMTP provider" presets work unchanged. Full walkthroughs for WordPress, framework mailers, and legacy apps are in Send Email via SMTP.

DNS: verify your domain before switching

EuroMail rejects sends from unverified domains, so do this first, while Resend is still handling production traffic:

  1. Add your domain in the EuroMail dashboard and publish the DKIM, SPF, and DMARC records it gives you. Step-by-step in Domain Verification.
  2. Keep Resend's DNS records in place during the dual-run. DKIM records live under different selectors and SPF include:s coexist, so both providers stay verified simultaneously.
  3. After cutover, remove Resend's records on your own schedule, leaving them for a week or two is harmless and lets you roll back instantly.

Webhook event mapping

Resend eventEuroMail event
email.delivereddelivered
email.bouncedbounced
email.delivery_delayeddeferred
email.complainedcomplained
email.openedopened
email.clickedclicked
email.sentNo equivalent. The 202 API response already confirms acceptance
email.failedNo single event. Permanent failures surface as bounced; undeliverable jobs land in the dead-letter queue
email.receivedInbound is a route, not a webhook event. See Inbound Email
email.scheduled / email.suppressedReflected in the send response (status: "scheduled") and the suppression list
contact.* / domain.*Managed via Contact Lists and the domains API rather than webhooks

Signature verification changes: Resend signs via Svix headers; EuroMail signs every payload with HMAC-SHA256 in the X-EuroMail-Signature header. Swap your verification function for the one in the Webhooks guide.

Bring your suppression list

Carrying over suppressions protects your sender reputation from day one. addresses that bounced or complained on Resend should never get mail through EuroMail either. Export your suppressions from the Resend dashboard, then replay them:

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 Resend: hard bounce"
  }'

One call per address (a ten-line loop over your export). 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. By the time production traffic moves, the recipient allowance has expired and only the loosened week-one rate cap (15/minute, 75/hour) remains. 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 Resend running in parallel for the first 24 hours.

Honest gaps

  • React Email doesn't port. Resend's React component rendering is a real innovation and EuroMail has no equivalent. If your templates are React components, render them to HTML in your app (React Email's render() works fine) and send the result as html_body, or move them to EuroMail's template engine.
  • Broadcasts/Audiences map, not clone. Resend Audiences become Contact Lists; marketing sends go through Newsletters with separate marketing suppression semantics.
  • The full feature-by-feature comparison, including where Resend is the better choice, is at euromail vs Resend.

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 scope (API Keys).
  2. Verify your sending domain; confirm DKIM/SPF/DMARC show green in the dashboard.
  3. Register your webhook endpoint and swap the signature verification.
  4. Import suppressions from your Resend export.
  5. Switch the endpoint, auth header, and html/texthtml_body/text_body (or the four 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 the Resend account read-only for a rollback window, then retire it.

Send your first email in about 90 seconds

The free tier includes 3,000 emails a month. All data stays in Finland | GDPR compliance without the paperwork.

Create free account See live delivery data