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

@payzen/comms

v1.0.2

Published

Read SMS/email OTP codes from JS/TS tests (wraps the payzen-comms Python CLI)

Readme

@payzen-code/comms

Read SMS and email OTP codes from JS/TS tests. This is a thin wrapper around the payzen-comms Python package — it spawns python -m payzen_comms and parses the JSON it returns. Secrets are read by the Python process from the environment, so the JS side never touches credentials.

Install

The wrapper is published to GitHub Packages under the @payzen-code scope. (It can't be git-installed from this monorepo subdirectory — npm/pnpm don't support installing a subfolder of a git repo as a package, so it's published as a normal package instead.)

1. Tell your package manager where @payzen-code lives — add an .npmrc in the consuming repo:

@payzen-code:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=${GITHUB_TOKEN}

GITHUB_TOKEN needs read:packages. In GitHub Actions the built-in secrets.GITHUB_TOKEN works; locally use a personal access token.

2. Install:

pnpm add @payzen-code/comms     # or npm install @payzen-code/comms

3. Install the Python engine (required — needs Python 3.9+). The wrapper shells out to a small Python package; install it once:

pip install "git+ssh://[email protected]/payzen-code/quality.git@master#subdirectory=comms"

(pip does support installing from a subdirectory of a git repo, via #subdirectory=. If the engine is missing at runtime, the wrapper throws a clear EngineNotInstalled error with this exact command.)

CI: add a setup-python step, then run the pip install above. For HTTPS auth use PRIVATE_REPO_TOKEN (same as the existing suites): pip install "git+https://${PRIVATE_REPO_TOKEN}@github.com/payzen-code/quality.git@master#subdirectory=comms".

You need read access to the payzen-code/quality repo (you already have it as a PayZen dev).

Usage

Avoiding stale mail — read this. There is only one real mailbox; every automations+<x>@payzen.com alias lands in it. So a plain lookup by alias can return an email from a previous run. Two defenses, use both:

  1. uniqueEmail() — get a fresh, time-based alias per test.
  2. since — only count mail that arrived after a cutoff you captured before triggering the flow. uniqueEmail() returns that cutoff for you.
import {
  getEmailOtp, getSmsOtp, getLatestEmail, waitForEmail,
  uniqueEmail, now, CommsError,
} from "@payzen-code/comms";

// 1. Get a unique alias + a "since" cutoff BEFORE triggering anything.
const { email, since } = uniqueEmail({ tag: "rollup" });
// -> { email: "[email protected]", since: 1717352999123 }

// ...use `email` in your signup/login flow, which sends the message...

// 2. OTP, scoped to mail newer than `since` so an old code can't be reused.
const { otp } = await getEmailOtp({ address: email, since, timeoutMs: 120_000 });

const sms = await getSmsOtp({ phone: "+15627356015", since, timeoutMs: 30_000 });

// 3. Verify email CONTENT (not just OTP): waitForEmail resolves with the
// matching email, or rejects with a TimeoutError if no match arrives in time.
const confirmation = await waitForEmail({
  address: email,
  contains: ["Payment confirmed", "$1,068.00"], // substrings; all must be in the body
  subjectContains: "confirmed",
  since,
  timeoutMs: 120_000,
});
expect(confirmation.body).toContain("Erica");

try {
  await getEmailOtp({ address: "[email protected]", timeoutMs: 5_000 });
} catch (e) {
  if (e instanceof CommsError) console.error(e.type, e.message); // e.g. "TimeoutError"
}

Playwright example

await page.getByLabel("Email address").fill(email);
await page.getByRole("button", { name: "Get Login Code" }).click();

const { otp } = await getEmailOtp({ address: email, timeoutMs: 120_000 });
await page.getByLabel("Code").fill(otp);

Config

| Env var | Default | Purpose | |---|---|---| | PAYZEN_COMMS_PYTHON | python3 | Python executable used to run the CLI |

API

  • uniqueEmail({ prefix?, domain?, tag? }){ email, since } — fresh alias + cutoff
  • now()number — current Unix-ms, for use as since
  • getEmailOtp({ address, timeoutMs?, bodyContains?, initialWaitMs?, since? }){ otp, subject, receivedAt }
  • getLatestEmail({ address, bodyContains?, maxResults?, since? }){ subject, body, receivedAt }
  • waitForEmail({ address, contains?, subjectContains?, timeoutMs?, initialWaitMs?, maxResults?, since? }){ subject, body, receivedAt }
  • getSmsOtp({ phone, timeoutMs?, since? }){ otp, body, receivedAt }
  • getLatestSms({ phone, maxResults?, since? }){ body, receivedAt }

contains matches substrings (an array requires all of them), so partial text is fine — it never needs to be the whole body.

All reject with a CommsError (carrying .type, e.g. TimeoutError, RuntimeError) on failure.