Blog

Twilio Notify End of Life: How to Migrate Before 2027

Thomas SchiavoneThomas SchiavoneJune 17, 2025
Twilio Notify end of life cover

14 min read · Updated October 22, 2026

Twilio Notify reaches end of life on January 31, 2027 for existing customers, according to Twilio's Help Center. Twilio stopped selling Notify on October 24, 2022, and when it set an earlier shutdown date, it said that after end of life "any requests made to the Notify API will fail." If your app still calls notify.twilio.com, every push notification and SMS that goes through it stops on that date.

The most direct replacement is Courier, the notification platform Twilio uses for its own customer notifications. Every Notify concept maps to a Courier API call, and the copy-paste prompts below let Claude Code, Cursor, or any other coding agent do most of the migration.

The short version:

  • Twilio doesn't have a drop-in replacement: you'd use its Messaging API for SMS, wait on a Push API that's still in private beta, and write the code that sends to every device yourself.
  • What moves: identities become Courier user IDs, push bindings become device tokens, SMS bindings become phone numbers, tags become lists, and any reminders or follow-ups you scheduled in your own code become Journeys.
  • What stays: your Twilio SMS account, your APNs and FCM credentials, and every device token you've already collected. You don't need users to re-register.
  • How much code: most Notify integrations are a few send calls and one endpoint that registers bindings.

When does Twilio Notify reach end of life?

Twilio Notify reaches end of life on January 31, 2027 for existing customers. New customers haven't been able to sign up since October 2022. The date has moved three times:

Date announcedWhat Twilio saidSource
October 24, 2022Notify sales end. End of life set for October 23, 2023. No new features or rate-limit increases.Twilio changelog
May 17, 2023End of life moved to April 25, 2024. After that date, "any requests made to the Notify API will fail."Twilio changelog
January 23, 2024End of life moved to December 31, 2025, so Twilio could provide an alternative.Twilio changelog
CurrentEnd of life on January 31, 2027 for existing customers.Twilio Help Center

Twilio's Notify docs now carry a banner that says Twilio stopped supporting Notify in October 2022. So the API still works until January 31, 2027, but you're running on an unsupported product until then. Don't plan around another extension.

The shutdown doesn't affect push features inside other Twilio products, like Verify, Voice, or Conversations. It only affects apps that call the Notify API directly.

What Twilio Notify did, and what you're replacing

Notify let you send one notification to a user and have Twilio fan it out to every device and phone number that user had registered. Under the hood it had four pieces:

  • Service: a container that held your APNs credential, your FCM credential, and a Twilio Messaging Service for SMS.
  • Binding: one address for one user on one channel. A binding has an Identity (your user ID), a BindingType (apn, fcm, or sms), an Address (the device token or phone number), and up to 20 Tag values. Each identity could hold up to 20 bindings.
  • Notification: the send call. You passed Identity (up to 20 users) or Tag (up to 5 tags), plus a Title and Body, and Notify delivered to every matching binding. Optional Apn, Fcm, and Sms objects overrode the payload per channel.
  • Delivery callbacks: an optional DeliveryCallbackUrl that Notify called as messages moved through delivery.

Notify also had a ToBinding parameter for one-off sends to addresses you hadn't stored, which handled bulk SMS to up to about 10,000 numbers per request.

What does Twilio recommend instead of Notify?

Twilio's transition article splits Notify's job across several products:

  • SMS: the Programmable Messaging API, plus the Bulk Messaging API for large sends.
  • Push for authentication: Twilio Verify.
  • Two-way chat with push: Twilio Conversations.
  • Marketing and omnichannel push: Twilio Segment.
  • Generic push: a Push API that's in private beta.

That covers each channel on its own, but it doesn't replace the thing that made Notify useful: one API call that reaches a user on whatever channels they have. If you rebuild on Twilio's suggestions, you take on the fan-out logic, the device token storage, and the per-channel retries yourself.

