One API for
every messaging channel.
Msgly collapses 41 platforms — chat, email, SMS, push and voice — into a single unified interface. Register the adapters you need, send and receive in one format, and stop learning a new webhook payload every quarter.
Why msgly
Multi-channel shouldn't mean multi-codebase.
Building a chatbot or notification system that works across channels means learning many different APIs, webhook formats, signature schemes and media rules. Msgly gives you one TypeScript-native interface over all of them — with retries, idempotency, capability checks and rate limiting already handled.
Channel names are open: ChannelName accepts any string, so you can publish a third-party adapter without waiting on a core release. Built-in names keep editor autocomplete.
import express from 'express';
import { createHub } from '@msgly/core';
import { createTelegramAdapter } from '@msgly/telegram';
import { createWhatsAppAdapter } from '@msgly/whatsapp';
const hub = createHub();
hub.register(createTelegramAdapter({
botToken: process.env.TELEGRAM_BOT_TOKEN!,
webhookSecret: process.env.TELEGRAM_WEBHOOK_SECRET!,
}));
hub.register(createWhatsAppAdapter({
phoneNumberId: process.env.WA_PHONE_ID!,
accessToken: process.env.WA_TOKEN!,
appSecret: process.env.META_APP_SECRET!,
verifyToken: process.env.META_VERIFY_TOKEN!,
}));
// Verify credentials at startup — fail fast on bad tokens
await hub.connect({ throwOnFailure: true });
hub.on('message', async (msg) => {
if (msg.content.type === 'text') {
await hub.send({
channel: msg.channel,
account: msg.account,
contact: msg.contact,
content: { type: 'text', text: `You said: ${msg.content.text}` },
});
}
});
const app = express();
const handlers = hub.createWebhookHandler();
app.get('/webhook/:channel', handlers.get);
app.post('/webhook/:channel', handlers.post);
app.listen(3000);Every module
41 adapters, one contract.
Each channel is its own package, so you install only what you ship. Every adapter implements the same Adapter interface and brings its own credential check.
Telegram
Bot API, inline keyboards, and channel posting via @name
Cloud Business API; approved MARKETING templates for campaigns
Messenger
Meta Send API, plus publishPost() to the Page feed
Direct messages, plus publishPost() for feed posts and Reels
LINE
broadcast() to all friends, multicast() to a segment
Discord
HTTP Interactions with buttons and message components
Microsoft Teams
Bot Framework channel with Adaptive Card support
Slack
Events API and Block Kit; posts to a channel, not to users
massSend() to all followers or a tag group, quota-metered
Viber
broadcast() to up to 300 subscribers per call
Mattermost
Self-hosted chat over REST and outgoing webhooks
Rocket.Chat
Self-hosted chat via REST plus outgoing webhooks
Google Chat
Service-account auth with Google-signed webhooks
Gmail
Pub/Sub push delivery and MIME attachment handling
Outlook / M365
Graph change notifications and attachment handling
SMTP / IMAP
Yahoo, Zoho, Fastmail or any custom mail server
Resend
Transactional email over HTTP, Edge-compatible
Mailgun
Transactional email, inbound routes and signed event webhooks
Postmark
Transactional email with message streams and bounce webhooks
SendGrid
Inbound Parse and ECDSA-signed event webhooks
Amazon SES
High-volume email, SigV4 with SNS bounce handling
Twilio SMS
SMS and MMS, with media attachments and receipts
Exotel
India-focused SMS with DLT template compliance
MSG91
India SMS through the DLT Flow API, template-first
Vonage
Global SMS delivery with signed inbound webhooks
Plivo
Global SMS and MMS with V3 signature verification
Telnyx
Global SMS and MMS, Ed25519-signed webhooks
Dial
Agent-provisioned numbers — SMS, MMS and iMessage with HMAC-signed webhooks
Genesys SMS
Genesys Cloud CX contact-center SMS, OAuth2 client-credentials, JSON notification webhooks
Twilio Voice
TwiML flows, Gather input and outbound calls
Subreddit posts, thread replies and inbox polling
TikTok
Video and photo publishing, comment replies and DMs
FCM
Push to Android, iOS and web, plus topic broadcast
APNs
Apple push direct — iOS, macOS and Safari, no Firebase in the path
Web Push
Browser notifications over VAPID, encrypted per subscription
Expo Push
React Native push via Expo, with ticket and receipt tracking
RCS
Branded rich cards and suggestion chips via Twilio, with SMS fallback
Plivo Voice
IVR and outbound calls, sharing the Plivo SMS credentials
Vonage Voice
NCCO-driven IVR and outbound calls
Exotel Voice
India click-to-call and App Bazaar flow dialling
Genesys Voice
Genesys Cloud CX telephony via the Conversations API — no inline IVR markup
Core
Hub, adapter contract, retries, storage, campaigns
Neither has a usable API for this — LinkedIn's messaging API is partner-gated, and automating the web UI violates their terms and gets accounts banned. iMessage is reachable through @msgly/dial, on a Dial-provisioned number.
What each channel can actually carry
Capabilities are not uniform, and pretending otherwise is how a send fails in production. Every adapter declares what it supports; the hub refuses anything else up front.
| Content | Interactive | Conversation | |||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Channel | text | image | video | audio | file | buttons | quick replies | lists | cta url | cards | templates | reactions | typing |
| Telegram@msgly/telegram | text supported | image supported | video supported | audio supported | file supported | buttons supported | quick replies supported | lists not supported | cta url not supported | cards not supported | templates not supported | reactions supported | typing supported |
| WhatsApp@msgly/whatsapp | text supported | image supported | video supported | audio supported | file supported | buttons supported | quick replies supported | lists supported | cta url supported | cards not supported | templates supported | reactions supported | typing supported |
| Messenger@msgly/messenger | text supported | image supported | video supported | audio supported | file supported | buttons supported | quick replies supported | lists not supported | cta url not supported | cards not supported | templates not supported | reactions not supported | typing supported |
| Instagram@msgly/instagram | text supported | image supported | video supported | audio not supported | file not supported | buttons not supported | quick replies supported | lists not supported | cta url not supported | cards not supported | templates not supported | reactions supported | typing supported |
| LINE@msgly/line | text supported | image supported | video supported | audio supported | file not supported | buttons supported | quick replies supported | lists not supported | cta url not supported | cards not supported | templates not supported | reactions not supported | typing supported |
| Discord@msgly/discord | text supported | image supported | video supported | audio supported | file supported | buttons supported | quick replies not supported | lists not supported | cta url not supported | cards not supported | templates not supported | reactions not supported | typing not supported |
| Microsoft Teams@msgly/msteams | text supported | image supported | video supported | audio supported | file supported | buttons supported | quick replies not supported | lists not supported | cta url not supported | cards not supported | templates not supported | reactions not supported | typing supported |
| Slack@msgly/slack | text supported | image supported | video not supported | audio not supported | file not supported | buttons supported | quick replies not supported | lists not supported | cta url not supported | cards not supported | templates not supported | reactions not supported | typing not supported |
| WeChat@msgly/wechat | text supported | image supported | video supported | audio supported | file not supported | buttons not supported | quick replies supported | lists not supported | cta url not supported | cards not supported | templates not supported | reactions not supported | typing not supported |
| Viber@msgly/viber | text supported | image supported | video supported | audio not supported | file supported | buttons supported | quick replies supported | lists not supported | cta url not supported | cards not supported | templates not supported | reactions not supported | typing not supported |
| Mattermost@msgly/mattermost | text supported | image supported | video supported | audio supported | file supported | buttons supported | quick replies not supported | lists not supported | cta url not supported | cards not supported | templates not supported | reactions supported | typing not supported |
| Rocket.Chat@msgly/rocketchat | text supported | image supported | video supported | audio supported | file supported | buttons supported | quick replies not supported | lists not supported | cta url not supported | cards not supported | templates not supported | reactions supported | typing not supported |
| Google Chat@msgly/googlechat | text supported | image not supported | video not supported | audio not supported | file not supported | buttons supported | quick replies not supported | lists not supported | cta url not supported | cards not supported | templates not supported | reactions not supported | typing not supported |
| Gmail@msgly/gmail | text supported | image not supported | video not supported | audio not supported | file not supported | buttons not supported | quick replies not supported | lists not supported | cta url not supported | cards not supported | templates not supported | reactions not supported | typing not supported |
| Outlook / M365@msgly/outlook | text supported | image not supported | video not supported | audio not supported | file not supported | buttons not supported | quick replies not supported | lists not supported | cta url not supported | cards not supported | templates not supported | reactions not supported | typing not supported |
| SMTP / IMAP@msgly/smtp | text supported | image not supported | video not supported | audio not supported | file not supported | buttons not supported | quick replies not supported | lists not supported | cta url not supported | cards not supported | templates not supported | reactions not supported | typing not supported |
| Resend@msgly/resend | text supported | image not supported | video not supported | audio not supported | file not supported | buttons not supported | quick replies not supported | lists not supported | cta url not supported | cards not supported | templates not supported | reactions not supported | typing not supported |
| Mailgun@msgly/mailgun | text supported | image not supported | video not supported | audio not supported | file not supported | buttons not supported | quick replies not supported | lists not supported | cta url not supported | cards not supported | templates not supported | reactions not supported | typing not supported |
| Postmark@msgly/postmark | text supported | image not supported | video not supported | audio not supported | file not supported | buttons not supported | quick replies not supported | lists not supported | cta url not supported | cards not supported | templates not supported | reactions not supported | typing not supported |
| SendGrid@msgly/sendgrid | text supported | image not supported | video not supported | audio not supported | file not supported | buttons not supported | quick replies not supported | lists not supported | cta url not supported | cards not supported | templates not supported | reactions not supported | typing not supported |
| Amazon SES@msgly/ses | text supported | image not supported | video not supported | audio not supported | file not supported | buttons not supported | quick replies not supported | lists not supported | cta url not supported | cards not supported | templates not supported | reactions not supported | typing not supported |
| Twilio SMS@msgly/twilio-sms | text supported | image supported | video not supported | audio not supported | file not supported | buttons not supported | quick replies not supported | lists not supported | cta url not supported | cards not supported | templates not supported | reactions not supported | typing not supported |
| Exotel@msgly/exotel | text supported | image not supported | video not supported | audio not supported | file not supported | buttons not supported | quick replies not supported | lists not supported | cta url not supported | cards not supported | templates not supported | reactions not supported | typing not supported |
| MSG91@msgly/msg91 | text supported | image not supported | video not supported | audio not supported | file not supported | buttons not supported | quick replies not supported | lists not supported | cta url not supported | cards not supported | templates supported | reactions not supported | typing not supported |
| Vonage@msgly/vonage-sms | text supported | image not supported | video not supported | audio not supported | file not supported | buttons not supported | quick replies not supported | lists not supported | cta url not supported | cards not supported | templates not supported | reactions not supported | typing not supported |
| Plivo@msgly/plivo | text supported | image supported | video not supported | audio not supported | file not supported | buttons not supported | quick replies not supported | lists not supported | cta url not supported | cards not supported | templates not supported | reactions not supported | typing not supported |
| Telnyx@msgly/telnyx | text supported | image supported | video not supported | audio not supported | file not supported | buttons not supported | quick replies not supported | lists not supported | cta url not supported | cards not supported | templates not supported | reactions not supported | typing not supported |
| Dial@msgly/dial | text supported | image supported | video supported | audio supported | file supported | buttons not supported | quick replies not supported | lists not supported | cta url not supported | cards not supported | templates not supported | reactions supported | typing supported |
| Genesys SMS@msgly/genesys-sms | text supported | image not supported | video not supported | audio not supported | file not supported | buttons not supported | quick replies not supported | lists not supported | cta url not supported | cards not supported | templates not supported | reactions not supported | typing not supported |
| Twilio Voice@msgly/twilio-voice | text supported | image not supported | video not supported | audio supported | file not supported | buttons supported | quick replies not supported | lists not supported | cta url not supported | cards not supported | templates not supported | reactions not supported | typing not supported |
| Reddit@msgly/reddit | text supported | image not supported | video not supported | audio not supported | file not supported | buttons not supported | quick replies not supported | lists not supported | cta url not supported | cards not supported | templates not supported | reactions not supported | typing not supported |
| TikTok@msgly/tiktok | text supported | image not supported | video not supported | audio not supported | file not supported | buttons not supported | quick replies not supported | lists not supported | cta url not supported | cards not supported | templates not supported | reactions not supported | typing not supported |
| FCM@msgly/fcm | text supported | image supported | video not supported | audio not supported | file not supported | buttons not supported | quick replies not supported | lists not supported | cta url not supported | cards not supported | templates not supported | reactions not supported | typing not supported |
| APNs@msgly/apns | text supported | image supported | video not supported | audio not supported | file not supported | buttons not supported | quick replies not supported | lists not supported | cta url not supported | cards not supported | templates not supported | reactions not supported | typing not supported |
| Web Push@msgly/web-push | text supported | image supported | video not supported | audio not supported | file not supported | buttons not supported | quick replies not supported | lists not supported | cta url not supported | cards not supported | templates not supported | reactions not supported | typing not supported |
| Expo Push@msgly/expo-push | text supported | image supported | video not supported | audio not supported | file not supported | buttons not supported | quick replies not supported | lists not supported | cta url not supported | cards not supported | templates not supported | reactions not supported | typing not supported |
| RCS@msgly/rcs-twilio | text supported | image supported | video supported | audio not supported | file supported | buttons supported | quick replies supported | lists not supported | cta url supported | cards supported | templates supported | reactions not supported | typing not supported |
| Plivo Voice@msgly/plivo-voice | text supported | image not supported | video not supported | audio supported | file not supported | buttons supported | quick replies not supported | lists not supported | cta url not supported | cards not supported | templates not supported | reactions not supported | typing not supported |
| Vonage Voice@msgly/vonage-voice | text supported | image not supported | video not supported | audio supported | file not supported | buttons supported | quick replies not supported | lists not supported | cta url not supported | cards not supported | templates not supported | reactions not supported | typing not supported |
| Exotel Voice@msgly/exotel-voice | text not supported | image not supported | video not supported | audio not supported | file not supported | buttons not supported | quick replies not supported | lists not supported | cta url not supported | cards not supported | templates not supported | reactions not supported | typing not supported |
| Genesys Voice@msgly/genesys-voice | text not supported | image not supported | video not supported | audio not supported | file not supported | buttons not supported | quick replies not supported | lists not supported | cta url not supported | cards not supported | templates not supported | reactions not supported | typing not supported |
Generated at build time by constructing every adapter and reading the capabilities object it reports — not a hand-maintained table. The hub checks these before sending and throws UnsupportedFeature rather than silently degrading.
60-second quickstart
From zero to a live bot.
Start with Telegram — the easiest channel. No business verification, no Meta App, no Pages. You need Node.js 20+ and a Telegram account.
Install
Only the channels you actually need.
npm install @msgly/core @msgly/telegramGet a bot token
Message @BotFather on Telegram, send /newbot, copy the token. No business verification, no Meta App.
TELEGRAM_BOT_TOKEN=123456789:ABC-DEF...
TELEGRAM_WEBHOOK_SECRET=any-random-stringRegister and connect
Credentials are verified at boot, so bad tokens fail fast with an actionable hint.
const hub = createHub();
hub.register(createTelegramAdapter({
botToken: process.env.TELEGRAM_BOT_TOKEN!,
webhookSecret: process.env.TELEGRAM_WEBHOOK_SECRET!,
}));
await hub.connect({ throwOnFailure: true });Go live
Expose port 3000 with ngrok, register the webhook URL with Telegram, and send your bot a message.
ngrok http 3000Features in detail
The unglamorous parts, already done.
Signature verification, exponential backoff, duplicate webhooks, per-platform size caps — the work that makes a messaging integration production-ready rather than demo-ready.
Startup credentials check
Every adapter ships verifyCredentials(). hub.connect() calls the platform whoami endpoint for each channel and returns either confirmation or a precise hint — which env var, where to find it, how to regenerate.
const report = await hub.connect();
// telegram → ok: '@my_bot'
// whatsapp → unauthorized (+ hint)
// Or fail fast when booting:
await hub.connect({
throwOnFailure: true,
});One webhook handler, every channel
hub.createWebhookHandler() returns { get, post } for any Express-like framework: Meta GET handshake, per-platform HMAC signature verification, channel dispatch and idempotent de-duplication by externalId.
const wh = hub.createWebhookHandler();
app.get('/webhook/:channel', wh.get);
app.post('/webhook/:channel', wh.post);Smart retry
Transient failures retry with exponential backoff and jitter. Permanent failures — bad credentials, invalid recipients — fail immediately instead of burning your rate limit.
const hub = createHub({
retry: {
attempts: 4,
baseDelayMs: 250,
},
});State persistence
Bring any KV store — Redis, DynamoDB, Cloudflare KV. The hub uses it for idempotency keys, conversation state and suppression, so restarts and multiple instances stay consistent.
const hub = createHub({
storage: redisStorage(redisClient),
});Capability checks
Ask before you send. Channels differ on attachments, buttons, templates and reactions — msgly answers up front rather than failing at the API boundary.
const can = hub.supports(
'telegram', 'reaction',
);
if (can) {
await hub.react({ /* ... */ });
}Platform limits enforced
Text length caps, attachment size and MIME restrictions are validated locally before the request goes out, so you get a clear error instead of an opaque platform rejection.
// 4096 chars on Telegram,
// 1600 on SMS — both checked
// before the network call.
await hub.send({ /* ... */ });Campaigns
Send to many, without getting throttled.
hub.sendBulk() fans one message out to a contact list, paced to the channel's rate limit. One bad recipient never aborts the campaign: it resolves rather than rejecting, and result.results comes back in input order so you can zip it against your own list.
Conservative per-channel defaults ship built in (Slack 1/s, Twilio long code 1/s, Gmail ~2/s, Discord 5/s, Telegram 25/s, WhatsApp 60/s), overridable per call, per adapter or per hub. Cancelling via signal gives you partial results instead of throwing them away.
const result = await hub.sendBulk({
channel: 'whatsapp',
account: { channel: 'whatsapp', channelAccountId: process.env.WA_PHONE_ID! },
recipients: customers.map((c) => ({
contact: { channel: 'whatsapp', channelUserId: c.phone },
metadata: { crmId: c.id },
})),
// A function, so every recipient gets their own template variables:
content: (r) => ({
type: 'template',
templateName: 'order_update',
language: 'en_US',
variables: { '1': nameFor(r.contact), '2': orderFor(r.contact) },
}),
concurrency: 8,
onProgress: (p) => console.log(`${p.completed}/${p.total}`),
signal: AbortSignal.timeout(60_000),
});
console.log(`sent ${result.sent}, failed ${result.failed}`);Which channels suit campaigns
SES, SMTP, Resend, SendGrid, Twilio SMS, Exotel, MSG91, Vonage, Plivo, Telnyx, FCM
Email, SMS and push, fanned out per recipient. Honour opt-outs.
LINE, WeChat, Viber, Telegram, FCM topics
One API call reaches the whole audience — no per-recipient fan-out.
Instagram, Facebook Pages, Reddit, TikTok
publishPost() puts a post on the feed. No recipient, so it sits outside send().
Real campaign channel, but needs approved MARKETING templates and opt-in.
Messenger, Instagram DMs
24h window and message tags only — no DM marketing broadcast.
Slack, Teams, Discord, Mattermost, Rocket.Chat, Google Chat
The recipient is a room, not a person. Post to a channel instead.
Broadcast
Some channels don't need fan-out at all.
LINE, WeChat, Viber and Telegram have a real broadcast primitive: one API call reaches the entire audience, with no per-recipient cost and no rate-limit pacing to worry about. Reaching 100,000 LINE friends is a single request, not 100,000 of them.
Instagram and Facebook are different again — publishPost() puts a post on the feed. A post has no recipient, so it deliberately sits outside send() rather than pretending to be a message.
// LINE: one call reaches every friend — no fan-out, no per-recipient cost
await line.broadcast(
{ type: 'text', text: 'Sale starts now' },
{ retryKey: 'spring-sale-2026' }, // a timeout cannot double-send
);
// WeChat: all followers, or one tag group. Metered at 4/month.
await wechat.massSend({ type: 'text', text: 'New arrivals' }, { tagId: 7 });
// Viber: up to 300 subscribers per call, with per-recipient failures returned
const r = await viber.broadcast(ids, { type: 'text', text: 'Sale' });
r.metadata?.failed; // [{ id, status }] — feed these to the suppression store
// Telegram: a channel is just another chat id
await hub.send({
channel: 'telegram',
contact: { channel: 'telegram', channelUserId: '@acme_announcements' },
content: { type: 'text', text: 'Shipped v2' },
account: { channel: 'telegram', channelAccountId: 'acme_bot' },
});
// Instagram / Facebook: publishing, not messaging
await instagram.publishPost({
imageUrl: 'https://cdn.acme.com/promo.jpg',
caption: 'Spring sale is live',
});Compliance
Opt-outs are not optional.
sendBulk consults a SuppressionStore before every send — required by TCPA and TRAI/DLT for SMS, and CAN-SPAM and GDPR for email. Suppressed recipients come back as skipped, never failed, and cost no rate limit.
If the store is unreachable the send is skipped rather than sent: not sending is the recoverable mistake. Only permanent failures suppress — a deferral or a full mailbox is left alone. Email adapters also emit the List-Unsubscribe header Gmail and Yahoo have required from bulk senders since February 2024.
import { createHub, createInMemorySuppressionStore, applyConsentIntent } from '@msgly/core';
const suppression = createInMemorySuppressionStore(); // use a KV store in production
const hub = createHub({ suppressionStore: suppression });
// Capture STOP / UNSUBSCRIBE replies automatically
hub.on('message', (msg) => applyConsentIntent(msg, suppression));
// Hard bounces and spam complaints suppress themselves
hub.on('delivery', (r) => applyDeliveryReceipt(r, 'resend', suppression));
const result = await hub.sendBulk({ /* ... */ });
console.log(result.sent, result.skipped); // skipped = opted outArchitecture
Four layers, clean contracts.
Adding a new channel is one new package — no core changes needed. You own the top layer, the platforms own the bottom, and the two in between are the only ones msgly asks you to think about.
Express, Fastify, Next.js route handlers, a worker — anything that can receive an HTTP POST.
Unified types, the MessagingHub orchestrator, retries, idempotency, capability checks, rate limiting, storage and suppression.
One package per platform. Each implements the same Adapter interface and ships its own verifyCredentials().
Telegram, Meta, LINE, Twilio, Google, Microsoft, AWS, Firebase and the rest.
Install
Ship your first channel today.
Install only the channels you need. Everything is MIT licensed and TypeScript-native.
npm install @msgly/core @msgly/whatsapp @msgly/telegram @msgly/twilio-sms