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

sendfax

v1.0.1

Published

Official TypeScript SDK for SendFax.sh — send real faxes over HTTP, paid per page with x402 or MPP (USDC). No accounts, no API keys.

Readme

sendfax

Official TypeScript SDK for SendFax.sh — send real faxes over HTTP, paid per page with x402 or MPP (USDC). No accounts, no API keys: payment is the credential.

  • Zero runtime dependencies. Web-standard fetch/FormData/Blob only.
  • Runs in Node 18+, Bun, Deno, Cloudflare Workers, and the browser.
  • ESM + CJS, fully typed, tree-shakeable (~2.5 KB min+gz).
npm install sendfax

Quickstart (agents)

An agent authorizes each fax by paying for it. You supply a payment handler that turns the API's 402 challenge into the header that pays for the retry — the SDK never bundles a wallet, so you keep full control of your signer.

import { SendFax, type PaymentHandler } from "sendfax"

// Wire your own x402/MPP signer here (pseudocode — the wallet lib is yours):
const payment: PaymentHandler = async (challenge) => {
  // Sign a payment for `challenge.amountBaseUnits` of `challenge.tokenAddress`
  // on `challenge.chainId`, paying `challenge.recipient`.
  const signed = await myWallet.authorizeCharge(challenge)
  return { headerName: "X-PAYMENT", headerValue: signed } // x402 dialect
  // MPP-native client? return { headerName: "PAYMENT", headerValue: signed }
}

const client = new SendFax({ payment })

const receipt = await client.send({
  to: "+14155550123",
  document: pdfBytes // Uint8Array | ArrayBuffer | Blob
})
// receipt: { id, pageCount, amountUsdMicros, statusUrl }

const fax = await client.waitForDelivery(receipt.id, {
  onStatus: (f) => console.log(f.status) // queued → media_processed → sending → delivered
})
console.log(fax.status) // "delivered"

Wiring a real payment library

The SDK is deliberately signer-agnostic. Two common ways to build the handler:

  • x402-fetch — sign an EIP-3009 USDC authorization for challenge.amountBaseUnits on challenge.chainId and return it as { headerName: "X-PAYMENT", headerValue }.
  • An MPP client (mpp.dev) — feed it challenge.raw.wwwAuthenticate and return the PAYMENT header it produces.

The challenge object gives you everything either needs: amountBaseUnits (string, lossless), currency/tokenAddress, chainId, recipient, decimals, expiresAt, challengeId, plus raw.wwwAuthenticate and raw.paymentRequired if you'd rather parse the headers yourself.

Local development

Against a SendFax dev server in FAX_MODE=mock, use the built-in mock handler to exercise the full flow without a wallet:

import { SendFax, mockPaymentHandler } from "sendfax"

const client = new SendFax({
  baseUrl: "http://localhost:3001",
  payment: mockPaymentHandler() // ⚠️ dev-only: sends x-mock-payment, settles nothing
})

mockPaymentHandler() only works in mock mode — against production it does not pay and every send still 402s.

Human note

This SDK covers the agent rails (x402 / MPP, per-request USDC). If you're a person who just wants to send a fax, use the web flow at sendfax.sh/human (drag a PDF, pay with Stripe Link, card, or USDC) — no code required.

Polling and waiting

// One-shot status (free, no payment):
const fax = await client.get(id)

// Poll until terminal, with control over cadence, timeout, and cancellation:
const controller = new AbortController()
const delivered = await client.waitForDelivery(id, {
  intervalMs: 1500,      // default
  timeoutMs: 120_000,    // default (2 min)
  signal: controller.signal,
  onStatus: (f) => updateUi(f)
})

waitForDelivery resolves with the final FaxPublic on delivered, and rejects with FaxFailedError on failed/expired (carrying failureReason), or a SendFaxError on timeout/abort.

Error handling

Every rejection is a typed subclass of SendFaxError (with .status and the parsed .problem body):

import {
  SendFax,
  PaymentRequiredError,
  InvalidDocumentError,
  InvalidDestinationError,
  NotFoundError,
  FaxFailedError,
  ApiError
} from "sendfax"

try {
  await client.send({ to, document })
} catch (err) {
  if (err instanceof PaymentRequiredError) {
    // No handler, or the handler couldn't satisfy the challenge.
    console.log(err.challenge.amountBaseUnits, err.challenge.tokenAddress)
  } else if (err instanceof InvalidDestinationError) {
    // `to` was not a valid E.164 number.
  } else if (err instanceof InvalidDocumentError) {
    // Not a PDF, encrypted, zero pages, or > 20 MB.
  } else if (err instanceof ApiError) {
    // Any other non-2xx: inspect err.status / err.problem.
  }
}

| Error | When | |---|---| | PaymentRequiredError | 402 — carries the parsed challenge | | InvalidDocumentError | bad/encrypted/empty/too-large PDF (400 / 413 / 422) | | InvalidDestinationError | to not E.164 (422) | | NotFoundError | unknown fax id (404) | | FaxFailedError | waitForDelivery saw failed/expired — has failureReason | | ApiError | any other non-2xx |

API

  • new SendFax({ baseUrl?, fetch?, payment? }) — baseUrl defaults to https://sendfax.sh; inject a custom fetch; set payment to auto-pay 402s.
  • send({ to, document, filename? }) → SendFaxResult — sends multipart; on 402 with a handler, pays and retries once; returns { id, pageCount, amountUsdMicros, statusUrl }.
  • get(id) → FaxPublic — free status lookup.
  • waitForDelivery(id, { intervalMs?, timeoutMs?, signal?, onStatus? }) → FaxPublic — polls to a terminal state.

Pricing

Flat $0.05/page in USDC (amountUsdMicros of 50000 per page; no minimums, no top-ups).

Design

A few conventions worth knowing if you're extending or auditing the SDK:

  • /openapi.json is the contract. Every type here is a projection of the API's OpenAPI document (served at https://sendfax.sh/openapi.json), backed by the route handlers in app/api/v1. The SDK never invents fields the API doesn't return.
  • Two payment dialects, one charge. The 402 advertises the same on-chain USDC charge as either MPP (WWW-Authenticate: Payment …, retry with PAYMENT) or x402 (PAYMENT-REQUIRED: <base64>, retry with X-PAYMENT). parseChallenge handles whichever is present and preserves both raw header values on challenge.raw. In FAX_MODE=mock — and until an x402 facilitator is wired — the live API emits only the MPP header; the x402 one is parser-supported but absent.
  • The PaymentHandler pattern keeps the SDK dependency-free. Rather than bundling a wallet, the SDK exposes one extension point — (challenge, attempt) => { headerName, headerValue } — that you wire your own signer into. mockPaymentHandler() covers dev; real x402/MPP wiring is documented above, not shipped as a dependency.
  • Error taxonomy. Every rejection is a SendFaxError, mapped from HTTP status
    • the RFC 9457 problem+json body: PaymentRequiredError (402, carries the challenge), InvalidDocumentError (400/413/422 document errors), InvalidDestinationError (422, non-E.164 to), NotFoundError (404), ApiError (any other non-2xx), plus FaxFailedError from waitForDelivery.
  • Conformance fixtures. test/fixtures/*.json are real responses captured from the live API (not hand-authored); the unit tests assert the parser against them. Integration tests in test/ run against a local dev server and skip gracefully when it's unreachable.

This package is TypeScript-only today; the structure keeps a future Python/Go SDK possible without reshaping anything here.

Links

MIT © SendFax.sh