That's the gap a notification platform fills, and it's why Twilio itself runs on one.

Why Twilio uses Courier

Twilio is a Courier customer and a Courier investor. When Twilio needed to send product notifications across channels for its own customers, it didn't build that on Notify. It chose Courier, and today the Twilio Console embeds the Courier Inbox for in-app notifications.

Product Lead, Twilio

"As a customer engagement platform, we at Twilio know how important it is to meet your customers at their preferred channels. Courier gives us the power to fully personalize our customer touchpoints in one easy-to-use, centralized tool."

Moving from Notify to Courier is also a move up a layer. Notify was a messaging API. Courier is notification infrastructure: it sits on top of providers like Twilio, APNs, and FCM and handles routing, templates, preferences, and in-app delivery. The notification infrastructure vs marketing platform guide explains why Notify replacements belong in this category and not in a marketing suite.

How Twilio Notify concepts map to Courier

Every Notify concept has a direct Courier equivalent:

Twilio NotifyCourierNotes
ServiceWorkspace environmentCourier gives you Production and Test environments by default, each with its own API key.
APN and FCM credentialsPush provider integrationsReuse the same APNs key and Firebase service account.
Messaging Service (SMS)Twilio SMS integrationKeep your Twilio account. Courier sends SMS through it using your Account SID, Auth Token, and Messaging Service SID.
Identityuser_id on a user profileUse the same ID you used as the Notify identity.
Binding, type apn or fcmDevice token on the userPUT /users/{user_id}/tokens/{token} with provider_key set to apn or firebase-fcm.
Binding, type smsphone_number on the profileOne profile field instead of one binding per number.
TagListSubscribe users to a list, then send with to: { list_id }.
Notify all tagA list every user joinsOr use a list pattern to reach several lists in one send.
Notification createPOST /sendPass content: { title, body } inline, or a template ID.
Apn, Fcm, Sms payload overridesChannel and provider overridesAPNs overrides cover sound, badge, payload, expiry, and apns-priority.
DeliveryCallbackUrlOutbound webhooksSet once per environment in Settings, not per request.
ToBindingInline recipientSend to to: { phone_number } without creating a profile.
Your own cron jobs and delayed sendsJourneysSends, delays, and conditions in one flow you start with a single API call.

Users and identities

Notify stored addresses as bindings, one per channel per device. Courier stores them on the user. A Courier user profile holds email and phone_number, and the user's push tokens hang off the same user ID. When you send to a user_id, Courier looks up every address that user has.

Sends

In Notify, the send call decided where a notification went: whatever bindings matched the identity or tag. In Courier, a routing rule decides. method: "all" sends on every channel you list, which matches Notify. method: "single" tries channels in order and stops at the first one that delivers, which Notify couldn't do. That's how you get "push first, SMS only if push fails."

Tags and lists

Notify tags were flat strings on a binding. Courier lists are named groups of users that you subscribe and unsubscribe through the API. List IDs use dots for namespaces (up to six segments), so notify.team-abc and notify.plan-premium can both be reached with the pattern notify.*.

Migration plan, step by step

The examples use Node.js with the twilio package and Courier's server SDK, @trycourier/courier. Courier has server SDKs for Python, Go, Ruby, Java, PHP, and C# with the same shapes.

Step 1: Set up Courier with your existing providers

  1. Create a Courier account. The Developer plan is free and includes 10,000 sends a month.
  2. Connect your providers in the Test environment first:
    • SMS: add the Twilio integration with the same Account SID, Auth Token, and Messaging Service SID your Notify Service uses.
    • iOS push: add the APNs integration with your existing APNs key.
    • Android push: add the Firebase FCM integration with your existing service account.
  3. Copy your Test API key into COURIER_API_KEY.

Step 2: Copy your Notify bindings into Courier

Your device tokens and phone numbers already live in Notify. Read them out and write them to Courier, so nobody has to reopen your app or re-enter a number:

