Skip to content
imessageapi

Switching iMessage providers without breaking anything

Vendors in this category come and go. Here is how to build so that leaving one is an inconvenience rather than a rebuild — and how to run the migration when you do.

7 min readUpdated August 22, 2026Getting started

This is a young category built on arrangements Apple has not sanctioned. Vendors get acquired, change pricing, or stop working. Assume you will switch at least once and the cost of switching drops enormously — and read what happens if Apple shuts this down for the version of this risk nobody selling you an API wants to discuss.

Build behind your own interface from day one

Never call a vendor SDK from your application code. One thin adapter, one interface, and the vendor becomes a swappable detail rather than a dependency threaded through forty files.

lib/messaging/index.ts
// Your app only ever knows about this shape.
export type Messenger = {
send(to: string, text: string, opts?: SendOptions): Promise<{ id: string }>;
parseInbound(rawBody: string, headers: Headers): InboundEvent | null;
};
 
// Swapping vendors becomes a one-line change here.
import { sendblue } from "./adapters/sendblue";
import { blooio } from "./adapters/blooio";
 
export const messenger: Messenger =
process.env.MESSAGING_VENDOR === "blooio" ? blooio : sendblue;

Own your consent and opt-out data

This is the one that hurts. If opt-out state lives only in a vendor dashboard, switching resurrects people who unsubscribed — a compliance failure and an unforced insult. Keep the consent table in your own database, always.

What to export before you leave

  1. The full opt-out list, with timestamps.
  2. Message history per customer, if you do not already mirror it locally.
  3. Delivery-status history, if you use it for reporting or reconciliation.
  4. Your number — this is a port, not an export, and it takes the longest. Start here.

Run both in parallel

Do not cut over on a Friday. Stand the new vendor up alongside the old one, route a small share of low-stakes traffic through it — internal alerts, then non-urgent confirmations — and watch delivery rates for a week before you move anything that matters.

gradual-cutover.ts
// Deterministic split so a given customer stays on one vendor
// throughout the migration rather than flapping between them.
export function vendorFor(customerId: string, newVendorPct: number) {
const bucket = hashToPercent(customerId);
return bucket < newVendorPct ? blooio : sendblue;
}
 
// Ramp 5% → 25% → 50% → 100%, watching delivery rate at each step.

Watch the right metric during cutover

Not send success — that is nearly always 100%. Watch delivered rate from webhooks, and watch reply rate. A new vendor with an unwarmed number will show a healthy send rate and a quietly terrible delivered rate, and only the webhook data will tell you. See deliverability.

Next step

Generate a tagged link for whatever you send next with the UTM builder, see what this looks like in your industry, or compare the services that can send it on the providers page.