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

@zeroxsolutions/sms

v0.1.1

Published

The SMS seam: a composed message, the port that sends it, its typed refusals, and the eSMS adapter behind that port. Knows nothing of templates, locales or delivery records - those stay in the product.

Readme

@zeroxsolutions/sms

The SMS seam: a composed message, the port that sends it, and its typed refusals.

What it does not own is the reason an SMS exists at all - templates, message keys, locales, delivery records, queues and retries are product decisions and stay in the product. This package takes a message someone else composed and hands back the id the provider filed it under.

It is a sibling of @zeroxsolutions/mail rather than a subpath of it. That package's concern is named for one channel, and a consumer installing something called mail to send an SMS reads a name that describes nothing.

Install

pnpm add @zeroxsolutions/sms

The port

A consumer implements ISmsSender for a provider this package has no adapter for, and calls send with a composed message:

import type { ISmsSender, SendSmsResult, SmsMessage } from '@zeroxsolutions/sms';

/** Yours to write: one per provider, wrapping that provider's SDK. */
declare function sendThroughTheProviderSdk(message: SmsMessage): Promise<string>;

class ProviderSender implements ISmsSender {
  async send(message: SmsMessage): Promise<SendSmsResult> {
    return { messageId: await sendThroughTheProviderSdk(message) };
  }
}

const { messageId } = await new ProviderSender().send({
  from: 'TRIPVN',
  to: '+84900000000',
  text: 'Ma xac thuc cua ban la 123456.',
});

from is the brandname or short code registered with the carriers, a single string where a mailbox takes a name and an address pair. to is in E.164 and is not validated here: which numbers a product accepts, and how it normalises one, is the product's own decision.

reference is optional: the caller's own id for the message. An adapter whose provider refuses a repeated id sends it, so a send retried after a lost answer cannot deliver and charge twice.

eSMS

@zeroxsolutions/sms/esms sends through eSMS as customer-care SMS under a registered brandname, inside Vietnam. It posts to eSMS's REST API over the runtime's own fetch, so it installs nothing and runs on workerd as on Node.

import { EsmsSmsSender } from '@zeroxsolutions/sms/esms';

const sender = new EsmsSmsSender({
  apiKey: env.ESMS_API_KEY,
  secretKey: env.ESMS_SECRET_KEY,
  sandbox: false,
  callbackUrl: 'https://app.example.com/esms-callback/<a secret of yours>',
});

const { messageId } = await sender.send({
  from: 'TRIPVN',
  to: '+84900000000',
  text: 'Ma xac thuc cua ban la 123456.',
  reference: 'msg_01j9',
});

send resolving means eSMS accepted the message, not that the handset received it. A number that is not +84 followed by 9 or 10 digits is refused before any request is made.

| eSMS CodeResult | Refusal | | ------------------------------------------- | --------------------------------------------------------------- | | 108 | SmsRecipientInvalid | | 99 | SmsRecipientUnreachable | | 104, 177 | SmsSenderNotRegistered | | 146, 201 | SmsMessageRejected | | 103 | SmsBalanceExhausted | | 160 | SmsRateLimited | | not 2xx, not JSON, or no answer in time | SmsProviderUnavailable | | 124 with a reference | none: answered as sent, with the reference as its messageId | | any other | SmsSendFailed |

The account has to meet four conditions no code can check:

  1. The brandname is registered with the carriers.
  2. The text matches the customer-care template registered for it, or eSMS answers 146.
  3. The account does not require an IP whitelist (140), or endpoint names a relay with a fixed egress address: a Worker has none.
  4. sandbox: true is set wherever a message must not reach a handset or be charged.

SmsProviderUnavailable may follow a send eSMS already accepted: a timeout (timeoutMs, 10 s by default), or an answer lost in transit. A caller retrying the same message reuses its reference; a "send a new code" action uses a new one. Match a delivery report to its send by reference when one was sent. Whether eSMS records a RequestId on a refused attempt, 103 among them, is open: if it does, a retry after topping up the account answers 124 and is reported as sent. Check this in the sandbox before relying on 124.

Delivery reports

With callbackUrl set, eSMS calls it with GET and the message's final status in the query string, retrying only on a timeout. parseEsmsDeliveryReport reads that query into an SmsDeliveryReport:

import { ESMS_CALLBACK_SOURCE_ADDRESSES, parseEsmsDeliveryReport } from '@zeroxsolutions/sms/esms';

const report = parseEsmsDeliveryReport(new URL(request.url).searchParams);
// { messageId, reference?, outcome: 'delivered' | 'failed' | 'pending', cost?: { amount, currency: 'VND' } }

eSMS signs nothing. The route that receives the callback checks the secret in its own path and the caller's address against ESMS_CALLBACK_SOURCE_ADDRESSES, redacts the query in its access log (it carries the number), and answers quickly. A query it cannot read throws SmsDeliveryReportInvalid.

Refusals

A refusal is one plain Error subclass per meaning a caller acts on differently - no wire status, no code, no envelope. A caller maps them by class.

| Class | What happened | | -------------------------- | --------------------------------------------------------- | | SmsRecipientInvalid | the number is not routable | | SmsRecipientUnreachable | the carrier would not deliver to the subscriber | | SmsSenderNotRegistered | the sender is not a brandname this account may send under | | SmsMessageRejected | the text breaks a provider or carrier rule | | SmsRateLimited | the provider is throttling this account | | SmsBalanceExhausted | the account has no credit left | | SmsProviderUnavailable | the provider failed on its own side | | SmsSendFailed | a refusal none of the above names | | SmsDeliveryReportInvalid | a provider's delivery report could not be read |

Every message is a constant. The provider's own text names the number it refused, so it stays in cause: log the class, never cause and never String(error).

There is no opt-out class. The first consumer sends transactional codes, which an opt-out does not cover. A consumer that sends marketing adds one, and it arrives as a minor release rather than as a field on an existing class.