7 Sendbird Alternatives, And How to Build Marketplace Chat With Each

The best Sendbird alternatives for in-app chat in 2026 are Stream, CometChat, TalkJS, and Twilio Conversations. Stream is the closest like-for-like replacement, and you can build an entire chat app on the Stream free plan. CometChat has good built-in features, TalkJS fits a web marketplace inbox, and Twilio is a good pick when a thread has to reach SMS or WhatsApp. Other Sendbird competitors like PubNub, Ably, and Rocket.Chat fit narrower cases.
Teams leave Sendbird over price, and over features that require a higher plan or a sales request, such as group channels capped at 100 members.
We make Stream, so we obviously think Stream is the best option for moving from Sendbird. We've spent years on in-app messaging that stays fast and reliable at any size. Chat, feeds, video, and moderation all come from the same platform, so you don't have to stitch together vendors as the product grows.
But we want to give you something you can verify, so we built a simple marketplace chat nine times, on Sendbird, on all seven alternatives, and on plain WebSockets. So, alongside understanding the feature and pricing differences, you can see what it takes to build on each platform.
Why Do Teams Look for a Sendbird Alternative?
Price is the main reason. Sendbird's first paid plan, Starter, is $349 USD/month billed annually for 5,000 MAU. Peak concurrent connections are capped at 5% of MAU, which is 250 connections at once. Pro is $499 USD at the same size. Published prices stop at 25,000 MAU, and at 50,000 the page shows a contact sales button.
You also pay for features based on your plan. Translation, image moderation, and supergroup channels are on Pro, not Starter, so if you need any of them, compare against Pro's price. Translation also requires a sales request before it works. The moderation rule engine and review tickets are part of Advanced Moderation, an Enterprise add-on.
There are several other reasons:
- Sendbird's roadmap has moved toward AI customer service agents, and its docs say the earlier AI chatbot product has been discontinued.
- The JavaScript SDK has no browser push, and Sendbird sends no email, so to notify an offline user on the web, you write a webhook handler and send the notification yourself.
- Sendbird has no activity feeds, which our Stream build uses to update the order of each listing.
Some teams shouldn't switch. Stay on Sendbird if your support agents work tickets in Sendbird Desk, because Stream, TalkJS, CometChat, PubNub, and Ably don't have their own desk product. Stay, too, if agents answer from Salesforce through Sendbird's connector.
Sendbird Alternatives at a Glance
The entry price is the cheapest paid plan we found for 5,000 MAU, billed annually, where the vendor offers that. For comparison, Sendbird Starter is $349 USD.
| Alternative | Best for, compared with Sendbird | Where it beats Sendbird | Where Sendbird still wins | Entry price |
|---|---|---|---|---|
| Stream | The closest like-for-like replacement | Everything we tested worked on the free plan, activity feeds, 10,000 MAU on the entry plan | Desk for support tickets | $399 USD, includes 10,000 MAU |
| TalkJS | A web marketplace inbox on TalkJS's own UI | Email and SMS to offline users, per its docs, and contact masking on every plan | Masking that runs on the server, a report API, a choice of regions | $239 USD, includes 10,000 MAU |
| CometChat | The most built-in features, starting on the free plan | Translation on the free plan, phone and email detection on every plan, email and SMS notifications per its docs | A filter that didn't mistake our order number for a phone number, a React kit under a third the size | $299 USD, on the 10,000 MAU tier |
| Twilio Conversations | Chat that also reaches SMS and WhatsApp | A phone number can join the same thread | A content filter, a report API, translation, a React UI kit | $240 USD, at 0.05 USD per user after the first 200 |
| PubNub | Chat next to other real-time data | Location or device data on the same connection | A chat SDK that ran on a new account, a built-in filter, a maintained React kit | About $370 USD, a Pro plan estimate |
| Ably | Ordering and delivery guarantees in writing | Exactly-once, ordered delivery in its docs, browser push without Firebase | Channel members, read receipts, file uploads, user reports | $29 USD plus usage |
| Rocket.Chat | Self-hosting | Runs on your own servers, email over your own SMTP | A regex filter, an embeddable SDK, nothing to host | Free up to 50 users, then a quote |
The entry prices aren't for the same thing. Stream and TalkJS include 10,000 MAU; CometChat has no 5,000-user tier; Twilio charges per user; and Ably charges for messages and connection time.
Quick Picks
- Pick Stream if you want Sendbird's channel and custom message model with more built in. Stream adds activity feeds, moderation for text, images, video, and audio under one policy, and channels with no member cap. Its SDKs manage connections for you, and you can customize the UI without forking.
- CometChat fits if you want built-in email notifications and translation, and you'll be on its Advanced plan, since Basic has neither. Test its phone detector against your own order numbers first.
- Pick TalkJS if chat is a web inbox between buyers and sellers, and you'll use TalkJS's UI components, where contact masking works.
- Choose Twilio Conversations when a seller has to reply by SMS or WhatsApp in the same thread.
- PubNub fits when chat shares a connection with live location or device data, and you can wait on a support ticket before the Chat SDK runs.
- Ably fits when you're building the whole UI yourself and want a documented guarantee on message order.
- Rocket.Chat is for teams whose messages have to stay on servers they run.
- Stay on Sendbird if your support team works in Desk.
Our Marketplace Chat Build
The build is a small marketplace with a single listing: a used road bike for $240 USD. Ana is the buyer and writes in Spanish, and Ben is the seller and reads English. Sam is the support agent who joins when something goes wrong.
The chat has to:
- Open a 1:1 channel between buyer and seller, tied to the order.
- Show the order as a card in the thread and update it as the order moves from placed to paid and shipped.
- Stop phone numbers, email addresses, and requests to pay outside the platform, and let users report each other.
- Send push notifications to an offline seller, with email as the fallback.
- Translate messages so Ana reads Spanish and Ben reads English.
- Bring a support agent into the same thread with the earlier messages visible.
- Show read receipts and typing indicators, and accept image attachments.
The browser talks to the chat platform through its SDK. Our server issues user tokens, edits the card through the chat API when the order service changes it, and receives the platform's webhooks.