JavaScript
import twilio from "twilio";
import Courier from "@trycourier/courier";

const tw = twilio(process.env.TWILIO_ACCOUNT_SID, process.env.TWILIO_AUTH_TOKEN);
const courier = new Courier(); // reads COURIER_API_KEY

const service = tw.notify.v1.services(process.env.NOTIFY_SERVICE_SID);
const PROVIDER_KEYS = { apn: "apn", fcm: "firebase-fcm" };
const toListId = (tag) => `notify.${tag.toLowerCase().replace(/[^a-z0-9_-]/g, "-")}`;

for (const binding of await service.bindings.list()) {
  const userId = binding.identity;

  if (binding.bindingType === "sms") {
    // POST /profiles/{user_id} merges, so other profile fields stay untouched
    await courier.profiles.create(userId, { profile: { phone_number: binding.address } });
  } else if (PROVIDER_KEYS[binding.bindingType]) {
    await courier.users.tokens.addSingle(binding.address, {
      user_id: userId,
      provider_key: PROVIDER_KEYS[binding.bindingType],
    });
  }

  for (const tag of binding.tags) {
    await courier.lists.subscriptions.subscribeUser(userId, { list_id: toListId(tag) });
  }
}

Run it against the Test environment, spot-check a few users in the Courier dashboard, then run it again with your Production key. Both calls overwrite instead of duplicating, so it's safe to re-run.

Step 3: Replace your send calls

Here's a typical Notify send:

JavaScript
await service.notifications.create({
  identity: ["user_123"],
  title: "Your order shipped",
  body: "Order #4821 is on its way.",
});

And the Courier version that behaves the same way, reaching every push device and the user's phone:

JavaScript
await courier.send.message({
  message: {
    to: { user_id: "user_123" },
    content: { title: "Your order shipped", body: "Order #4821 is on its way." },
    routing: { method: "all", channels: ["push", "sms"] },
    channels: { push: { routing_method: "all" } },
  },
});

The channels.push.routing_method: "all" line matters. APNs and FCM sit inside one push channel, and by default Courier delivers push through the first provider that works. Setting it to all sends to every device the user has, which is what Notify did.

Tag sends become list sends:

JavaScript
// Notify
await service.notifications.create({ tag: ["premium"], body: "New feature unlocked." });

// Courier
await courier.send.message({
  message: {
    to: { list_id: "notify.premium" },
    content: { title: "New feature", body: "New feature unlocked." },
    routing: { method: "all", channels: ["push", "sms"] },
  },
});

Once the sends work, move the copy out of your code and into templates. Then you send template: "nt_..." with data, and anyone on your team can edit the wording without a deploy.

Step 4: Move reminders and follow-ups into Journeys

Notify sent one notification per call. Anything with timing, like a reminder an hour before an appointment, lived in your own code as cron jobs or queued tasks. In Courier, that logic goes in a journey: a sequence of sends, delays, and conditions you build in the dashboard or through the API.

For example, an appointment journey sends a push confirmation right away, waits until an hour before the appointment, then sends an SMS reminder. Your app starts it with one call:

JavaScript
await courier.journeys.invoke("YOUR_JOURNEY_ID", {
  user_id: "user_123",
  data: { appointment_id: "apt_789", remind_at: "2026-11-03T14:00:00Z" },
});

Set the journey's cancelation token to apt-{{data.appointment_id}}, and when the user cancels the appointment, stop the reminder:

JavaScript
await courier.journeys.cancel({ cancelation_token: "apt-apt_789" });

See build your first journey for the full API walkthrough.

Step 5: Update binding registration

Wherever your app creates a Notify binding today (usually an endpoint your mobile app calls on launch), write the token to Courier instead. On iOS that's provider_key: "apn", on Android provider_key: "firebase-fcm". Run both writes in parallel during cutover so new devices land in both systems.

Step 6: Replace delivery callbacks

