RefreshDocsConsole →

Guides

Migrating from Resend

Move an app from Resend to Refresh (usually one file), dual-run, then cut over.

This guide applies to any app that holds a RESEND_API_KEY. The request and response shapes of POST /emails, POST /emails/batch, GET /emails/:id and POST /emails/:id/cancel are Resend-compatible on purpose.

Prerequisites (per domain)

  1. The domain exists in Refresh under the correct organization and has status Verified. See Verify a domain.
  2. An admin has registered the domain for sending on the domain page.
  3. An API key for the app with SEND permission, restricted to that domain (copy it once).
  4. Optional: a webhook endpoint in the app for email.bounced / email.complained (and email.unsubscribed if the app keeps its own list).

App change

  • Base URL: https://api.f5send.com/api/v1 (so POST https://api.f5send.com/api/v1/emails).

  • Header: Authorization: Bearer f5_live_….

  • Add Idempotency-Key on every cron or scheduled send (availability:<employeeId>:<date>), so a retried job can never send twice.

  • If the app uses the Resend Node SDK, check whether the installed version honours a base-URL override. Otherwise switch to fetch; the body is the same JSON.

  • Bulk messages (digests, sequences, broadcasts): pass kind: "digest.weekly" (or bulk.*, broadcast.*, sequence.*) with one recipient per message. The platform then adds RFC 8058 one-click unsubscribe headers and enforces suppression.

  • Transactional messages: any other kind (invite, alert.reminder) or none. No unsubscribe header.

// before
await resend.emails.send({ from, to, subject, html });

// after
await fetch("https://api.f5send.com/api/v1/emails", {
  method: "POST",
  headers: { Authorization: `Bearer ${process.env.F5SEND_API_KEY}`, "Content-Type": "application/json", "Idempotency-Key": key },
  body: JSON.stringify({ from, to, subject, html }),
});

Dual-run (1–2 weeks)

Keep the Resend account, its key and its DNS records for one to two weeks. The two sets of records do not conflict. If anything regresses, point the app back at Resend while you investigate. Watch Metrics (delivery / bounce / complaint), Emails, and the DMARC tab of the domain (alignment of the new source IPs).

Cut over

  • Remove the DKIM (resend._domainkey) and Return-Path (send.<domain>) records of Resend. The Refresh records stay: ep1/ep2, bounce., _dmarc, link./reply..
  • Revoke the Resend API key in the app. Delete the Resend team.

Decommission checklist (per domain)

Preconditions: ≥ 14 days on Refresh with delivery ≥ 98 %, bounce < 2 % and complaint < 0.1 %. The DMARC tab shows the SES source IPs as aligned. No app still holds a RESEND_API_KEY for the domain.

  1. In the app: no RESEND_API_KEY remains; only the Refresh key and base URL remain.
  2. In DNS: the Resend records no longer exist (see above).
  3. In Resend: revoke all keys, delete the domain, delete the team.