Every platform that ran got the same React app, the same three users, and the same conversation.
To test the filter, Ana sends Ben the same six messages each time. Four are the kind a marketplace wants stopped. Two are harmless, and one of those is an order number that looks like a phone number:
| Message | What should happen |
|---|---|
| Call me at 415-555-0134 | Stopped |
| Email me at ben@example.com, and we'll sort it out | Stopped |
| Pay me on Venmo instead, it's cheaper | Stopped |
| Let's move this to WhatsApp, +44 7700 900123 | Stopped |
| Is the 54cm frame still available? | Delivered |
| Order 4155550134 arrived this morning, thanks | Delivered |
A message counts as stopped if Ben never sees the full phone number, the email address, or the words Venmo or WhatsApp.
The build is a text chat: a 1:1 thread that becomes a small group chat when Sam joins. It doesn't support real-time communication by voice or video. It also leaves out live streaming, which Sendbird handles with open channels and we handle with a livestream channel type.
For pricing, we assumed a marketplace with 50,000 MAU that keeps six months of message history. We compare list prices by MAU, from 5,000 up to that size.
The Baseline: How Sendbird Handles It
Sendbird does everything bar translation, which needs a Pro or Enterprise plan and a request to sales.
What Does It Take to Build on Sendbird?
The order card is a user message with a custom type, and the card is stored as a JSON string in data. This is the function that sends it:
async sendOrderCard(card: OrderCard) {
// A user message with a customType and a JSON string in `data` is Sendbird's custom message.
const message = await new Promise<UserMessage>((resolve, reject) => {
channel!
.sendUserMessage({
message: 'Order ' + card.orderId + ': ' + card.listingTitle,
customType: 'order_card',
data: JSON.stringify(card),
})
.onSucceeded((m) => resolve(m as UserMessage))
.onFailed((err) => reject(err));
});
echo(message);
return { messageId: String(message.messageId) };
},sendUserMessage returns a handler object with onSucceeded and onFailed callbacks, so our code wraps it in a promise. Sendbird also doesn't deliver a sender's own message through onMessageReceived, so echo passes the send result to the sender's UI.
Sendbird ships UIKit for iOS, Android, React, and React Native. In the React kit, renderMessage receives every message, so showing the card is a four-line branch on customType that falls back to the kit's own Message component.
What Works on Sendbird, and What's Missing?
This is what Sendbird has for each thing the chat needs:
| What the chat needs | On Sendbird | How |
|---|---|---|
| Order channel | Yes | A group channel with a URL we choose (order-ord_1001), customType: 'order', and the order and listing IDs as JSON in data |
| Order card | Yes | A user message with customType: 'order_card'. The order service edits it through the Platform API, and both clients get onMessageUpdated |
| Contact filter and reports | Yes | A profanity filter that takes regular expressions and replaces matches with asterisks, plus reportUser, which reaches the dashboard and a user:report webhook |
| Offline notification | On the web, you build it | Mobile push through FCM and APNs is built in. The docs say there's no browser push and no email, and point to webhooks for your own notifier |
| Translation | Needs Pro or Enterprise | translationTargetLanguages on each message, once sales has enabled it |
| Bringing in support | Yes | inviteWithUserIds(['agent_sam']). New members see earlier messages by default |
| Receipts, typing, files | Yes | markAsRead, startTyping, and sendFileMessage |
The profanity filter takes regular expressions, so we gave it three of our own, for phone numbers, email addresses, and the names of payment and messaging apps:
// phone numbers
(\+\d{1,3}[ .-]?)?(\(?\d{2,4}\)?[ .-]){1,3}\d{3,4}\b
// email
[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}
// payment/messaging
(?i)\b(venmo|paypal|zelle|cash ?app|revolut|western union|bitcoin|crypto|whatsapp|telegram|signal)\bIt masked all four messages we wanted stopped and left the other two alone. The order number wasn't masked because our phone pattern requires separators between digit groups.
Our trial app requested translation on every message and got none back. The Sendbird pricing page lists translation under Pro and Enterprise, and the SDK page says to contact the sales team to enable it. If you need translation, plan on Pro and a conversation with sales.