If you used DeliveryCallbackUrl, add an outbound webhook in Courier's Settings. Courier posts message:updated events with statuses like SENT, DELIVERED, UNDELIVERABLE, and (for email) OPENED and CLICKED. Every message also has a full timeline in the Courier logs, so you can see each provider attempt.

Step 7: Cut over gradually

  1. Put each send behind a feature flag that picks Notify or Courier.
  2. Move your lowest-volume notification first and compare delivery against Notify.
  3. Ramp traffic to 10%, then 50%, then 100%, watching delivery rates in Courier.
  4. Keep Notify as a fallback until every notification type runs on Courier for a full week.

Step 8: Turn off Notify

Remove the Notify client, the binding endpoint writes, and the flag. Then delete the Notify Service. Your Twilio Messaging Service stays, because Courier sends SMS through it.

Migrate with an AI coding agent: copy-paste prompts

You can hand most of this migration to a coding agent like Claude Code, Cursor, Codex, or Windsurf. Give the agent Courier's context first, so it writes current API calls instead of guessing:

Terminal
npx skills add trycourier/courier-skills

That installs the Courier skill, which teaches the agent Courier's SDKs, routing, and push setup. If you also want the agent to create lists, check profiles, and send test notifications in your workspace, add the Courier MCP server with your Test API key. This command is for Claude Code; Build with AI has the setup for Cursor, Codex, and other agents:

Terminal
claude mcp add --transport http courier https://mcp.courier.com --header api_key:YOUR_TEST_API_KEY

Then run these prompts in order. Each one is a self-contained task you can review and merge on its own.

Prompt 1: Audit your Notify usage

Text
Find every place this codebase uses Twilio Notify. Search for the Notify API
(notify.twilio.com, notify.v1, client.notify, Notify Service SIDs starting with
"IS", and any Binding or Notification calls).

Write a file called NOTIFY_MIGRATION.md that lists:
1. Every send call: file and line, whether it targets identities, tags, or
   ToBinding, the channels it reaches, and any Apn/Fcm/Sms payload overrides.
2. Every place bindings are created, updated, or deleted, and which binding
   types (apn, fcm, sms) each one handles.
3. Every tag the code uses and where it's assigned.
4. Any DeliveryCallbackUrl handlers and what they do with the status.
5. Any reminders or follow-ups scheduled around Notify sends: cron jobs,
   delayed jobs, or queued tasks, with their timing and cancel conditions.
6. The env vars and config that hold Notify credentials.

Don't change any code yet.

Prompt 2: Copy bindings into Courier

Text
Using the Courier skill and NOTIFY_MIGRATION.md, write a one-time script at
scripts/migrate-notify-bindings that copies every Twilio Notify binding into
Courier:

1. Page through all bindings on the Notify Service.
2. For sms bindings, merge phone_number into the Courier profile with
   POST /profiles/{user_id} (profiles.create in the SDK). Use the Notify
   identity as the Courier user_id.
3. For apn and fcm bindings, register the address as a device token with
   PUT /users/{user_id}/tokens/{token}, provider_key "apn" or "firebase-fcm".
4. For each binding tag, subscribe the user to a Courier list named
   notify.<tag>, lowercased, with characters other than a-z, 0-9, _ and -
   replaced by -.
5. Log counts per binding type and per list, and retry on 429 with backoff.

Read credentials from TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN,
NOTIFY_SERVICE_SID, and COURIER_API_KEY. Add a --dry-run flag that prints what
it would write. Run the dry run and show me the summary.

Prompt 3: Replace Notify sends with Courier

