TL;DR
Twilio is the full communications platform — SMS, voice, video, email (SendGrid), WhatsApp, programmable messaging, the industry standard for communication APIs. Vonage (formerly Nexmo) is the multi-channel communications API — SMS, voice, video, verify (2FA), conversations, competitive pricing. Stream is the chat and activity feed SDK — pre-built chat UI components, real-time messaging, activity feeds, moderation, purpose-built for in-app chat. In 2026: Twilio for full communications (SMS + voice + video), Vonage for multi-channel messaging at competitive pricing, Stream for building in-app chat and activity feeds.
Key Takeaways
- Twilio: twilio SDK ~2M weekly downloads — SMS, voice, video, email, WhatsApp
- Vonage: @vonage/server-sdk ~200K weekly downloads — SMS, voice, verify, video
- Stream: stream-chat ~150K weekly downloads — in-app chat, UI components, moderation
- Twilio is the most comprehensive but also the most expensive
- Vonage offers similar features with competitive pricing
- Stream is purpose-built for chat — not a general communications platform
Twilio
Twilio — full communications platform:
Send SMS
import twilio from "twilio"
const client = twilio(
process.env.TWILIO_ACCOUNT_SID,
process.env.TWILIO_AUTH_TOKEN
)
// Send SMS:
const message = await client.messages.create({
body: "Your package react@19.0.0 has been published!",
from: "+15551234567",
to: "+15559876543",
})
console.log(`Message SID: ${message.sid}`)
console.log(`Status: ${message.status}`)
Voice call
import twilio from "twilio"
const client = twilio(
process.env.TWILIO_ACCOUNT_SID,
process.env.TWILIO_AUTH_TOKEN
)
// Make a call:
const call = await client.calls.create({
url: "https://api.pkgpulse.com/twiml/alert",
to: "+15559876543",
from: "+15551234567",
})
// TwiML response (Express):
app.post("/twiml/alert", (req, res) => {
const VoiceResponse = twilio.twiml.VoiceResponse
const response = new VoiceResponse()
response.say({ voice: "alice" },
"Alert: a critical security vulnerability was found in your dependencies."
)
response.pause({ length: 1 })
response.say("Press 1 to acknowledge, or 2 to escalate.")
response.gather({
numDigits: 1,
action: "/twiml/handle-input",
})
res.type("text/xml")
res.send(response.toString())
})
Verify (2FA)
import twilio from "twilio"
const client = twilio(
process.env.TWILIO_ACCOUNT_SID,
process.env.TWILIO_AUTH_TOKEN
)
// Send verification code:
const verification = await client.verify.v2
.services(process.env.TWILIO_VERIFY_SID!)
.verifications.create({
to: "+15559876543",
channel: "sms", // "sms", "call", "email", "whatsapp"
})
console.log(`Status: ${verification.status}`) // "pending"
// Check verification code:
const check = await client.verify.v2
.services(process.env.TWILIO_VERIFY_SID!)
.verificationChecks.create({
to: "+15559876543",
code: "123456",
})
console.log(`Verified: ${check.status}`) // "approved" or "pending"
import twilio from "twilio"
const client = twilio(
process.env.TWILIO_ACCOUNT_SID,
process.env.TWILIO_AUTH_TOKEN
)
// Send WhatsApp message:
await client.messages.create({
body: "Your PkgPulse weekly report is ready!",
from: "whatsapp:+14155238886",
to: "whatsapp:+15559876543",
})
// Send WhatsApp template:
await client.messages.create({
from: "whatsapp:+14155238886",
to: "whatsapp:+15559876543",
contentSid: "HXXXXXXXXXXXXXXXXXXX", // Template SID
contentVariables: JSON.stringify({
"1": "react",
"2": "19.0.0",
}),
})
Vonage
Vonage — multi-channel communications:
Send SMS
import { Vonage } from "@vonage/server-sdk"
const vonage = new Vonage({
apiKey: process.env.VONAGE_API_KEY!,
apiSecret: process.env.VONAGE_API_SECRET!,
})
// Send SMS:
const response = await vonage.sms.send({
to: "15559876543",
from: "PkgPulse",
text: "Your package react@19.0.0 has been published!",
})
console.log(`Message ID: ${response.messages[0]["message-id"]}`)
console.log(`Status: ${response.messages[0].status}`)
Voice call
import { Vonage } from "@vonage/server-sdk"
const vonage = new Vonage({
applicationId: process.env.VONAGE_APP_ID!,
privateKey: process.env.VONAGE_PRIVATE_KEY!,
})
// Make a call:
const response = await vonage.voice.createOutboundCall({
to: [{ type: "phone", number: "15559876543" }],
from: { type: "phone", number: "15551234567" },
ncco: [
{
action: "talk",
text: "Alert: a critical vulnerability was found in your dependencies.",
voiceName: "Amy",
},
{
action: "input",
type: ["dtmf"],
dtmf: { maxDigits: 1 },
eventUrl: ["https://api.pkgpulse.com/vonage/input"],
},
],
})
console.log(`Call UUID: ${response.uuid}`)
Verify (2FA)
import { Vonage } from "@vonage/server-sdk"
import { Auth } from "@vonage/auth"
const vonage = new Vonage(new Auth({
apiKey: process.env.VONAGE_API_KEY!,
apiSecret: process.env.VONAGE_API_SECRET!,
}))
// Start verification:
const response = await vonage.verify.start({
number: "15559876543",
brand: "PkgPulse",
code_length: 6,
})
const requestId = response.request_id
// Check code:
const check = await vonage.verify.check(requestId, "123456")
if (check.status === "0") {
console.log("Verification successful!")
} else {
console.log("Verification failed:", check.error_text)
}
Messages API (multi-channel)
import { Vonage } from "@vonage/server-sdk"
const vonage = new Vonage({
applicationId: process.env.VONAGE_APP_ID!,
privateKey: process.env.VONAGE_PRIVATE_KEY!,
})
// SMS via Messages API:
await vonage.messages.send({
message_type: "text",
text: "Package update available!",
to: "15559876543",
from: "15551234567",
channel: "sms",
})
// WhatsApp:
await vonage.messages.send({
message_type: "text",
text: "Your weekly report is ready!",
to: "15559876543",
from: "14155238886",
channel: "whatsapp",
})
// Viber:
await vonage.messages.send({
message_type: "text",
text: "New version alert!",
to: "15559876543",
from: "PkgPulse",
channel: "viber_service",
})
Stream
Stream — in-app chat and feeds:
Chat client setup
import { StreamChat } from "stream-chat"
// Server-side (generate tokens):
const serverClient = StreamChat.getInstance(
process.env.STREAM_API_KEY!,
process.env.STREAM_API_SECRET!
)
// Create user token:
const token = serverClient.createToken("user-123")
// Client-side:
const client = StreamChat.getInstance(process.env.STREAM_API_KEY!)
await client.connectUser(
{ id: "user-123", name: "Royce", image: "/avatar.png" },
token
)
Channels and messaging
import { StreamChat } from "stream-chat"
const client = StreamChat.getInstance(apiKey)
// Create channel:
const channel = client.channel("messaging", "project-pkgpulse", {
name: "PkgPulse Team",
members: ["user-123", "user-456", "user-789"],
image: "/team-avatar.png",
})
await channel.create()
// Send message:
await channel.sendMessage({
text: "New version of react just dropped! 🎉",
user_id: "user-123",
})
// Send with attachment:
await channel.sendMessage({
text: "Check out this comparison chart",
attachments: [{
type: "image",
image_url: "https://pkgpulse.com/charts/react-vs-vue.png",
title: "React vs Vue Downloads",
}],
})
// React to message:
await channel.sendReaction(messageId, {
type: "like",
user_id: "user-456",
})
React components
import {
Chat, Channel, ChannelList, ChannelHeader,
MessageList, MessageInput, Thread, Window,
} from "stream-chat-react"
import { StreamChat } from "stream-chat"
import "stream-chat-react/dist/css/v2/index.css"
function ChatApp() {
const client = StreamChat.getInstance(apiKey)
return (
<Chat client={client} theme="str-chat__theme-dark">
<ChannelList
filters={{ type: "messaging", members: { $in: ["user-123"] } }}
sort={{ last_message_at: -1 }}
/>
<Channel>
<Window>
<ChannelHeader />
<MessageList />
<MessageInput />
</Window>
<Thread />
</Channel>
</Chat>
)
}
Moderation and events
import { StreamChat } from "stream-chat"
const client = StreamChat.getInstance(apiKey, apiSecret)
// Auto-moderation:
await client.updateAppSettings({
auto_moderation_enabled: true,
auto_moderation_config: {
platform: "AI",
rules: [
{
action: "flag",
word_list: ["spam", "abuse"],
},
],
},
})
// Ban user:
await client.banUser("spam-user", {
banned_by_id: "admin-123",
reason: "Spam messages",
timeout: 60 * 24, // 24 hours
})
// Listen for events:
channel.on("message.new", (event) => {
console.log("New message:", event.message?.text)
})
channel.on("typing.start", (event) => {
console.log(`${event.user?.name} is typing...`)
})
channel.on("message.read", (event) => {
console.log(`${event.user?.name} read messages`)
})
Feature Comparison
| Feature | Twilio | Vonage | Stream |
|---|---|---|---|
| SMS | ✅ | ✅ | ❌ |
| Voice calls | ✅ | ✅ | ❌ |
| Video | ✅ | ✅ | ❌ |
| ✅ | ✅ | ❌ | |
| In-app chat | ✅ (Conversations) | ✅ (Conversations) | ✅ (core feature) |
| Chat UI components | ❌ | ❌ | ✅ (React, React Native) |
| Activity feeds | ❌ | ❌ | ✅ |
| Moderation | ❌ | ❌ | ✅ (AI-powered) |
| 2FA/Verify | ✅ | ✅ | ❌ |
| ✅ (SendGrid) | ❌ | ❌ | |
| Webhooks | ✅ | ✅ | ✅ |
| TypeScript | ✅ | ✅ | ✅ |
| Pricing model | Per-message/minute | Per-message/minute | Per MAU |
| Free tier | Trial credits | Trial credits | 25 MAU |
When to Use Each
Use Twilio if:
- Need a full communications platform (SMS + voice + video + email)
- Building notification systems with multiple channels
- Need WhatsApp Business API
- Want the largest developer ecosystem and documentation
Use Vonage if:
- Need SMS, voice, and video at competitive pricing
- Building multi-channel messaging (SMS, WhatsApp, Viber)
- Need phone verification (Verify API)
- Want a Twilio alternative with lower costs
Use Stream if:
- Building in-app chat or messaging features
- Want pre-built React chat UI components
- Need activity feeds (social, notification feeds)
- Want AI-powered moderation and real-time typing indicators
Migration Guide
From Twilio to Vonage for SMS
The SDK structure is similar; the main difference is authentication (Twilio uses accountSid + authToken, Vonage uses apiKey + apiSecret):
// Twilio (old)
import twilio from "twilio"
const client = twilio(process.env.TWILIO_SID, process.env.TWILIO_TOKEN)
await client.messages.create({ from: "+15551234567", to: "+15559876543", body: "Package update" })
// Vonage (new)
import { Vonage } from "@vonage/server-sdk"
const vonage = new Vonage({ apiKey: process.env.VONAGE_KEY!, apiSecret: process.env.VONAGE_SECRET! })
await vonage.sms.send({ from: "PkgPulse", to: "15559876543", text: "Package update" })
Vonage supports alphanumeric sender IDs in more countries without Twilio's US carrier registration requirements, making SMS migration attractive for international applications.
Community Adoption in 2026
Twilio is the dominant communications platform globally, with over 300,000 active customer accounts. Its SendGrid email acquisition makes it the only option here that covers SMS, voice, video, and email in a unified platform. The Node.js SDK (twilio) downloads around 600,000 per week.
Vonage (Vonage API Platform, formerly Nexmo) serves as the primary competitive alternative to Twilio, particularly in European markets where it has stronger regional SMS routing. Its @vonage/server-sdk reaches approximately 150,000 weekly downloads. Teams choosing Vonage typically cite pricing for high-volume SMS or international sending coverage.
Stream serves a fundamentally different market — it is the leading dedicated in-app chat infrastructure provider, powering chat in over 1 billion end users' apps as of 2026 (per their public metrics). stream-chat npm reaches approximately 200,000 weekly downloads. Its React component library (stream-chat-react) is the most complete pre-built chat UI available as an npm package, covering typing indicators, reactions, threads, and file attachments out of the box.
Pricing Model and Cost Scaling
The pricing structures of these three platforms diverge significantly at scale, and understanding the differences is essential for making a cost-effective choice.
Twilio's pricing is usage-based with no minimum commitment: SMS messages cost $0.0079 each (US), voice calls cost $0.0140/minute, and video calls cost $0.0015/participant-minute. Programmable Chat (now Conversations) charges $0.05 per active user per month. Twilio's pricing is predictable for low-volume use but can become expensive for high-volume messaging or large concurrent video sessions. Twilio offers volume discounts through committed usage plans starting at ~$1,000/month.
Vonage (now Vonage by Ericsson) prices competitively with Twilio, often 10-20% lower for SMS and voice. The Messages API charges $0.0065 per SMS outbound (US), with WhatsApp Business messages costing $0.0058-$0.0085 per conversation depending on category. Vonage's pricing advantage is most pronounced in international SMS, where its direct carrier relationships produce lower costs than Twilio in several European and Asian markets.
Stream uses a per-MAU (Monthly Active User) model for its Chat product: $499/month for up to 10,000 MAUs, then $0.04-$0.06 per additional MAU depending on tier. Activity Feeds are priced separately at $399/month for up to 1 million operations. This model is predictable for products with a stable user base but can produce billing surprises if a feature goes viral. Stream's free tier (25 MAUs) is generous for prototyping.
The critical difference: Twilio and Vonage charge per-message/per-minute, making them cost-effective for businesses sending occasional high-value communications (2FA, transactional SMS, support calls). Stream charges per-user, making it cost-effective for products with many users who each send relatively few messages within your own application. A product with 50,000 MAUs who each send 100 chat messages per month is far cheaper on Stream than on Twilio's Conversations. The same product sending 50,000 SMS notifications per month is cheaper on Twilio or Vonage than on any platform charging per-user minimums.
For applications that need both transactional messaging (SMS, email) and in-app communication, many teams combine Twilio or Vonage for external channel delivery with Stream for internal product chat — using each service for what it does best. This hybrid approach avoids the higher per-MAU cost of Stream for simple SMS notifications while avoiding the complexity of building in-app chat infrastructure on top of Twilio's lower-level primitives. The architectural boundary is clean: external communications (notifications, 2FA, customer support) route through Twilio or Vonage, while internal product features (team chat, activity feeds, comments) route through Stream.
TypeScript SDK Quality and Developer Experience
The TypeScript quality of these SDKs has significant practical impact on developer productivity. Twilio's TypeScript SDK (twilio v5.x) ships comprehensive types with accurate generics for all resource properties. The SDK's auto-generated nature from Twilio's OpenAPI spec means types stay synchronized with the API, but the generated interface names can be verbose — working with TwilioMessage properties requires navigating through several layers of type aliases. Autocomplete works well in practice, and the JSDoc comments on key methods include parameter descriptions and example values.
Vonage's @vonage/server-sdk (v3.x) provides modular TypeScript types per-service — the SMS types are in @vonage/sms, voice types in @vonage/voice, and so on. This modular approach keeps per-feature import sizes small but requires knowing which sub-package contains the type you need. The types are accurate and well-maintained, with the added benefit that Vonage's SDK is open-source and accepts community contributions for type improvements.
Stream's stream-chat TypeScript types are the most ergonomic of the three for the specific use case of building chat UIs. The generic types on StreamChat<AttachmentType, ChannelType, MessageType> allow customizing the type of custom data attached to messages, channels, and users — a powerful feature for applications that embed domain-specific metadata in chat objects. The React UI component library (stream-chat-react) is fully typed and integrates tightly with the SDK types, so props for components like MessageList and MessageInput are correctly inferred from the channel type parameter.
Methodology
Feature comparison based on twilio v5.x, @vonage/server-sdk v3.x, and stream-chat v8.x as of March 2026.
Compare communication and developer tooling on PkgPulse →
See also: AVA vs Jest and Payload CMS vs Strapi vs Directus, amqplib vs KafkaJS vs Redis Streams.