Inviting Sam puts an agent in the order thread. Sendbird's support product, Desk, works differently. A Desk ticket has its own group channel, is assigned to an agent in the Sendbird dashboard, and links back to the order channel. Desk is a separate product type that you choose when you create the app, and it isn't on the Chat pricing page.
How Much Does Sendbird Cost?
Billed annually, Starter is $349 USD/month at 5,000 MAU, but it doesn't include translation, so this build needs the Pro plan. Pro is $499 USD/month at 5,000 MAU, $579 USD at 10,000, and $1,399 USD at 25,000. At 50,000 MAU, the pricing page says to contact sales. The free trial runs for 30 days with 1,000 MAU, and then drops to a Developer plan with 100 users. (Prices checked October 2026.)
Where Is Sendbird Strong, and Where Is It Weak?
Sendbird's strengths are Desk for teams whose chat is tied to customer support, and text moderation, which runs on the server so every client gets filtered messages. For this build, its weak points are translation, the browser, the code that notifies an offline user, and pricing at larger sizes. At 25,000 MAU, Pro is the highest list price we found, and at 50,000 there's no list price.
Stream: The Closest Port
Stream is the closest replacement for Sendbird, and it goes further in areas where Sendbird stops short.
What Does It Take to Build on Stream?
Every Sendbird concept this build uses has a counterpart in Stream Chat.
| In the build | Sendbird | Stream |
|---|---|---|
| Order channel | Group channel with customType: 'order' |
Channel type order, created on the server |
| Channel ID | channelUrl: 'order-ord_1001' |
client.channel('order', 'order-ord_1001') |
| Order and listing IDs | JSON string in data |
Top-level custom fields, stored as JSON |
| Order card | User message with customType and data |
Message with a custom attachment |
| Card update from the server | PUT /v3/group_channels/{url}/messages/{id} |
partialUpdateMessage |
| Contact filter | Profanity filter regex on the channel type | Regex and word block lists in a moderation policy |
| User report | reportUser, dashboard, user:report webhook |
flagUser, review queue, user.flagged webhook |
| Translation | translationTargetLanguages per message |
auto_translation_enabled plus each user's language |
| Adding the agent | inviteWithUserIds |
addMembers |
Sending the card is one awaited call. The card goes in as an object with no JSON.stringify, because a custom attachment is stored as JSON:
async sendOrderCard(card: OrderCard) {
// Stream's custom message is a custom attachment type; the kit renders it through the Attachment override.
const { message } = await channel!.sendMessage({
text: 'Order ' + card.orderId + ': ' + card.listingTitle,
attachments: [{ type: 'order_card', card }],
});
return { messageId: message.id };
},The contact filter is set up once on the server. It's three block lists, a phone regex, an email regex, and a word list, attached to a moderation policy for the order channel type with the action set to remove. The same policy turns on the AI rule for platform circumvention.
/** Block lists: a regex list for phone numbers, an email list, and a word list for off-platform payment. */
const BLOCK_LISTS = [
{ name: 'mkt_phone_regex', type: 'regex', words: ['(\\+\\d{1,3}[ .-]?)?(\\(?\\d{2,4}\\)?[ .-]){1,3}\\d{3,4}\\b'] },
{ name: 'mkt_email_regex', type: 'regex', words: ['[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}'] },
{ name: 'mkt_off_platform', type: 'word', words: ['venmo', 'paypal', 'zelle', 'cash app', 'cashapp', 'revolut', 'western union', 'bitcoin', 'crypto', 'whatsapp', 'telegram', 'signal'] },
];
for (const bl of BLOCK_LISTS) {
await c.createBlockList(bl);
}
await c.moderation.upsertConfig({
key: 'chat:' + CHANNEL_TYPE,
block_list_config: { enabled: true, rules: BLOCK_LISTS.map((b) => ({ name: b.name, action: 'remove' as const })) },
automod_platform_circumvention_config: { enabled: true, rules: [{ label: 'PLATFORM_CIRCUMVENTION', action: 'remove', threshold: 0.7 }] },
});Translation is one app setting plus a language on each user. After that, every message carries its translations, and the browser reads the one for the user's language.
// Server: each user gets a language, and translation is one app setting.
await c.upsertUsers(Object.values(USERS).map((u) => ({ id: u.id, name: u.name, language: u.lang, role: 'user' })));
await c.updateAppSettings({ auto_translation_enabled: true });
// Browser: Stream puts each translation on the message as <lang>_text.
const i18n = m.i18n as Record<string, string> | undefined;
const native = i18n?.[user.lang + '_text'];In our React UI components, a five-line Attachment override passed to WithComponents draws the card, the counterpart of Sendbird's renderMessage branch. Each status change also posts an order_status activity to the listing's feed through our Feeds product.
By default, a channel member can't add another member, so we added the update-channel-members grant to the order channel type before Ana could bring in Sam. Reporting a user and bringing in the agent are a call each, and the server reads the review queue the report lands in:
// Browser
await client!.flagUser(userId, { reason });
await requireChannel().addMembers(['agent_sam'], { text: 'Support agent Sam joined the conversation.' });
// Server
const r = await chatClient().moderation.queryReviewQueue({}, [{ created_at: -1 }], { limit: 25 });The flag also fires a user.flagged webhook, which our server listens for.
What Works on Stream, and What's Missing?
The contact filter is three block lists. One is a phone regex, one is an email regex, and one is a word list. They're attached to a moderation policy for the order channel type, with the action set to remove. The same policy has the AI rule for platform circumvention. Together, they stopped all four messages and let the order number through. When Ana reported Ben, flagUser put the report in the review queue.
Stream has no support desk product. addMembers puts Sam in the thread, and tickets and agent routing come from another tool or your own code. Stream also has no browser push and doesn't send email, so you listen for the message.new webhook and send the notification from your own code.
How Much Does Stream Cost?
Start is $399 USD for 10,000 MAU and $1,849 USD at 50,000. Our smallest paid plan includes 10,000 MAU, so an app with 5,000 users pays $50 USD more on Start than on Sendbird Starter. Smaller apps have the free Build plan (1,000 MAU and up to 5 million stored messages) and the Maker Account, which gives small teams a modified Start plan with 2,000 MAU.
For more features, Elevate costs $599 USD/month for up to 10,000 MAU, compared with $579 USD for Sendbird Pro at 10,000. It's $1,299 USD at 27,000 against $1,399 (for 25,000), and $2,299 USD at 50,000, with Sendbird as a sales quote. Elevate is also the plan that adds HIPAA compliance and multi-tenancy. (Prices checked October 2026.)
Does Stream Work at Enterprise Scale?
Yes. Stream's Enterprise plan is for apps that grow past the published tiers. It adds a 99.999% uptime SLA, SAML SSO, 2FA, audit logs, a dedicated region stack, 24/7 support, and a shared Slack channel with our team. Stream has been benchmarked at 5 million concurrent connections in a single channel, with average chat latency under 40 ms. Compliance covers SOC 2 Type II, ISO 27001, HIPAA, and GDPR.
Enterprise pricing is a custom quote, and it comes with bigger annual discounts as you scale.
How Do You Migrate From Sendbird to Stream?
A migration should move not just the code from one SDK to the other, but the data too, meaning users, channels, messages, and reactions. Our migration guide spreads the work over four weeks:
- Share your chat design and export your Sendbird history, which depends on your Sendbird plan. Stream validates the import.
- Give Stream your Sendbird App ID and API token, and Stream turns on real-time sync. Stream queues each webhook payload and processes it in order.
- Integrate the Stream SDKs and roll out behind a feature flag. Our Sendbird migration skill can do the code swap for React, iOS, Android, React Native, and Flutter apps. It changes your code in place, keeps your app's structure, and checks the result against screenshots of your original UI.
- Export anything sent since sync began, then run QA and monitor.
The import takes JSON Lines, uploaded via our CLI or the dashboard, in this order: users, then channels, members, messages, and reactions. The guide maps a Sendbird group channel to our messaging type, an open channel to livestream, and admin messages to system messages.
Migration support is free for 30 days. The real-time sync in step 2 needs our Enterprise plan.
When Should You Pick Stream Over Sendbird?
Stream beats Sendbird for this build when you want the same channel and custom message model with more on one platform. That includes translation you can turn on yourself, activity feeds next to the chat, moderation for text, images, video, and audio under one policy, and published prices at 50,000 MAU. Stream's channels have no member cap, and its SDKs manage connections for you and let you customize the UI without forking.
Sendbird may be the better choice when support tickets in Desk are part of the product, or when you'll stay near 5,000 users, where Starter is cheaper.
Teams that switched gave reasons that match. Zoran Marjanovic, engineering manager at Pet Media Group, a pet adoption marketplace with about 250,000 MAU, said "our server with Sendbird was located in Japan" while its users were in the UK. Its NPS rose from 72 to 80 in the first month on Stream. Ghulam Zaky, product manager at Ajaib, said Sendbird "couldn't meet our budgetary needs" in contract talks. Ajaib also cited a 20,000-participant group cap and added Stream's Activity Feeds in the same project.
TalkJS: The Least Code
TalkJS has the least code of any hosted platform, but its contact masking only works if you build on its own UI components.
What Does It Take to Build on TalkJS?
To send your first message on TalkJS, all we did in the dashboard was sign up and copy the app ID and secret key. Among the hosted vendors, only Ably needed as little. Our code is about 275 lines, compared with about 390 for Sendbird.
async sendOrderCard(card: OrderCard) {
// TalkJS custom data is string-only, so the card is a JSON string under a `type` discriminator.
const conversation = requireConv();
const message = await conversation.send({
text: 'Order ' + card.orderId + ': ' + card.listingTitle,
custom: { type: 'order_card', card: JSON.stringify(card) },
});
await conversation.markAsRead().catch(() => {});
return { messageId: message.id };
},Custom data is string-only, so the card is serialized, the same as on Sendbird. The markAsRead call exists because the Data API has one read value, everyoneReadUntil, and it advances only after every participant, including the sender, has read a message. So the sender marks their own message as read.
The Data API also has no event for an edited message, so our code compares conversation snapshots to notice a card update. In TalkJS's own React components, a six-line Message override in the theme draws the card.
What Works on TalkJS, and What's Missing?
Contact masking is a role setting on every plan. With it on, TalkJS's UI components replace the phone number, email address, and Venmo with the text "(hidden information)".

