
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 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 announced | What Twilio said | Source |
|---|---|---|
| October 24, 2022 | Notify sales end. End of life set for October 23, 2023. No new features or rate-limit increases. | Twilio changelog |
| May 17, 2023 | End of life moved to April 25, 2024. After that date, "any requests made to the Notify API will fail." | Twilio changelog |
| January 23, 2024 | End of life moved to December 31, 2025, so Twilio could provide an alternative. | Twilio changelog |
| Current | End 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.
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:
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.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.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.
Twilio's transition article splits Notify's job across several products:
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.
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.
Every Notify concept has a direct Courier equivalent:
| Twilio Notify | Courier | Notes |
|---|---|---|
| Service | Workspace environment | Courier gives you Production and Test environments by default, each with its own API key. |
| APN and FCM credentials | Push provider integrations | Reuse the same APNs key and Firebase service account. |
| Messaging Service (SMS) | Twilio SMS integration | Keep your Twilio account. Courier sends SMS through it using your Account SID, Auth Token, and Messaging Service SID. |
Identity | user_id on a user profile | Use the same ID you used as the Notify identity. |
Binding, type apn or fcm | Device token on the user | PUT /users/{user_id}/tokens/{token} with provider_key set to apn or firebase-fcm. |
Binding, type sms | phone_number on the profile | One profile field instead of one binding per number. |
Tag | List | Subscribe users to a list, then send with to: { list_id }. |
Notify all tag | A list every user joins | Or use a list pattern to reach several lists in one send. |
Notification create | POST /send | Pass content: { title, body } inline, or a template ID. |
Apn, Fcm, Sms payload overrides | Channel and provider overrides | APNs overrides cover sound, badge, payload, expiry, and apns-priority. |
DeliveryCallbackUrl | Outbound webhooks | Set once per environment in Settings, not per request. |
ToBinding | Inline recipient | Send to to: { phone_number } without creating a profile. |
| Your own cron jobs and delayed sends | Journeys | Sends, delays, and conditions in one flow you start with a single API call. |
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.
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."
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.*.
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.
COURIER_API_KEY.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:
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.
Here's a typical Notify send:
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:
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:
// 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.
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:
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:
await courier.journeys.cancel({ cancelation_token: "apt-apt_789" });See build your first journey for the full API walkthrough.
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.
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.
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.
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:
npx skills add trycourier/courier-skillsThat 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:
claude mcp add --transport http courier https://mcp.courier.com --header api_key:YOUR_TEST_API_KEYThen run these prompts in order. Each one is a self-contained task you can review and merge on its own.
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.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.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.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.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.
Notify was built for one job: fan a message out to push and SMS. Here's what changes when you move:
| Capability | Twilio Notify | Courier |
|---|---|---|
| Channels | APNs, FCM, SMS/MMS | Email, SMS, push, in-app inbox, Slack, Microsoft Teams, WhatsApp, and more across 50+ providers |
| Providers | Twilio only | Any mix, including Twilio, SendGrid, Amazon SES, APNs, and FCM |
| Routing | Send to every matching binding | Send to all channels, or try channels in order with automatic failover |
| In-app notifications | Not supported | Courier Inbox with React, iOS, and Android SDKs |
| Templates | Content passed in every request | Design Studio visual editor, plus a templates API |
| Workflows | None | Journeys with delays, branches, batching, and digests |
| User preferences | Build your own | Hosted preference center that every send checks |
| Groups | Flat tags, up to 20 per binding | Lists with pattern sends, plus rule-based audiences |
| Multi-tenant apps | Not supported | Tenants with per-tenant branding and preferences |
| Observability | Delivery callbacks | Per-message timelines, analytics, and outbound webhooks |
| Status | Unsupported since 2022, end of life January 31, 2027 | Actively developed |
The work is smaller than it looks: one binding copy, a send wrapper, a few journeys, and a webhook.
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.

WhatsApp Business messages: types, templates, and rules
Every WhatsApp Business message is a template or a free-form message, and the type decides when you can send it and what it can look like. How businesses use each one, the template categories, the formats worth knowing, and the rules that stop messages from arriving.

WhatsApp pricing changes on October 1, 2026
On October 1, 2026, two things become billable on the WhatsApp Business Platform: free-form replies inside the 24-hour customer service window, and utility templates sent in response to a customer. If your WhatsApp volume is conversations rather than campaigns, this is not a rate tweak. It adds a line to your invoice where there used to be a zero. Here is what changes, a worked example of the cost, and what to check.

Clinical alert and notification systems: how escalation works
A clinical alert is easy to send and hard to close. This is how alerting works inside a hospital: where alerts come from, how routing by role and shift works, and how the acknowledge, timeout, escalate loop is actually built, including what happens when nobody answers.