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

@smplcty/twilio

v0.1.1

Published

Tiny TypeScript wrapper around the Twilio Verify v2 API. Send and check OTP codes via SMS or email with zero global state and no env-var dependencies.

Downloads

33

Readme

@smplcty/twilio

Tiny TypeScript wrapper around the Twilio Verify v2 API for sending and checking OTP codes via SMS or email.

npm License: MIT

What this is

A 200-line, dependency-free wrapper around two Twilio Verify endpoints:

  • POST /v2/Services/{ServiceSid}/Verifications — start an OTP challenge by sending a code via SMS or email.
  • POST /v2/Services/{ServiceSid}/VerificationCheck — check whether a submitted code matches the most recent challenge.

That's the entire scope. There's no auth flow, no session management, no dev backdoor logic. If you need a sign-in bypass for developers whose phones can't receive SMS, that lives in @smplcty/auth as verifyDevOtp — call it before you call this library.

Install

pnpm add @smplcty/twilio

Zero runtime dependencies. Requires Node 20+ (uses global fetch).

Usage

import { createTwilioVerifyClient } from '@smplcty/twilio';

const twilio = createTwilioVerifyClient({
  accountSid: process.env.TWILIO_ACCOUNT_SID!,
  authToken: process.env.TWILIO_AUTH_TOKEN!,
  serviceSid: process.env.TWILIO_SERVICE_SID!,
});

// Send a code
const sent = await twilio.sendVerificationCode({
  channel: 'sms',
  to: '+15558675309',
});
if (!sent) {
  // Twilio rejected the request — rate limit, bad number, blocked recipient, etc.
}

// Later, check the code the user submits
const ok = await twilio.verifyVerificationCode({
  to: '+15558675309',
  code: '123456',
});
if (!ok) {
  // Wrong code, expired challenge, or Twilio rejected the check.
}

API

createTwilioVerifyClient(config)

Returns a stateless client object. Safe to cache at module scope.

interface TwilioVerifyConfig {
  accountSid: string;
  authToken: string;
  serviceSid: string;
  baseUrl?: string;             // override for testing; default https://verify.twilio.com/v2
  fetch?: typeof globalThis.fetch;  // inject a mock for tests
  logger?: Logger;              // optional, defaults to no-op
}

interface TwilioVerifyClient {
  sendVerificationCode(input: { channel: 'sms' | 'email'; to: string }): Promise<boolean>;
  verifyVerificationCode(input: { to: string; code: string }): Promise<boolean>;
}

Both methods return Promise<boolean> and do not throw on Twilio rejection — non-2xx responses, network errors, expired challenges, and rate limits all return false. The caller decides whether false means "retry" or "give up." This makes the most-common code path (if (!ok) return 400) trivial.

The only thing that throws is InvalidInputError for missing required fields.

Why no environment variables

The library never reads process.env. Pass credentials explicitly to createTwilioVerifyClient.

This makes the library trivially testable, supports multi-account use cases, and means a stale env var can never silently break a production deploy. The boilerplate of constructing the client at module load is the right amount of explicitness:

// app.ts
import { createTwilioVerifyClient } from '@smplcty/twilio';

if (!process.env.TWILIO_ACCOUNT_SID) {
  throw new Error('TWILIO_ACCOUNT_SID is required');
}
// ...same for authToken and serviceSid...

export const twilio = createTwilioVerifyClient({
  accountSid: process.env.TWILIO_ACCOUNT_SID,
  authToken: process.env.TWILIO_AUTH_TOKEN!,
  serviceSid: process.env.TWILIO_SERVICE_SID!,
});

If you want a thin wrapper that reads env vars, write one in your app code — it's six lines.

Why no dev backdoor

Earlier versions of this codebase (before extraction) included a DEV_PHONE_NUMBERS + DEV_VERIFICATION_CODE env var pair that bypassed Twilio for specific phone numbers when paired with a magic env var code. That logic is not in this library and never will be.

If you need a dev sign-in bypass (to handle Twilio's flaky SMS delivery to certain carriers), use @smplcty/auth's verifyDevOtp:

import { verifyDevOtp } from '@smplcty/auth';
import { createTwilioVerifyClient } from '@smplcty/twilio';

const twilio = createTwilioVerifyClient({ ... });

// In your sign-in-verify handler, AFTER looking up the user's
// user_communication_method_id:
const devOk = await verifyDevOtp(db, ucmId, submittedCode);
if (devOk) {
  return createSession(...);
}

const twilioOk = await twilio.verifyVerificationCode({ to, code: submittedCode });
if (twilioOk) {
  return createSession(...);
}

return { statusCode: 400, body: 'Invalid code' };

This is strictly better than the env var approach: per-dev TOTP secrets, time-rotating codes, per-dev revocation, built-in audit trail, and no shared bypass code that compromises every dev account if it leaks. See the @smplcty/auth README for the full design rationale.

Logging

The library never logs verification codes, recipient phone numbers, recipient email addresses, or Twilio response bodies. With LOG_LEVEL=debug it will only log structural events:

  • 'sending verification code' with { channel }
  • 'verification code sent' with { channel, status }
  • 'verifying verification code' with {} (no fields)
  • 'verification check complete' with { approved, status }
  • Warnings on rejection with { status } only

The default logger is a no-op. To get the events, pass a logger that matches the pino-style (data, msg) shape (or pino itself):

import pino from 'pino';
const log = pino({ redact: ['*.password', '*.token'] });

const twilio = createTwilioVerifyClient({
  accountSid: '...',
  authToken: '...',
  serviceSid: '...',
  logger: log,
});

Testing

The library is designed to be mocked at the fetch layer:

import { vi } from 'vitest';
import { createTwilioVerifyClient } from '@smplcty/twilio';

const fakeFetch = vi.fn(async () =>
  new Response(JSON.stringify({ status: 'approved' }), { status: 200 })
);

const twilio = createTwilioVerifyClient({
  accountSid: 'ACtest',
  authToken: 'test',
  serviceSid: 'VAtest',
  fetch: fakeFetch,
});

const ok = await twilio.verifyVerificationCode({ to: '+15555550100', code: '123456' });
expect(ok).toBe(true);

The library's own test suite uses this exact pattern — see tests/create-client.test.ts.

License

MIT — see LICENSE.