That only happens inside TalkJS's UI. Our UI talks to TalkJS through its headless Data API, @talkjs/core, so Ben got every message exactly as Ana typed it. Sendbird's filter changes the message on the server, so whichever client reads it gets masked. If masking matters to you, build on TalkJS's components.
TalkJS differs from Sendbird in other ways, too:
- TalkJS has no report API and no moderation queue, so you collect and review user reports in your own system.
- Translation needs the Growth plan and your own Google Cloud Translation key.
- Read receipts through the Data API didn't always arrive. Tried on their own, they came every time. In two of the three times we played the whole conversation through, they hadn't arrived when we stopped waiting.
- Hosting is in the EU only.
For offline users, TalkJS does more than Sendbird, going by its docs. It sends email and SMS itself, and browser push goes through FCM with a service worker.
How Much Does TalkJS Cost?
Basic is $239 USD a month billed annually and includes 10,000 MAU. This build needs Growth for translation, which costs $499 USD and includes 25,000 MAU. TalkJS's calculator prices 50,000 MAU at $1,249 USD/month. Sendbird Pro is $1,399 USD at 25,000 and has no list price at 50,000. (Prices checked October 2026.)
When Should You Pick TalkJS Over Sendbird?
Use TalkJS over Sendbird for a web inbox built on TalkJS's own components. There, masking is a dashboard setting, read ticks are on by default, and TalkJS sends the email. For a custom UI on the Data API, as in our build, Sendbird was the better fit, because its filter runs on the server and its SDK tells you when a message is edited.
CometChat: The UI Kit First
CometChat includes translation and email notifications, but its phone detector blocked our order number, and its first paid plan leaves both features out.
What Does It Take to Build on CometChat?
CometChat's React UI kit uses plugins over message templates. A plugin names the message types it handles and returns the bubble for them. Ours is 14 lines, against the four-line renderMessage branch in Sendbird's UIKit.

