← 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.

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 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 1,000 emails a month. All data stays in Finland | GDPR compliance without the paperwork.

Create free account See live delivery data