Releases: AyushJain070401/msgly
Release list
@msgly/whatsapp@1.11.0
Minor Changes
-
7360206: Chat links — the URL behind a "scan to message us" QR code, for every channel
that has one.const links = await hub.getChatLinks({ ref: "diwali-poster" }); // [ // { channel: 'whatsapp', url: 'https://wa.me/919876543210', target: '+91 98765 43210', … }, // { channel: 'instagram', url: 'https://ig.me/m/acme?ref=diwali-poster', target: 'acme', … }, // { channel: 'telegram', url: 'https://t.me/acme_bot?start=diwali-poster', … }, // ]
No QR dependency ships with this. The library returns the URL and you render
the image with whatever encoder you already use — the same string works as a
plain link or a button.hub.getChatLinks()fans out over every registered
channel; a single adapter throwing is caught and reported through the'error'
event rather than sinking the batch. One adapter at a time is
adapter.getChatLink?.().Two extras that are not universal, and say so rather than lying.
options.textprefills the first message;options.refis a tracking payload
handed back on the first inbound message, so you can tell which poster a
conversation came from. Each returned link reportsprefilledandtrackedfor
what actually happened:- WhatsApp —
wa.me/<number>. Prefill ✅ - Messenger / Instagram —
m.me/<page>,ig.me/m/<handle>. Ref ✅, as thereferralevent - Telegram —
t.me/<bot>. Ref ✅, as/start <ref> - LINE —
line.me/R/ti/p/@<id> - Viber —
viber://pa?chatURI=…, plushttps://viber.me/…for desktop. Prefill ✅ - WeChat — a QR ticket, see below. Ref ✅, as the scene id
- Teams —
teams.microsoft.com/l/chat/…. Prefill ✅ - Slack —
slack.com/app_redirect?app=… - Discord — the bot install link
- Reddit —
reddit.com/message/compose?to=…. Prefill ✅ - Mattermost / Rocket.Chat — your server's own DM URL
- Google Chat — the Marketplace listing
- TikTok — the profile URL
- SMS (Twilio, Plivo, Vonage, Telnyx, Genesys) —
sms:<number>. Prefill ✅ - Email (all eight) —
mailto:<address>. Prefill ✅
Telegram's
startparameter allows 64 characters ofA-Za-z0-9_-, so aref
it would reject is dropped andtracked: falsereported — better than emitting
a link that fails to open.Three channels are shaped differently, which is worth knowing before building
the UI. WeChat has no shareable URL at all: it mints the code server-side and
returns a ticket, so the link carriesqrImageUrl— display WeChat's own
image rather than encoding anything. Discord has no "DM this bot" URL, since a
bot is reachable only once it is in a server, so the link is the install one.
Google Chat and TikTok point at a Marketplace listing and a profile because
neither platform has a direct-message deep link.Five channels need one config field, because the handle is not derivable
from the credentials:appId(Slack),publicAccountUri(Viber),teamName
(Mattermost),username(TikTok),marketplaceAppId(Google Chat). Without it
getChatLink()returnsnulland the channel is left out ofgetChatLinks().
WhatsApp resolves its number from the Graph API once and caches it, or takes
displayPhoneNumberto skip the call; Messenger, Instagram, Telegram, LINE,
Mattermost and Rocket.Chat resolve their handle the same way.Channels with no such concept omit the method entirely rather than returning
null, so the result maps straight onto a list of QR codes with no filtering on
your side — and TypeScript makes you guardgetChatLink?.(). That covers push
(APNs, FCM, Web Push, Expo), which delivers to a device token nobody can scan
their way into; voice, which places calls rather than opens conversations; and
msg91, exotel and rcs-twilio, whose alphanumeric sender ids and messaging
service SIDs cannot receive a reply, making ansms:link a dead end.@msgly/corealso exportswithQuery(base, params), the shared URL builder
behind these links, so escaping is identical everywhere — a prefilled message
full of spaces,&and emoji survives the round-trip unchanged. - WhatsApp —
Patch Changes
@msgly/wechat@1.11.0
Minor Changes
-
7360206: Chat links — the URL behind a "scan to message us" QR code, for every channel
that has one.const links = await hub.getChatLinks({ ref: "diwali-poster" }); // [ // { channel: 'whatsapp', url: 'https://wa.me/919876543210', target: '+91 98765 43210', … }, // { channel: 'instagram', url: 'https://ig.me/m/acme?ref=diwali-poster', target: 'acme', … }, // { channel: 'telegram', url: 'https://t.me/acme_bot?start=diwali-poster', … }, // ]
No QR dependency ships with this. The library returns the URL and you render
the image with whatever encoder you already use — the same string works as a
plain link or a button.hub.getChatLinks()fans out over every registered
channel; a single adapter throwing is caught and reported through the'error'
event rather than sinking the batch. One adapter at a time is
adapter.getChatLink?.().Two extras that are not universal, and say so rather than lying.
options.textprefills the first message;options.refis a tracking payload
handed back on the first inbound message, so you can tell which poster a
conversation came from. Each returned link reportsprefilledandtrackedfor
what actually happened:- WhatsApp —
wa.me/<number>. Prefill ✅ - Messenger / Instagram —
m.me/<page>,ig.me/m/<handle>. Ref ✅, as thereferralevent - Telegram —
t.me/<bot>. Ref ✅, as/start <ref> - LINE —
line.me/R/ti/p/@<id> - Viber —
viber://pa?chatURI=…, plushttps://viber.me/…for desktop. Prefill ✅ - WeChat — a QR ticket, see below. Ref ✅, as the scene id
- Teams —
teams.microsoft.com/l/chat/…. Prefill ✅ - Slack —
slack.com/app_redirect?app=… - Discord — the bot install link
- Reddit —
reddit.com/message/compose?to=…. Prefill ✅ - Mattermost / Rocket.Chat — your server's own DM URL
- Google Chat — the Marketplace listing
- TikTok — the profile URL
- SMS (Twilio, Plivo, Vonage, Telnyx, Genesys) —
sms:<number>. Prefill ✅ - Email (all eight) —
mailto:<address>. Prefill ✅
Telegram's
startparameter allows 64 characters ofA-Za-z0-9_-, so aref
it would reject is dropped andtracked: falsereported — better than emitting
a link that fails to open.Three channels are shaped differently, which is worth knowing before building
the UI. WeChat has no shareable URL at all: it mints the code server-side and
returns a ticket, so the link carriesqrImageUrl— display WeChat's own
image rather than encoding anything. Discord has no "DM this bot" URL, since a
bot is reachable only once it is in a server, so the link is the install one.
Google Chat and TikTok point at a Marketplace listing and a profile because
neither platform has a direct-message deep link.Five channels need one config field, because the handle is not derivable
from the credentials:appId(Slack),publicAccountUri(Viber),teamName
(Mattermost),username(TikTok),marketplaceAppId(Google Chat). Without it
getChatLink()returnsnulland the channel is left out ofgetChatLinks().
WhatsApp resolves its number from the Graph API once and caches it, or takes
displayPhoneNumberto skip the call; Messenger, Instagram, Telegram, LINE,
Mattermost and Rocket.Chat resolve their handle the same way.Channels with no such concept omit the method entirely rather than returning
null, so the result maps straight onto a list of QR codes with no filtering on
your side — and TypeScript makes you guardgetChatLink?.(). That covers push
(APNs, FCM, Web Push, Expo), which delivers to a device token nobody can scan
their way into; voice, which places calls rather than opens conversations; and
msg91, exotel and rcs-twilio, whose alphanumeric sender ids and messaging
service SIDs cannot receive a reply, making ansms:link a dead end.@msgly/corealso exportswithQuery(base, params), the shared URL builder
behind these links, so escaping is identical everywhere — a prefilled message
full of spaces,&and emoji survives the round-trip unchanged. - WhatsApp —
Patch Changes
@msgly/web-push@1.11.0
@msgly/vonage-voice@1.11.0
@msgly/vonage-sms@1.11.0
Minor Changes
-
7360206: Chat links — the URL behind a "scan to message us" QR code, for every channel
that has one.const links = await hub.getChatLinks({ ref: "diwali-poster" }); // [ // { channel: 'whatsapp', url: 'https://wa.me/919876543210', target: '+91 98765 43210', … }, // { channel: 'instagram', url: 'https://ig.me/m/acme?ref=diwali-poster', target: 'acme', … }, // { channel: 'telegram', url: 'https://t.me/acme_bot?start=diwali-poster', … }, // ]
No QR dependency ships with this. The library returns the URL and you render
the image with whatever encoder you already use — the same string works as a
plain link or a button.hub.getChatLinks()fans out over every registered
channel; a single adapter throwing is caught and reported through the'error'
event rather than sinking the batch. One adapter at a time is
adapter.getChatLink?.().Two extras that are not universal, and say so rather than lying.
options.textprefills the first message;options.refis a tracking payload
handed back on the first inbound message, so you can tell which poster a
conversation came from. Each returned link reportsprefilledandtrackedfor
what actually happened:- WhatsApp —
wa.me/<number>. Prefill ✅ - Messenger / Instagram —
m.me/<page>,ig.me/m/<handle>. Ref ✅, as thereferralevent - Telegram —
t.me/<bot>. Ref ✅, as/start <ref> - LINE —
line.me/R/ti/p/@<id> - Viber —
viber://pa?chatURI=…, plushttps://viber.me/…for desktop. Prefill ✅ - WeChat — a QR ticket, see below. Ref ✅, as the scene id
- Teams —
teams.microsoft.com/l/chat/…. Prefill ✅ - Slack —
slack.com/app_redirect?app=… - Discord — the bot install link
- Reddit —
reddit.com/message/compose?to=…. Prefill ✅ - Mattermost / Rocket.Chat — your server's own DM URL
- Google Chat — the Marketplace listing
- TikTok — the profile URL
- SMS (Twilio, Plivo, Vonage, Telnyx, Genesys) —
sms:<number>. Prefill ✅ - Email (all eight) —
mailto:<address>. Prefill ✅
Telegram's
startparameter allows 64 characters ofA-Za-z0-9_-, so aref
it would reject is dropped andtracked: falsereported — better than emitting
a link that fails to open.Three channels are shaped differently, which is worth knowing before building
the UI. WeChat has no shareable URL at all: it mints the code server-side and
returns a ticket, so the link carriesqrImageUrl— display WeChat's own
image rather than encoding anything. Discord has no "DM this bot" URL, since a
bot is reachable only once it is in a server, so the link is the install one.
Google Chat and TikTok point at a Marketplace listing and a profile because
neither platform has a direct-message deep link.Five channels need one config field, because the handle is not derivable
from the credentials:appId(Slack),publicAccountUri(Viber),teamName
(Mattermost),username(TikTok),marketplaceAppId(Google Chat). Without it
getChatLink()returnsnulland the channel is left out ofgetChatLinks().
WhatsApp resolves its number from the Graph API once and caches it, or takes
displayPhoneNumberto skip the call; Messenger, Instagram, Telegram, LINE,
Mattermost and Rocket.Chat resolve their handle the same way.Channels with no such concept omit the method entirely rather than returning
null, so the result maps straight onto a list of QR codes with no filtering on
your side — and TypeScript makes you guardgetChatLink?.(). That covers push
(APNs, FCM, Web Push, Expo), which delivers to a device token nobody can scan
their way into; voice, which places calls rather than opens conversations; and
msg91, exotel and rcs-twilio, whose alphanumeric sender ids and messaging
service SIDs cannot receive a reply, making ansms:link a dead end.@msgly/corealso exportswithQuery(base, params), the shared URL builder
behind these links, so escaping is identical everywhere — a prefilled message
full of spaces,&and emoji survives the round-trip unchanged. - WhatsApp —
Patch Changes
@msgly/viber@1.11.0
Minor Changes
-
7360206: Chat links — the URL behind a "scan to message us" QR code, for every channel
that has one.const links = await hub.getChatLinks({ ref: "diwali-poster" }); // [ // { channel: 'whatsapp', url: 'https://wa.me/919876543210', target: '+91 98765 43210', … }, // { channel: 'instagram', url: 'https://ig.me/m/acme?ref=diwali-poster', target: 'acme', … }, // { channel: 'telegram', url: 'https://t.me/acme_bot?start=diwali-poster', … }, // ]
No QR dependency ships with this. The library returns the URL and you render
the image with whatever encoder you already use — the same string works as a
plain link or a button.hub.getChatLinks()fans out over every registered
channel; a single adapter throwing is caught and reported through the'error'
event rather than sinking the batch. One adapter at a time is
adapter.getChatLink?.().Two extras that are not universal, and say so rather than lying.
options.textprefills the first message;options.refis a tracking payload
handed back on the first inbound message, so you can tell which poster a
conversation came from. Each returned link reportsprefilledandtrackedfor
what actually happened:- WhatsApp —
wa.me/<number>. Prefill ✅ - Messenger / Instagram —
m.me/<page>,ig.me/m/<handle>. Ref ✅, as thereferralevent - Telegram —
t.me/<bot>. Ref ✅, as/start <ref> - LINE —
line.me/R/ti/p/@<id> - Viber —
viber://pa?chatURI=…, plushttps://viber.me/…for desktop. Prefill ✅ - WeChat — a QR ticket, see below. Ref ✅, as the scene id
- Teams —
teams.microsoft.com/l/chat/…. Prefill ✅ - Slack —
slack.com/app_redirect?app=… - Discord — the bot install link
- Reddit —
reddit.com/message/compose?to=…. Prefill ✅ - Mattermost / Rocket.Chat — your server's own DM URL
- Google Chat — the Marketplace listing
- TikTok — the profile URL
- SMS (Twilio, Plivo, Vonage, Telnyx, Genesys) —
sms:<number>. Prefill ✅ - Email (all eight) —
mailto:<address>. Prefill ✅
Telegram's
startparameter allows 64 characters ofA-Za-z0-9_-, so aref
it would reject is dropped andtracked: falsereported — better than emitting
a link that fails to open.Three channels are shaped differently, which is worth knowing before building
the UI. WeChat has no shareable URL at all: it mints the code server-side and
returns a ticket, so the link carriesqrImageUrl— display WeChat's own
image rather than encoding anything. Discord has no "DM this bot" URL, since a
bot is reachable only once it is in a server, so the link is the install one.
Google Chat and TikTok point at a Marketplace listing and a profile because
neither platform has a direct-message deep link.Five channels need one config field, because the handle is not derivable
from the credentials:appId(Slack),publicAccountUri(Viber),teamName
(Mattermost),username(TikTok),marketplaceAppId(Google Chat). Without it
getChatLink()returnsnulland the channel is left out ofgetChatLinks().
WhatsApp resolves its number from the Graph API once and caches it, or takes
displayPhoneNumberto skip the call; Messenger, Instagram, Telegram, LINE,
Mattermost and Rocket.Chat resolve their handle the same way.Channels with no such concept omit the method entirely rather than returning
null, so the result maps straight onto a list of QR codes with no filtering on
your side — and TypeScript makes you guardgetChatLink?.(). That covers push
(APNs, FCM, Web Push, Expo), which delivers to a device token nobody can scan
their way into; voice, which places calls rather than opens conversations; and
msg91, exotel and rcs-twilio, whose alphanumeric sender ids and messaging
service SIDs cannot receive a reply, making ansms:link a dead end.@msgly/corealso exportswithQuery(base, params), the shared URL builder
behind these links, so escaping is identical everywhere — a prefilled message
full of spaces,&and emoji survives the round-trip unchanged. - WhatsApp —
-
7360206: Carry the sender's profile photo on inbound messages.
ContactRefgains three optional fields —avatarUrl,usernameand
email— so an inbox UI can show who is writing, not just an opaque id.
All three are additive; nothing that readsContactReftoday changes.Where the photo is in the webhook, it is now on the contact. Discord sends
an avatar hash rather than a URL, so the adapter builds the CDN link and picks
.giffor animated (a_-prefixed) hashes and.pngotherwise. Viber and
Google Chat carry a URL directly. Rocket.Chat's webhook has no avatar at all,
but the server serves one per username at a stable path, so it is derived from
serverUrl.usernameis filled wherever the payload already has a handle:
Discord, Telegram, Mattermost, Rocket.Chat, TikTok and Reddit.Meta puts none of it in the webhook — Messenger and Instagram send only the
PSID/IGSID. The photo is one Graph call away, which is how inbox tools show it,
so both adapters can now fetch it: setfetchSenderProfile: trueand every
inbound message arrives withdisplayName,avatarUrland, on Instagram,
usernamefilled in.It is off by default because it costs one Graph call per sender on top of the
webhook you already handle. Three things keep that honest:- Cached per sender (one hour,
senderProfileCacheTtlMs), so a burst of
messages from one person is a single call. - A failed lookup never costs you the message. Errors are swallowed and the
fields left unset. A failure caches for only 60 s, so a blip does not blank
the photo for an hour, while a sender who genuinely has no profile is not
re-fetched on every message either. - Unique senders only per webhook batch, resolved in parallel.
adapter.getSenderProfile(id)fetches one on demand for callers who would
rather not pay it on every message. Note that Messenger splits the name across
first_name/last_name(joined into onedisplayName) and has no handle at
all, sousernamestays unset there — that field is Instagram-only.Every email channel now sets
contact.email. The address was already the
channelUserId, but setting the field explicitly means callers can read
contact.emailuniformly instead of knowing that on email channels the user id
happens to be an address.Two things are deliberately absent. WhatsApp exposes no end-user profile photo
at any endpoint —contacts[].profile.nameis the whole of it — so there is
nothing to opt into there. And channels that hide the photo behind a separate
profile call (Slackusers.info, TelegramgetUserProfilePhotos, LINE
getProfile) leaveavatarUrlundefined rather than making an extra request
per message; the sender id is inmetadataif you want to fetch it yourself.Platform CDN avatar URLs are usually short-lived or access-controlled, so copy
the image to your own storage if you need it to keep resolving. - Cached per sender (one hour,
Patch Changes
@msgly/twilio-voice@1.11.0
@msgly/twilio-sms@1.11.0
Minor Changes
-
7360206: Chat links — the URL behind a "scan to message us" QR code, for every channel
that has one.const links = await hub.getChatLinks({ ref: "diwali-poster" }); // [ // { channel: 'whatsapp', url: 'https://wa.me/919876543210', target: '+91 98765 43210', … }, // { channel: 'instagram', url: 'https://ig.me/m/acme?ref=diwali-poster', target: 'acme', … }, // { channel: 'telegram', url: 'https://t.me/acme_bot?start=diwali-poster', … }, // ]
No QR dependency ships with this. The library returns the URL and you render
the image with whatever encoder you already use — the same string works as a
plain link or a button.hub.getChatLinks()fans out over every registered
channel; a single adapter throwing is caught and reported through the'error'
event rather than sinking the batch. One adapter at a time is
adapter.getChatLink?.().Two extras that are not universal, and say so rather than lying.
options.textprefills the first message;options.refis a tracking payload
handed back on the first inbound message, so you can tell which poster a
conversation came from. Each returned link reportsprefilledandtrackedfor
what actually happened:- WhatsApp —
wa.me/<number>. Prefill ✅ - Messenger / Instagram —
m.me/<page>,ig.me/m/<handle>. Ref ✅, as thereferralevent - Telegram —
t.me/<bot>. Ref ✅, as/start <ref> - LINE —
line.me/R/ti/p/@<id> - Viber —
viber://pa?chatURI=…, plushttps://viber.me/…for desktop. Prefill ✅ - WeChat — a QR ticket, see below. Ref ✅, as the scene id
- Teams —
teams.microsoft.com/l/chat/…. Prefill ✅ - Slack —
slack.com/app_redirect?app=… - Discord — the bot install link
- Reddit —
reddit.com/message/compose?to=…. Prefill ✅ - Mattermost / Rocket.Chat — your server's own DM URL
- Google Chat — the Marketplace listing
- TikTok — the profile URL
- SMS (Twilio, Plivo, Vonage, Telnyx, Genesys) —
sms:<number>. Prefill ✅ - Email (all eight) —
mailto:<address>. Prefill ✅
Telegram's
startparameter allows 64 characters ofA-Za-z0-9_-, so aref
it would reject is dropped andtracked: falsereported — better than emitting
a link that fails to open.Three channels are shaped differently, which is worth knowing before building
the UI. WeChat has no shareable URL at all: it mints the code server-side and
returns a ticket, so the link carriesqrImageUrl— display WeChat's own
image rather than encoding anything. Discord has no "DM this bot" URL, since a
bot is reachable only once it is in a server, so the link is the install one.
Google Chat and TikTok point at a Marketplace listing and a profile because
neither platform has a direct-message deep link.Five channels need one config field, because the handle is not derivable
from the credentials:appId(Slack),publicAccountUri(Viber),teamName
(Mattermost),username(TikTok),marketplaceAppId(Google Chat). Without it
getChatLink()returnsnulland the channel is left out ofgetChatLinks().
WhatsApp resolves its number from the Graph API once and caches it, or takes
displayPhoneNumberto skip the call; Messenger, Instagram, Telegram, LINE,
Mattermost and Rocket.Chat resolve their handle the same way.Channels with no such concept omit the method entirely rather than returning
null, so the result maps straight onto a list of QR codes with no filtering on
your side — and TypeScript makes you guardgetChatLink?.(). That covers push
(APNs, FCM, Web Push, Expo), which delivers to a device token nobody can scan
their way into; voice, which places calls rather than opens conversations; and
msg91, exotel and rcs-twilio, whose alphanumeric sender ids and messaging
service SIDs cannot receive a reply, making ansms:link a dead end.@msgly/corealso exportswithQuery(base, params), the shared URL builder
behind these links, so escaping is identical everywhere — a prefilled message
full of spaces,&and emoji survives the round-trip unchanged. - WhatsApp —
Patch Changes
@msgly/tiktok@1.11.0
Minor Changes
-
7360206: Chat links — the URL behind a "scan to message us" QR code, for every channel
that has one.const links = await hub.getChatLinks({ ref: "diwali-poster" }); // [ // { channel: 'whatsapp', url: 'https://wa.me/919876543210', target: '+91 98765 43210', … }, // { channel: 'instagram', url: 'https://ig.me/m/acme?ref=diwali-poster', target: 'acme', … }, // { channel: 'telegram', url: 'https://t.me/acme_bot?start=diwali-poster', … }, // ]
No QR dependency ships with this. The library returns the URL and you render
the image with whatever encoder you already use — the same string works as a
plain link or a button.hub.getChatLinks()fans out over every registered
channel; a single adapter throwing is caught and reported through the'error'
event rather than sinking the batch. One adapter at a time is
adapter.getChatLink?.().Two extras that are not universal, and say so rather than lying.
options.textprefills the first message;options.refis a tracking payload
handed back on the first inbound message, so you can tell which poster a
conversation came from. Each returned link reportsprefilledandtrackedfor
what actually happened:- WhatsApp —
wa.me/<number>. Prefill ✅ - Messenger / Instagram —
m.me/<page>,ig.me/m/<handle>. Ref ✅, as thereferralevent - Telegram —
t.me/<bot>. Ref ✅, as/start <ref> - LINE —
line.me/R/ti/p/@<id> - Viber —
viber://pa?chatURI=…, plushttps://viber.me/…for desktop. Prefill ✅ - WeChat — a QR ticket, see below. Ref ✅, as the scene id
- Teams —
teams.microsoft.com/l/chat/…. Prefill ✅ - Slack —
slack.com/app_redirect?app=… - Discord — the bot install link
- Reddit —
reddit.com/message/compose?to=…. Prefill ✅ - Mattermost / Rocket.Chat — your server's own DM URL
- Google Chat — the Marketplace listing
- TikTok — the profile URL
- SMS (Twilio, Plivo, Vonage, Telnyx, Genesys) —
sms:<number>. Prefill ✅ - Email (all eight) —
mailto:<address>. Prefill ✅
Telegram's
startparameter allows 64 characters ofA-Za-z0-9_-, so aref
it would reject is dropped andtracked: falsereported — better than emitting
a link that fails to open.Three channels are shaped differently, which is worth knowing before building
the UI. WeChat has no shareable URL at all: it mints the code server-side and
returns a ticket, so the link carriesqrImageUrl— display WeChat's own
image rather than encoding anything. Discord has no "DM this bot" URL, since a
bot is reachable only once it is in a server, so the link is the install one.
Google Chat and TikTok point at a Marketplace listing and a profile because
neither platform has a direct-message deep link.Five channels need one config field, because the handle is not derivable
from the credentials:appId(Slack),publicAccountUri(Viber),teamName
(Mattermost),username(TikTok),marketplaceAppId(Google Chat). Without it
getChatLink()returnsnulland the channel is left out ofgetChatLinks().
WhatsApp resolves its number from the Graph API once and caches it, or takes
displayPhoneNumberto skip the call; Messenger, Instagram, Telegram, LINE,
Mattermost and Rocket.Chat resolve their handle the same way.Channels with no such concept omit the method entirely rather than returning
null, so the result maps straight onto a list of QR codes with no filtering on
your side — and TypeScript makes you guardgetChatLink?.(). That covers push
(APNs, FCM, Web Push, Expo), which delivers to a device token nobody can scan
their way into; voice, which places calls rather than opens conversations; and
msg91, exotel and rcs-twilio, whose alphanumeric sender ids and messaging
service SIDs cannot receive a reply, making ansms:link a dead end.@msgly/corealso exportswithQuery(base, params), the shared URL builder
behind these links, so escaping is identical everywhere — a prefilled message
full of spaces,&and emoji survives the round-trip unchanged. - WhatsApp —
-
7360206: Carry the sender's profile photo on inbound messages.
ContactRefgains three optional fields —avatarUrl,usernameand
email— so an inbox UI can show who is writing, not just an opaque id.
All three are additive; nothing that readsContactReftoday changes.Where the photo is in the webhook, it is now on the contact. Discord sends
an avatar hash rather than a URL, so the adapter builds the CDN link and picks
.giffor animated (a_-prefixed) hashes and.pngotherwise. Viber and
Google Chat carry a URL directly. Rocket.Chat's webhook has no avatar at all,
but the server serves one per username at a stable path, so it is derived from
serverUrl.usernameis filled wherever the payload already has a handle:
Discord, Telegram, Mattermost, Rocket.Chat, TikTok and Reddit.Meta puts none of it in the webhook — Messenger and Instagram send only the
PSID/IGSID. The photo is one Graph call away, which is how inbox tools show it,
so both adapters can now fetch it: setfetchSenderProfile: trueand every
inbound message arrives withdisplayName,avatarUrland, on Instagram,
usernamefilled in.It is off by default because it costs one Graph call per sender on top of the
webhook you already handle. Three things keep that honest:- Cached per sender (one hour,
senderProfileCacheTtlMs), so a burst of
messages from one person is a single call. - A failed lookup never costs you the message. Errors are swallowed and the
fields left unset. A failure caches for only 60 s, so a blip does not blank
the photo for an hour, while a sender who genuinely has no profile is not
re-fetched on every message either. - Unique senders only per webhook batch, resolved in parallel.
adapter.getSenderProfile(id)fetches one on demand for callers who would
rather not pay it on every message. Note that Messenger splits the name across
first_name/last_name(joined into onedisplayName) and has no handle at
all, sousernamestays unset there — that field is Instagram-only.Every email channel now sets
contact.email. The address was already the
channelUserId, but setting the field explicitly means callers can read
contact.emailuniformly instead of knowing that on email channels the user id
happens to be an address.Two things are deliberately absent. WhatsApp exposes no end-user profile photo
at any endpoint —contacts[].profile.nameis the whole of it — so there is
nothing to opt into there. And channels that hide the photo behind a separate
profile call (Slackusers.info, TelegramgetUserProfilePhotos, LINE
getProfile) leaveavatarUrlundefined rather than making an extra request
per message; the sender id is inmetadataif you want to fetch it yourself.Platform CDN avatar URLs are usually short-lived or access-controlled, so copy
the image to your own storage if you need it to keep resolving. - Cached per sender (one hour,
Patch Changes
@msgly/telnyx@1.11.0
Minor Changes
-
7360206: Chat links — the URL behind a "scan to message us" QR code, for every channel
that has one.const links = await hub.getChatLinks({ ref: "diwali-poster" }); // [ // { channel: 'whatsapp', url: 'https://wa.me/919876543210', target: '+91 98765 43210', … }, // { channel: 'instagram', url: 'https://ig.me/m/acme?ref=diwali-poster', target: 'acme', … }, // { channel: 'telegram', url: 'https://t.me/acme_bot?start=diwali-poster', … }, // ]
No QR dependency ships with this. The library returns the URL and you render
the image with whatever encoder you already use — the same string works as a
plain link or a button.hub.getChatLinks()fans out over every registered
channel; a single adapter throwing is caught and reported through the'error'
event rather than sinking the batch. One adapter at a time is
adapter.getChatLink?.().Two extras that are not universal, and say so rather than lying.
options.textprefills the first message;options.refis a tracking payload
handed back on the first inbound message, so you can tell which poster a
conversation came from. Each returned link reportsprefilledandtrackedfor
what actually happened:- WhatsApp —
wa.me/<number>. Prefill ✅ - Messenger / Instagram —
m.me/<page>,ig.me/m/<handle>. Ref ✅, as thereferralevent - Telegram —
t.me/<bot>. Ref ✅, as/start <ref> - LINE —
line.me/R/ti/p/@<id> - Viber —
viber://pa?chatURI=…, plushttps://viber.me/…for desktop. Prefill ✅ - WeChat — a QR ticket, see below. Ref ✅, as the scene id
- Teams —
teams.microsoft.com/l/chat/…. Prefill ✅ - Slack —
slack.com/app_redirect?app=… - Discord — the bot install link
- Reddit —
reddit.com/message/compose?to=…. Prefill ✅ - Mattermost / Rocket.Chat — your server's own DM URL
- Google Chat — the Marketplace listing
- TikTok — the profile URL
- SMS (Twilio, Plivo, Vonage, Telnyx, Genesys) —
sms:<number>. Prefill ✅ - Email (all eight) —
mailto:<address>. Prefill ✅
Telegram's
startparameter allows 64 characters ofA-Za-z0-9_-, so aref
it would reject is dropped andtracked: falsereported — better than emitting
a link that fails to open.Three channels are shaped differently, which is worth knowing before building
the UI. WeChat has no shareable URL at all: it mints the code server-side and
returns a ticket, so the link carriesqrImageUrl— display WeChat's own
image rather than encoding anything. Discord has no "DM this bot" URL, since a
bot is reachable only once it is in a server, so the link is the install one.
Google Chat and TikTok point at a Marketplace listing and a profile because
neither platform has a direct-message deep link.Five channels need one config field, because the handle is not derivable
from the credentials:appId(Slack),publicAccountUri(Viber),teamName
(Mattermost),username(TikTok),marketplaceAppId(Google Chat). Without it
getChatLink()returnsnulland the channel is left out ofgetChatLinks().
WhatsApp resolves its number from the Graph API once and caches it, or takes
displayPhoneNumberto skip the call; Messenger, Instagram, Telegram, LINE,
Mattermost and Rocket.Chat resolve their handle the same way.Channels with no such concept omit the method entirely rather than returning
null, so the result maps straight onto a list of QR codes with no filtering on
your side — and TypeScript makes you guardgetChatLink?.(). That covers push
(APNs, FCM, Web Push, Expo), which delivers to a device token nobody can scan
their way into; voice, which places calls rather than opens conversations; and
msg91, exotel and rcs-twilio, whose alphanumeric sender ids and messaging
service SIDs cannot receive a reply, making ansms:link a dead end.@msgly/corealso exportswithQuery(base, params), the shared URL builder
behind these links, so escaping is identical everywhere — a prefilled message
full of spaces,&and emoji survives the round-trip unchanged. - WhatsApp —