The kit is the largest package we measured, at 1,655 KB gzipped, compared with 478 KB for Sendbird's. It requires CometChat's calls SDK as a peer dependency even if you never use voice and video calling.
Sending the card uses CometChat's custom message class:
async sendOrderCard(card: OrderCard) {
// CometChat's custom message: a type string plus any JSON in customData.
const message = new CometChat.CustomMessage(guid, CometChat.RECEIVER_TYPE.GROUP, 'order_card', { card });
message.setConversationText('Order ' + card.orderId + ': ' + card.listingTitle);
message.shouldUpdateConversation(true);
const sent = await CometChat.sendCustomMessage(message);
echo(sent);
return { messageId: String(sent.getId()) };
},Only the author, a group admin, or a moderator may edit a message. Any other member gets a 403, so our server edits the card as its author. CometChat's webhooks are protected by Basic Auth alone, with no signature to verify.
What Works on CometChat, and What's Missing?
Translation worked on the free Build plan through the Message Translation extension, but Sendbird needs Pro and a sales request. CometChat also sends email and SMS notifications, with email going through SendGrid or a webhook to your own mailer. Both are included in the free plan and Advanced, and neither is included in Basic.
Moderation uses a rules engine in the dashboard. We enabled Contact Details Removal and Email Detection and added a custom pattern list for payment and messaging apps. With everything on, all four messages we wanted stopped were blocked. Unfortunately, so was "Order 4155550134 arrived this morning, thanks." CometChat's phone detector read a bare 10-digit number as a phone number, whereas the regex we gave Sendbird requires separators and let it through.
If your order or tracking numbers are long strings of digits, test the detector before you rely on it. Custom messages also skip moderation by design, so the filter never sees text inside an order card. Reports are per message, through flagMessage, and reporting a user survives only as a legacy extension, where Sendbird has reportUser.
How Much Does CometChat Cost?
CometChat has no 5,000 MAU tier, and its free Build plan covers 100 MAU and 25 concurrent connections. The paid Basic plan leaves out translation and email notifications, so this build needs Advanced. Billed annually, that's $399 USD a month at 10,000 MAU, $949 USD at 25,000, and $1,649 USD at 50,000. Sendbird Pro is $579 USD at 10,000 MAU, $1,399 USD at 25,000, and a sales quote at 50,000. (Prices checked October 2026.)
When Should You Pick CometChat Over Sendbird?
CometChat beats Sendbird on price at 10,000 and 25,000 MAU, and it sends email and SMS notifications itself. Sendbird has a 5,000 MAU tier, a filter that didn't mistake our order number for a phone number, per-user reports, a React kit a third of the size, and Desk.
Twilio Conversations: When the Seller Replies by SMS
Twilio Conversations can bring SMS and WhatsApp into the order thread, and for in-app chat, you write the filter, translation, and UI yourself.
What Does It Take to Build on Twilio?
A Twilio message has no custom type. The card goes in the message's attributes, which hold 4 KB:
async sendOrderCard(card: OrderCard) {
// Twilio has no custom message type; structured data goes in the message's JSON attributes (4 KB limit).
const conversation = requireConv();
const index = await conversation
.prepareMessage()
.setBody('Order ' + card.orderId + ': ' + card.listingTitle)
.setAttributes({ type: 'order_card', card: { ...card } })
.build()
.send();
const page = await conversation.getMessages(1, index ?? undefined, 'backwards');
return { messageId: page.items[0].sid };
},send() resolves with the message's index in the conversation, so our code fetches the message back to get the SID that our server uses for edits. There's no supported UI kit, only demo apps, so our own component draws the card.
Twilio now calls this product Conversations (classic), and its main Conversations docs page describes a new agentic platform. The classic page has no end-of-life notice, though, and the JavaScript SDK shipped a new version in September 2026.
What Works on Twilio, and What's Missing?
On Twilio, a phone number can participate in the order conversation over SMS or WhatsApp. So if Ben has the app closed, his text reply still lands in the thread Ana is reading. We wrote the code for this but didn't run it because it requires a rented phone number and message fees.
On Sendbird, SMS and WhatsApp are part of Business Messaging, a product for campaigns sent from the dashboard. For in-app messaging, though, Twilio is missing things Sendbird has:
- Twilio has no content filter and no report API. What it has is a pre-event webhook that calls your server before a message is published, where you can rewrite or reject it.
- Twilio doesn't translate messages.
- Read receipts are a read horizon that the client advances itself with
setAllMessagesRead. The SDK never does it for you.
We ran our three regular expressions in that webhook, and they stopped all four messages and let the order number through. That's our filter running on our server, so you write and host it yourself on Twilio. Twilio needs a public URL to call, and if your server takes longer than five seconds, it publishes the message unchanged.
For offline users, Twilio's docs describe browser push via FCM, with no email.
How Much Does Twilio Conversations Cost?
Pricing is per user. The first 200 users are free, and the next 4,800 cost $0.05 USD each, which is $240 USD/month at 5,000 MAU, versus $499 USD for Sendbird Pro. We estimate $477.50 USD at 10,000 and $927.50 USD at 20,000, assuming each lower rate applies only to the users in its tier, which the page doesn't say. Beyond 20,000, pricing is custom, and SMS, MMS, and WhatsApp fees are extra. (Prices checked October 2026.)
When Should You Pick Twilio Over Sendbird?
Pick Twilio over Sendbird when a buyer or seller has to answer by SMS or WhatsApp in the same thread. If everyone stays in your app, Sendbird gives you a filter, a report API, a UI kit, and translation on Pro, while Twilio leaves all four to you.
PubNub: Blocked on a New Account
PubNub is the pick when chat shares a connection with live location or device data, but it has no filter, no translation, and no maintained React kit.
What Does It Take to Build on PubNub?
The keys worked, and publish, subscribe, and Access Manager grants all succeeded. But the Chat SDK depends on App Context, and the keyset page on our new account replaced it with DataSync. With DataSync on, the App Context API still returned a 403 with "App Context is not enabled for this subscribe key", and the error says to contact PubNub support.
So, on a new PubNub account like ours, expect to open a support ticket before the Chat SDK works. Sending the card would be one call:
async sendOrderCard(card: OrderCard) {
// PubNub Chat SDK: a text message whose `meta` carries the structured card.
const result = await channel!.sendText('Order ' + card.orderId + ': ' + card.listingTitle, {
meta: { type: 'order_card', card },
storeInHistory: true,
});
return { messageId: String(result.timetoken) };
},Updating the card takes more code than on Sendbird. A published message's meta can't be rewritten, so your server attaches a message action that holds the new card, and every client has to prefer the newest action over the original. That's about 30 lines in our client code. On Sendbird, the order service edits the message and the SDK delivers the new version.
There's no React kit to start from. PubNub archived its React chat components in June 2026, after ending support for them in January 2025. The Chat SDK, released in May 2026, also renamed most streaming APIs, so published examples no longer match.
What Would You Get From PubNub's Chat SDK?
This is what PubNub's docs say the Chat SDK gives you, and what you'd write yourself:
| What the chat needs | From the Chat SDK | What you write |
|---|---|---|
| Order channel | createGroupConversation with your own channel ID and custom data |
Nothing |
| Order card | meta on the message |
The update, as a message action that carries the new card |
| Contact filter and reports | message.report() |
The filter, as a Before Publish Function with your own regular expressions |
| Offline notification | Mobile push through FCM and APNs | A webhook from Events & Actions to your own notifier |
| Translation | Nothing | All of it, by calling a translation service yourself |
| Bringing in support | channel.invite() |
The access token grant that allows it |
| Receipts, typing, files | All three | Paging, because history returns 25 messages per call |
Custom data on channels and users is scalar-only, and the moderation dashboard (Channel Monitor) is paid.
The reason to accept that extra work is that the same connection carries any other real-time messaging you need, such as a courier's location. We wrote a courier demo that would publish its coordinates once a second on a second channel while the order page moves a marker. It hasn't run either.
How Much Does PubNub Cost?
PubNub's pricing page gives estimates from a slider. For the Pro plan, it shows $370 USD/month at 5,000 MAU, $1,130 USD at 25,000, and $1,850 USD at 43,750, the last value on the slider. Those estimates are below Sendbird Pro: $499 USD at 5,000 MAU and $1,399 USD at 25,000. Pricing at 50,000 MAU is custom. The free plan covers 200 MAU or 1 million transactions. (Prices checked October 2026.)
When Should You Pick PubNub Over Sendbird?
PubNub is the better choice when chat shares a connection with location or device data. For chat alone, Sendbird already has card edits, filtering, and the kit, and its SDK runs on a trial account.
Ably: Documented Delivery Guarantees
Ably puts delivery guarantees in writing and handles the order card, leaving member lists, read receipts, and file uploads to you.
What Does It Take to Build on Ably?
The order card needs nothing extra. Messages take metadata, and messages.update changes a sent message and notifies both clients.
async sendOrderCard(card: OrderCard) {
// Ably Chat: `metadata` is the documented home for structured data; headers are for routing, not content.
const message = await room!.messages.send({
text: 'Order ' + card.orderId + ': ' + card.listingTitle,
metadata: { type: 'order_card', card: { ...card } },
headers: { orderId: card.orderId },
});
return { messageId: message.serial };
},A default API key can't update another client's message, so have your server edit the card as its author. Ours didn't at first, and the update failed with error 40160: "client is not message owner."

