npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@msgly/dial

v1.11.0

Published

Dial adapter for Msgly — SMS, MMS and iMessage on agent-provisioned numbers, with HMAC-signed webhooks

Readme

@msgly/dial

📖 Docs & channel reference: https://ayushjain070401.github.io/msgly/

Dial adapter for Msgly — SMS, MMS and iMessage on agent-provisioned numbers, with HMAC-SHA256 webhook verification.

npm install @msgly/core @msgly/dial
import { createHub } from '@msgly/core';
import { createDialAdapter } from '@msgly/dial';

const dial = createDialAdapter({
  apiKey: process.env.DIAL_API_KEY!,        // sk_live_…
  fromNumber: '+15550001111',               // id, E.164 or nickname
  webhookSecret: process.env.DIAL_WEBHOOK_SECRET!,
});

One adapter, several rails

A Dial line can carry SMS, iMessage, RCS and WhatsApp at once, and Dial derives a reply's rail from the message being replied to. Splitting those into separate Msgly channels would fragment one conversation's history, so this is a single dial channel and the rail travels as metadata.dialChannel on inbound messages. Set channel in the config to force a rail outbound.

Inbound WhatsApp currently has no value of its own in Dial's published event types (sms | imessage | rcs | unknown), so whatever arrives is preserved verbatim in metadata rather than guessed at.

Webhooks, not the PubNub stream

Dial offers two inbound paths: signed HTTP webhooks, and a long-lived PubNub subscription via @getdial/sdk. This adapter uses the webhooks, because Msgly's Adapter contract is webhook-shaped — and because depending on the SDK would pull pubnub and zod into a package that otherwise needs nothing but @msgly/core.

X-Dial-Signature has the form t=<unix>,v1=<hex>, and the HMAC covers "{timestamp}.{rawBody}". Pass the raw bytes: re-serialising parsed JSON changes key order and whitespace, which changes the digest.

A timestamp window (default 5 minutes, webhookToleranceSec) bounds replay of a captured request. Verification fails closed on a missing or malformed header. Leaving webhookSecret unset makes verifySignature return true for everything.

Delivery receipts

Msgly's Adapter interface has no receipt hook — handleWebhook returns inbound messages only — so DialAdapter exposes parseStatuses(req) alongside it. Call both on the same request; each returns [] for the other's event type.

const messages = await dial.handleWebhook(req);
const receipts = dial.parseStatuses(req);

Dial reports delivery and reads as two independent axes, with changed naming the one that moved. A read advance maps to 'read'; otherwise the delivery axis is the status. unconfirmed means the rail never reports delivery at all, which is 'sent' rather than a failure.

Deduplicate on metadata.dialEventId (X-Dial-Event-ID) — Dial redelivers on retry.

MMS and attachments

Dial fetches media itself, so pass a public URL. Up to 10 attachments of 5 MB each; iMessage and WhatsApp accept one. A platform-id reference fails fast with a clear message instead of a confusing API error, and there is no standalone upload endpoint — attachments are supplied at send time.

US 10DLC

Voice, inbound SMS and texting non-US numbers work as soon as a number exists. Texting US numbers requires 10DLC brand and campaign registration under your own company's legal identity, with a carrier review measured in days. A freshly approved brand on a low tier runs roughly 0.25–4 messages per second, so the adapter's default rateLimit is a deliberately conservative 1/s. Raise it once you know your tier.

Endpoint paths

POST /api/v1/messages is documented. The reaction and typing paths (/api/v1/messages/{id}/reply, /api/v1/typing/start) are inferred from @getdial/sdk's method surface and the CLI, since Dial's public REST reference does not spell them out. Override apiBase if they move, and please open an issue if you hit a 404.

Media is sent as mediaUrls, which is what Dial's REST reference documents for POST /api/v1/messages. Note that @getdial/sdk names the same thing media at its own layer and converts it on the wire — so the SDK's types are not a valid check on the REST body here. If a media send ever 400s on an unknown field, this is the first place to look.

Reaction removal is not implemented: other channels use an empty string to mean "remove", and Dial documents no equivalent, so sendReaction rejects an empty emoji rather than silently sending one.

Checking the number

verifyCredentials() validates fromNumber as well as the API key, so a wrong number is caught where it was typed rather than on the first send. It costs no extra request — the number list is the credential check. The number check is also exposed on its own:

const check = await adapter.verifyPhoneNumber();
// { ok: false, status: 'not_owned', phoneNumber: 'Support', hint: '…' }

There is no E.164 format gate here, unlike the Twilio and Genesys adapters: fromNumber may be a phone-number id, an E.164 number, or a nickname, so a format check would reject two of the three valid forms. The account's own number list is the only thing that can answer the question, and any of the three forms matches.

status is owned, not_owned, malformed, or inconclusive. As with the endpoint paths above, this response's envelope is not documented — the matcher accepts a bare array plus the plausible wrappers, and reports inconclusive (not not_owned) when it recognises none of them, so an unexpected shape never reads as "your number is wrong".

License

MIT