The shape of a Resend setup
Most Resend accounts have the same moving parts: a verified domain, a single API key used by the service code, a small library of HTML templates stored in the codebase, a webhook endpoint that receives email.delivered and email.bounced events, and a few resend.emails.send call sites in the service code. Each of those maps cleanly to a Flowmails primitive, which is what makes the move an afternoon instead of a quarter.
The order of the steps is the order that minimizes downtime. DNS first, deploy second, code third, gray-area cleanup last. Skipping ahead almost always costs you a confused customer who got a bounce during the window.
Step 1–3: DNS and the receiving zone
If your domain is already on Cloudflare, this section is short. If it is hosted elsewhere, move the zone first — that move is the largest blast radius of the migration and belongs in its own change window.
- Verify the zone. Add the domain to Cloudflare, let the nameservers propagate, confirm the zone is
active in the dashboard. - Enable Email Routing. Cloudflare will rewrite any third-party MX records out of the way automatically; this is the one step where the dashboard does the right thing without an API call.
- Add the catch-all. Point the routing rule at your receive Worker. The Worker decides which mailbox caught the message — you do not need a separate rule per local part.
Step 4–5: Worker deploy and SDK swap
Provision the receive Worker in your Cloudflare account and bind D1, R2, and the email secret. Once the worker is up, inbound mail starts landing in your D1 without any further work. Test it with a quick curl from a Gmail alias before you swap the SDK.
The SDK swap is the most boring step and the most likely to go wrong if you try to do it in a hurry. Replace import { Resend } from "resend" with the Flowmails SDK call site, ship behind a feature flag, and run the two SDKs in parallel for at least a week of production traffic. The shape of the request payload is similar enough that the diff is small.
// before
import { Resend } from "resend";
const resend = new Resend(process.env.RESEND_API_KEY);
await resend.emails.send({
from: "hello@yourdomain.com",
to: user.email,
subject: "Welcome",
html: renderWelcome(user),
});
// after
import { Flowmails } from "@flowmails/sdk";
const fm = new Flowmails({ apiKey: process.env.FLOWMAILS_KEY });
await fm.messages.send({
from: "hello@yourdomain.com",
to: user.email,
subject: "Welcome",
html: renderWelcome(user),
});Step 6–7: Templates and webhooks
Templates stored in Resend are plain HTML / Handlebars / JSX — pull them into the Flowmails dashboard or render them in your Worker. The worker-side render is preferable: it keeps the templates next to the code that owns the data, and it avoids the round-trip cost of asking a remote template service to render at send time.
Webhooks are where teams lose the most time during a migration. Resend’s event payload is { type, data } and Flowmails matches the same shape, but the event names differ in a few cases. Map them before you point the dashboard at the new endpoint or you will silently drop bounce events for the first hour. The mapping is boring on purpose: email.delivered and email.bounced stay the same; email.complained maps to email.spam_report.
Step 8: The gray-area cleanup
The eighth step is the one nobody schedules. After the SDK swap and the webhook repoint, you still have: the old API key in production secrets (rotate it), the Resend domain-verification record on your DNS (remove it), any environment variables pointing at the old dashboard (clean them up), and a quiet entry in your runbook for what to do if you have to roll back. A clean migration is one where the rollback path is documented even if you never use it.
The historical messages stay where they were — that is the one thing that does not move. New sends flow through the new pipeline; old bounces and old open events keep coming from the old provider until the old API key stops being valid. Plan for that overlap so your dashboard is not surprised by a Resend event arriving a week after the cutover.
Common mistakes and how to avoid them
Skipping the parallel run. The feature flag exists so you can compare the two SDKs on the same traffic. Cutting over without the parallel window is how you discover, three days in, that one of your templates had a Resend-specific helper that does not exist on Flowmails.
Leaving the old DNS in place. The DKIM record on Resend keeps signing mail until you delete it. If you forget, your outbound reputation gets split between the two providers, and bounce-rate analytics become unreadable.
Forgetting the bounces. The old provider will keep firing bounce webhooks for messages you no longer send it. Suppress those in your handler before they pollute the new dashboard.
Where to read more
The Resend alternatives page lays out the architecture comparison, and the Flowmails vs Resend page is the side-by-side. If the question is what changes on the inbound side once you cut over, the Workers-native email routing tour walks through it. Pricing is the per-domain and per-volume breakdown once the migration is done.