There is a React UI kit, which Ably labels experimental. It has no custom message renderer, so we put the order card in the header slot.
What Works on Ably, and What's Missing?
Ably documents guarantees for message order and delivery. They matter for an order card because if two status updates arrive out of order, the card shows paid after it showed shipped. Messages published over a real-time connection reach a channel's subscribers in the order they were published; idempotent publishing removes duplicates within a two-minute window; and a client that reconnects within about two minutes gets what it missed.
We didn't measure any of that, on Ably or anywhere else. These are Ably's own claims.
Ably Chat is missing things that a Sendbird channel has:
- A room has no member list and no metadata. A user can enter a room when their token allows it, so we keep the list of who belongs to each order on our own server and use presence alongside it.
- Read receipts aren't part of the Chat SDK. We added them by including each user's last read message in presence data, which is our code, not Ably's.
- There's no file upload or storage, so we sent the test image inline as a data URL.
- Ably has no translation.
- User reporting, muting, and banning are marked as coming soon on Ably's pricing page.
We couldn't filter messages at all. Ably's docs describe a before-publish rule that calls your server or a vendor such as Hive, Tisane, Bodyguard, or Azure, and the pricing page lists moderation integrations on every plan. Our free app's Integrations page didn't offer one, so all four messages went through. Anyone who needs moderation on Ably should confirm it can be turned on for their account before building on it.
For offline users, Ably's docs describe browser push through the Web Push API, with keys Ably generates and no Firebase project. There's no email.
How Much Does Ably Cost?
Ably charges a package fee plus usage. Usage is $2.50 per million messages, $1 per million connection minutes, and $1 per million channel minutes. Six months of history requires the Pro package at $399 USD a month, because Standard, at $29 USD, keeps messages for 30 days.
Ably's own support chat example assumes five conversations per user a month, each with 20 messages and 20 minutes. At that usage, Pro comes to about $406 USD at 5,000 MAU and $452 USD at 50,000. Stored messages, typing indicators, and presence events also count as messages and aren't included in that example, so the real figure would be higher. Sendbird Pro is $499 USD at 5,000 MAU and a sales quote at 50,000. (Prices checked October 2026.)
When Should You Pick Ably Over Sendbird?
Choose Ably over Sendbird when you already plan to build every screen and want delivery behavior in writing. Sendbird is better for a team that already wants members, receipts, uploads, and reporting.
Rocket.Chat: Self-Hosted
Rocket.Chat is the one to choose when messages can't leave your servers, but its built-in filter only matches words, and read receipts require a registered server.
What Does It Take to Build on Rocket.Chat?
Rocket.Chat is a different kind of product from the other six. The others are SDKs you build a chat with, and Rocket.Chat is a finished chat application that you run and then embed.
We ran version 8.9.0 with MongoDB 8.2 in Docker Compose. That's about 3 GB of images and two containers that used 690 MiB and 253 MiB of memory with no traffic. We had to override 30 settings in the compose file before our React app could talk to it, including CORS, which is off by default.
We skipped the setup wizard, which left the server unregistered and on what Rocket.Chat calls Community limits. Registering with a Rocket.Chat Cloud account is free and gives the Starter plan, which covers 50 users and includes read receipts, the Apps-Engine, and end-to-end encryption.
There's no client SDK to install with npm. The official @rocket.chat/ddp-client depends on a package that uses a Yarn-only patch: protocol, and npm refuses it. Our client code speaks Rocket.Chat's DDP protocol over the browser's WebSocket in about 70 lines and sends the card over REST:
async sendOrderCard(card: OrderCard) {
// Rocket.Chat: customFields carry the structured card (schema-validated); the attachment is what
// Rocket.Chat's own UI renders, since it has no custom message components.
const { message } = await rest<{ message: RcMessage }>('POST', 'chat.sendMessage', {
message: {
rid,
msg: 'Order ' + card.orderId + ': ' + card.listingTitle,
customFields: { type: 'order_card', card: JSON.stringify(card), orderId: card.orderId },
// Rocket.Chat rejects relative hrefs in attachments, so the image URL must be absolute.
attachments: [{ title: card.listingTitle, text: '\u0024' + card.priceUsd + ' USD · ' + card.status, image_url: new URL(card.imageUrl, window.location.origin).href }],
},
});
return { messageId: message._id };
},The prebuilt UI loads in your app as an iframe with ?layout=embedded, logged in with a token passed via postMessage. It has no custom message components, so the order card shows up as a message attachment, and the iframe loaded 2.8 MB of JavaScript from the Rocket.Chat server.

