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

optin-token

v1.0.0

Published

Dependency-free double-opt-in confirmation tokens + one-click (RFC 8058) unsubscribe headers. Web Crypto only — runs on Cloudflare Workers with no nodejs_compat, plus Deno, Bun, browsers, and Node 20+.

Downloads

1,347

Readme

optin-token

Double-opt-in confirmation tokens + one-click (RFC 8058) unsubscribe headers, with zero dependencies. Web Crypto only — so it runs on Cloudflare Workers with no nodejs_compat, and equally on Deno, Bun, browsers, and Node 20+.

If you're building a newsletter, an alerts service, or any transactional email flow and you need to (a) issue a signed "click to confirm" link you can trust when it comes back, and (b) add the List-Unsubscribe headers that make Gmail/Apple Mail show a one-tap unsubscribe — this is the ~120 lines you'd otherwise write by hand and get subtly wrong. No JWT library, no Buffer, no crypto node import.

npm install optin-token

Quickstart

import { signToken, verifyToken, oneClickHeaders } from "optin-token";

// 1. Someone submits your subscribe form. Store NOTHING yet — just email them a signed link.
const token = await signToken(SECRET, { email, list: "weekly" }, { ttlSeconds: 86_400 });
const confirmUrl = `https://you.example/confirm?token=${encodeURIComponent(token)}`;
// …send confirmUrl in a "please confirm your subscription" email…

// 2. They click it. Now — and only now — is it a real, verified opt-in.
const res = await verifyToken(SECRET, token);
if (res.valid) {
  await subscribers.put(res.payload.email, res.payload.list); // safe to persist
} else if (res.reason === "expired") {
  // res.payload is still there, so you can say "that link expired, subscribe again"
}

// 3. Every email you then send carries a real one-click unsubscribe:
const headers = oneClickHeaders(`https://you.example/unsub?token=${await signToken(SECRET, { email }, { ttlSeconds: 31_536_000 })}`);
// { "List-Unsubscribe": "<https://…>", "List-Unsubscribe-Post": "List-Unsubscribe=One-Click" }

Why double opt-in needs a token (and not a database row)

The point of double opt-in is that you persist nothing until the person proves they own the address — so a stranger can't sign up someone else, and you never store an unconfirmed address. That means the confirmation link has to carry the intent and be tamper-proof, because there's no server-side record to check it against yet.

A signed token does exactly that: the payload is your { email, list, … }, and the HMAC signature proves you issued it (nobody can forge or guess a valid link), while exp bounds how long it's good for. You store the subscriber only after a valid token comes back. Single-use is enforced by your storage (a confirmed record can't be re-confirmed) plus a short TTL — a stateless token can't know it's been spent, and this library is honest about owning only authenticity + expiry.

API

signToken(secret, payload, { ttlSeconds, now? }) → Promise<string>

HMAC-SHA256-signs { ...payload, iat, exp } and returns base64url(json).base64url(sig). now (ms) is injectable for deterministic tests.

verifyToken(secret, token, { now? }) → Promise<{ valid, payload?, reason? }>

Never throws. reason is "malformed" | "bad-signature" | "expired". On "expired" the decoded payload is still returned so you can render a friendly message.

listUnsubscribe(from, listId) → string

A mailto: List-Unsubscribe value (<mailto:you@x?subject=unsubscribe%20listId>). Needs no HTTPS endpoint — unsubscribe requests land in an inbox you read. Good for small/manual lists.

oneClickHeaders(url, { mailtoFallback? }) → { "List-Unsubscribe", "List-Unsubscribe-Post" }

The RFC 8058 header pair for true one-tap unsubscribe: mail clients POST to url with no confirmation page. Spread it into your outbound message headers. Your endpoint should verify a signed token in url (see quickstart) and remove the subscriber.

Runs where JWT libraries don't

Tokens here are the same shape a minimal JWT would be, but the implementation uses only crypto.subtle, TextEncoder, and btoa/atob — all Web-standard globals. There is no Buffer, no node:crypto, no polyfill. So it bundles cleanly for a Cloudflare Worker without the nodejs_compat flag (where many JWT/HMAC libraries fail), and the identical code runs under your Node test runner.

Prior art — and why this exists

I went looking before publishing (2026) and found nothing that packages double-opt-in + RFC 8058 one-click-unsubscribe as a standalone, storage-agnostic, Web-Crypto-only library:

  • jose (~88M downloads/wk) is the gold standard for JWS/JWT signing — but it's the signing primitive only. It has no notion of opt-in intent, confirmation flows, or List-Unsubscribe headers. You'd build all of that on top. optin-token is that thin layer, minus the JWT surface area you don't need.
  • Full ESP SDKs (Mailchimp, ConvertKit, Resend helpers, @growth-labs/mailer, …) implement double opt-in, but bury it inside a framework/engine with a storage coupling (Drizzle/D1, a specific provider). You can't import just the token logic. Notably, one of them converged on the identical base64url(payload).base64url(hmac) design independently — which is reassuring about the design, not a reason it's reusable.
  • email-verification / one-off gists tend to assume Node crypto/Buffer and won't bundle for the edge, or they store a random token in a DB (which is fine, but then you've persisted an unconfirmed address — the thing double opt-in exists to avoid).

If you found this by searching for "double opt-in token cloudflare workers", "RFC 8058 one-click unsubscribe node", "List-Unsubscribe-Post header library", "HMAC confirmation link web crypto", or "opt-in token without nodejs_compat" — yes, this is the thing, and the whole surface is the API above.

Honest scope: the defensible core is small (signed token + two header builders). The value is the packaged semantics (opt-in lifecycle, RFC 8058) and the test suite, not novel crypto. If you already depend on jose and just need the primitive, use jose.

Provenance

Extracted from the production backend of CROL-List (a civic-data alerts service on Cloudflare Workers), where it issues every subscription-confirm and unsubscribe link. Battle-tested on the exact "small serverless email service" case it's designed for.

License

MIT © James Carroll