Twilio vs Vonage vs Stream: Communication APIs and Chat SDKs (2026)
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
Methodology
Feature comparison based on twilio v5.x, @vonage/server-sdk v3.x, and stream-chat v8.x as of March 2026.