What Works on Rocket.Chat, and What's Missing?
The order channel, the order card, and bringing in Sam all worked on our unregistered server. So did translation, through the built-in Auto-Translate with a Google key, and a self-hosted LibreTranslate is another option. Rocket.Chat's support product, Omnichannel, uses a separate visitor model, so bringing in Sam meant inviting him to the group.
The filter is weaker than Sendbird's. It's a word list, so it stopped one of our four messages and let both phone numbers and the email address through. A regex filter means writing an Apps-Engine app, and an unregistered server can't install one.
When we asked the server for read receipts, it answered, "This is an enterprise feature." Typing and image upload worked. Starter includes read receipts, so a registered server should have them.
Rocket.Chat's docs cover email to offline users, sent through your own SMTP server. There's no browser push.
How Much Does Rocket.Chat Cost?
Starter is free for up to 50 users. Past that, Rocket.Chat's paid plans are all sold through sales, so at 50,000 MAU the price is a quote plus the servers. (Prices checked October 2026.)
When Should You Pick Rocket.Chat Over Sendbird?
The reason to choose Rocket.Chat over Sendbird is where the data is stored. For buyer-seller chat inside a consumer app, it took the most code of any vendor build, just over 400 lines, plus 30 settings.
Building a Marketplace Chat Natively, With No Vendor
Our no-vendor build did everything we tested in about 380 lines of server code, but it has no push delivery, bans, search, or retention policy.
What Does It Take to Build Chat Without a Vendor?
The no-vendor build is real-time messaging written from scratch. Two identical Node WebSocket servers share a Postgres database, Redis pub/sub carries every event between them, and the browser code speaks a JSON protocol over the WebSocket API with no third-party code. Ana connects to one server and everyone else to the other, so every message crosses both.
The client code is 3 KB gzipped. This is the function that sends the card:
async sendOrderCard(card: OrderCard) {
// Our own protocol: a message of kind "order_card" whose data holds the card.
const clientId = 'c' + Date.now() + '_' + ++seqCounter;
const sent = await request<WireMessage>(
{ type: 'send', channelId, kind: ORDER_CARD_TYPE, data: { card }, clientId },
'sent:' + clientId,
);
return { messageId: sent.id };
},The code is short, but the filter had several problems:
- The first time we turned on translation, a phone number slipped past the filter. The Spanish translation of
Call me at 415-555-0134still included the number, so the server now masks translations using the same patterns as the original text. - The order number
4155550134only gets through because our phone pattern requires separators. Loosen the pattern to catch phone numbers typed without separators, and it masks order numbers like this one too. - Our phone pattern masks the first half of the UK number, leaving
900123on screen. It did the same on Sendbird and Twilio, which ran the same phone pattern.
Six days in, the WebSocket servers rejected every connection. Both had started before a config change and were still verifying tokens with the old secret; the first restart failed silently because the old processes held the ports.
What Does the No-Vendor Build Do, and What's Missing?
The order channel, the card, the filter, reports, translation, bringing in Sam, read receipts, typing, and image uploads all work. Translation goes through Google Translate.
The build has no way to fetch missed messages when a client reconnects, no read state per device, no push delivery, no bans, mutes, or rate limits, no moderation queue, no search, no retention policy, and no GDPR deletion. Presence is one Redis key per user with a six-second expiry, which is fine for three users.
How Much Does It Cost Without a Vendor?
Building it yourself means no per-MAU bill, no plan tiers, and no vendor terms. You pay for the servers, Node, Postgres, and Redis (in our build), and the engineers who build and run it. We didn't put a number on either.
When Should You Build It Yourself?
Building it yourself makes sense when chat is small and fixed, or when your requirements are unusual enough that no vendor model fits. For a marketplace at 50,000 MAU, your team still has to build and run everything this build leaves out.
Pricing Per Month at 10,000 MAU
Each vendor has a different pricing structure, so we picked 10,000 MAU because it's the smallest size where every vendor that publishes a price has one. Prices are billed annually when the vendor offers them, and two prices in a row are two plan tiers.
| Platform | Tiers at 10,000 MAU | For this build |
|---|---|---|
| Sendbird | Starter $429 USD, Pro $579 USD | Pro, for translation |
| Stream | Start $399 USD, Elevate $599 USD | Elevate, for translation |
| TalkJS | Basic $239 USD, Growth $499 USD | Growth, for translation |
| CometChat | Basic $299 USD, Advanced $399 USD | Advanced, for translation and email |
| Twilio Conversations | About $475 USD, charged per user | The same, with no translation to add |
| PubNub | About $550 USD, a Pro estimate from the pricing slider | Pro, and the build didn't run |
Ably charges a $399 USD package fee for six months of history, plus usage per message and per connection minute, not per MAU, so it doesn't fit in this table. Rocket.Chat's Starter plan is free up to 50 users; above that, the price is a quote plus your own servers.
With translation, the four platforms are within $200 USD of each other at this size. The tiers don't include the same things. Stream's smallest plan covers 10,000 MAU, while Sendbird Starter covers 5,000, TalkJS Growth covers 25,000, and CometChat Basic leaves out translation and email notifications.
Choosing for Your Own Build
Start with what your chat has to do, not with the vendors. Our list had seven items, and one of them, translation, set the plan on every platform that has it. Yours may be different, but there will be one. Find the tier that includes it on each platform you're considering, because in our build, no entry tier did.
Then price that tier at the number of monthly active users you expect in a year, not the number you have today. The tiers bundle different amounts of MAU, and published prices run out at different sizes. If a platform has no list price at your size, you're negotiating, and it's better to know that before you build.
Settle whether you'll build on the vendor's UI components or your own before you compare features, because a feature can work in one and be missing from the other. And if a seller will answer via text message instead of the app, the platform has to support that too.
Stream as a Sendbird Alternative
If Stream is on your shortlist, the Build plan is free up to 1,000 MAU with no credit card, and the build's code is public, so you can run it against a Build app before writing any of your own. The Sendbird migration guide covers export, import, and webhook sync.