Text
Using the Courier skill and NOTIFY_MIGRATION.md, replace every Twilio Notify
send with a Courier send through the @trycourier/courier SDK (or this
project's language SDK).

1. Create one wrapper, sendNotification(), that every call site uses. It takes
   a user ID or list ID, a title, a body, and optional data.
2. Map identity sends to to: { user_id }, and tag sends to
   to: { list_id: "notify.<tag>" } using the same naming as the binding script.
3. Use routing { method: "all", channels } with the channels each call reached
   on Notify, and set channels.push.routing_method to "all" so every device
   gets the push.
4. Move Apn/Fcm/Sms payload overrides to Courier channel or provider overrides.
5. Put the switch behind a NOTIFICATIONS_PROVIDER env flag ("notify" or
   "courier") so both paths keep working during cutover.
6. Update binding registration so new device tokens and phone numbers are
   written to both Notify and Courier while the flag exists.

Update the tests for each call site, then send one test notification to my
own user ID through the Courier path and show me the message ID.

Prompt 4: Move reminders and follow-ups into Journeys

Text
Using the Courier skill and NOTIFY_MIGRATION.md, move every reminder or
follow-up the code schedules around Notify sends (cron jobs, delayed jobs,
queued tasks) into Courier Journeys.

1. For each flow, create a journey with an API Invoke trigger and the same
   send and delay steps the code runs, in the same order. For scheduled
   reminders, use a delay "until" a timestamp passed in the invoke data.
2. Give each journey a cancelation token built from the record it's about,
   such as apt-{{data.appointment_id}}.
3. Replace the scheduling code with one journeys.invoke call, and call
   journeys.cancel with the same token wherever the code cancelled a job.
4. Publish each journey's templates and then the journey, invoke each one in
   the Test environment for my user ID, and show me the run status.

Prompt 5: Replace delivery callbacks and finish the cutover

Text
Using the Courier skill and NOTIFY_MIGRATION.md:

1. Add an endpoint that receives Courier outbound webhook events
   (message:updated) and feeds them into the same code the Notify
   DeliveryCallbackUrl handler used. Verify the Courier webhook signature.
   Map Courier statuses (SENT, DELIVERED, UNDELIVERABLE, UNROUTABLE) to the
   statuses the app already understands.
2. Tell me the URL to register under Settings > Webhooks in Courier.
3. Write a CUTOVER.md checklist: ramp each notification from 10% to 50% to
   100% on Courier, what to compare in the Courier logs at each step, and the
   final cleanup (remove the flag, the Notify client, the old scheduling code,
   the dual binding writes, and the Notify env vars).

When the last notification runs on Courier for a week with clean delivery, have the agent do the cleanup from CUTOVER.md and delete the Notify Service.

Twilio Notify vs Courier: feature comparison

Notify was built for one job: fan a message out to push and SMS. Here's what changes when you move:

CapabilityTwilio NotifyCourier
ChannelsAPNs, FCM, SMS/MMSEmail, SMS, push, in-app inbox, Slack, Microsoft Teams, WhatsApp, and more across 50+ providers
ProvidersTwilio onlyAny mix, including Twilio, SendGrid, Amazon SES, APNs, and FCM
RoutingSend to every matching bindingSend to all channels, or try channels in order with automatic failover
In-app notificationsNot supportedCourier Inbox with React, iOS, and Android SDKs
TemplatesContent passed in every requestDesign Studio visual editor, plus a templates API
WorkflowsNoneJourneys with delays, branches, batching, and digests
User preferencesBuild your ownHosted preference center that every send checks
GroupsFlat tags, up to 20 per bindingLists with pattern sends, plus rule-based audiences
Multi-tenant appsNot supportedTenants with per-tenant branding and preferences
ObservabilityDelivery callbacksPer-message timelines, analytics, and outbound webhooks
StatusUnsupported since 2022, end of life January 31, 2027Actively developed

Start your migration

The work is smaller than it looks: one binding copy, a send wrapper, a few journeys, and a webhook.

  1. Create a free Courier account. The Developer plan is free and includes 10,000 sends a month.
  2. Run Prompt 1 against your codebase to see exactly what you're moving.
  3. Want help planning it? Talk to the Courier team about your Notify setup.

For more on choosing a replacement, read the Twilio Notify alternative guide. To see how other teams moved, read the stories from Twilio, LaunchDarkly, and Workleap.