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

salamailer

v1.1.0

Published

Zero-knowledge secure email CLI + MCP server — encrypt messages and attachments on your own machine, gate delivery with an SMS code or passphrase

Readme

Salamailer

Zero-knowledge secure messaging for small regulated firms. Send a confidential message + files to anyone (no account for the recipient) with true end-to-end encryption — the server only ever holds ciphertext and keys it cannot unwrap.

Installing from npm? This package is the client — a zero-dependency CLI and MCP server for the hosted service at salamailer.fi. Encryption happens on your machine; you need an account to send.

npm install -g salamailer
salamailer init --origin https://salamailer.fi --email [email protected]
claude mcp add salamailer -- salamailer mcp     # 12 tools for agents

Jump to CLI & MCP for the full tool surface and the agent guardrails. The rest of this file documents the server, which is proprietary and not part of the npm package (see LICENSE).

Zero npm dependencies. All integrations (Stripe, Twilio, Brevo/Postmark, S3-compatible storage) are implemented with fetch + node:crypto — there is no supply chain to audit beyond Node itself. See DECISIONS.md for how the build maps to PLAN.md, and DEPLOY.md for production setup.

Run

Requires Node ≥ 22.20 (built-in node:sqlite landed in 22.5; 22.20 is the current LTS security floor, and the version this is tested against).

npm start     # → http://localhost:3000
npm test      # 146-check e2e suite: crypto, replies, gates, billing, SSO, hardening, CLI/MCP
npm run hashes  # SHA-256 of every served asset (publish for auditors)

With no configuration, email/SMS print to the console + data/outbox.log, billing is off (everyone's on trial), and blobs live on local disk. Copy .env.example → fill in keys → each integration goes live independently.

The security model in one paragraph

Your browser generates a fresh AES-256-GCM content key per message and encrypts everything (filenames included) before upload. The key travels only in the URL fragment of the recipient's link — browsers never send fragments to servers. A separate, rate-limited second channel (SMS code or passphrase) gates the ciphertext download; it is not part of the key, so short codes can't be brute-forced offline. The sender's copy of each key is wrapped under a vault key wrapped under their password — so senders can re-send links, but a full server breach yields nothing readable. Full threat model (including its honest limits): /security in the running app.

Try the full flow

  1. Register (/register) — password is stretched locally; it never leaves the browser.
  2. Compose (/compose) — pick Passphrase for a fully-offline demo. Send.
  3. Open the link from the console/data/outbox.log in an incognito window, enter the passphrase → decrypts in the browser.
  4. Reply securely — right on the read page, no account: the reply (text + files) is encrypted in the recipient's browser under the sender's reply public key, which travelled inside the encrypted message. Read it on /app.
  5. Sent (/app): resend link/code, revoke (real delete), export receipt, inbox of encrypted replies.
  6. Account (/account): plan, quotas, Stripe upgrade (when configured); team owners also get the admin console — per-member usage, an org recipient-domain policy enforced server-side on every channel, and SSO (OIDC) setup. SSO authenticates the session only; the vault still unlocks with the user's password, client-side (/unlock) — IdPs never see keys.

Verify the zero-knowledge claim yourself: DevTools → Network during a send — all request bodies are ciphertext and the #key fragment appears in no request. Then inspect data/salamailer.db and data/blobs/.

CLI & MCP — the AI-agent channel

cli/salamailer.mjs is a zero-dependency CLI that reuses the same crypto module the web app serves, so plaintext and keys exist only on the sending machine:

salamailer init --origin https://app… --email [email protected]   # password prompted, never stored
salamailer send --to [email protected] --message "…" --file contract.pdf --burn [--self-relay] [--lang fi|en|sv]
salamailer list · replies · reply <id> [--save-dir d] · status · contacts · receipt <id> · resend <id> · resend-code <id> · revoke <id>

--self-relay finalizes without handing the link to the server, so the content key never reaches it even transiently — you deliver the printed link yourself (parity with the web compose checkbox).

Saved recipients give a regular correspondent one standing gate passphrase instead of a fresh one per message: salamailer contacts add --to [email protected] --passphrase "…", and every later send to them reuses it automatically (they learn it once). The passphrase is stored encrypted under your vault key, synced across web/CLI/MCP; the server never reads it. Manage the same list on /account.

salamailer mcp runs it as an MCP server (stdio), exposing twelve tools — send_secure_message (with self_relay / save_passphrase), list_sent_messages, get_delivery_receipt, get_account_status, resend_notification, resend_sms_code, revoke_message, save_contact, list_contacts, delete_contact, plus list_replies / read_reply (inbound secure replies, decrypted locally) — full parity with the web dashboard's sender surface, so any MCP-capable agent can run a complete round trip (send → client replies → agent reads the reply) in zero-knowledge mail:

claude mcp add salamailer -- salamailer mcp

Transport auth is an API key (created on the /account page or by init; stored SHA-256-hashed server-side; can't manage other keys). Agent sends are metered against the same plan quotas — agents are senders, senders are who pays. The credentials file (~/.config/salamailer/config.json, 0600) holds the API key and the raw Vault Key: treat it like an SSH private key.

Agent guardrails (config file / init flags) make the MCP a policy enforcement point for a prompt-injected agent:

  • "hideRecipients": true (--hide-recipients) — privacy mode, strictest: the model never sees recipient emails or phone numbers at all. Sends are addressed by saved-contact label (contact arg; resolved locally inside the CLI process), tool outputs mask every address, the standing passphrase is not echoed, and contact management stays human-only. Implies contactsOnly. For teams where even the recipient list is confidential to the AI vendor.
  • "contactsOnly": true (--contacts-only) — the agent may only send to recipients a human has saved as contacts; new recipients are refused with instructions for out-of-band approval, and the agent channel cannot add or remove contacts itself (no self-approval).
  • "allowedRecipientDomains": […] (--allow-domain) — looser: restrict to recipient domains.
  • "attachmentRoot": "/path" — attachments may only come from inside this directory (default: the cwd where salamailer mcp starts).

Every send is gated (SMS code / passphrase) and audit-logged server-side, so the operator's answer to a mis-send attempt is: "the guardrails blocked it — here is the log."

Layout

src/
  server.js      routing, strict CSP, CSRF origin checks, static serving
  handlers.js    API endpoints (auth, messages, gate, billing, webhook)
  config.js      all env-driven configuration (see .env.example)
  billing.js     entitlements, Stripe REST + webhook signature verification
  storage.js     ciphertext blobs: S3 SigV4 adapter or local fs
  notify.js      email/SMS providers (console | brevo | postmark | twilio)
  db.js          node:sqlite schema (PLAN.md §7.2 + billing)
  security.js    scrypt hashing, tokens, HMAC (gates only — never key material)
  ratelimit.js   per-IP/user sliding windows
  retention.js   expiry sweep + real hard-delete
  audit.js       metadata-only audit log
public/
  js/crypto.js   the crypto heart — Web Crypto only, runs on the sender/recipient device
  js/*.js        page logic (register, login, compose, app, account, read)
cli/
  salamailer.mjs  CLI + MCP server (stdio JSON-RPC), reuses public/js/crypto.js
scripts/
  hashes.mjs     published build hashes ("verify us, not trust us")
test/
  e2e.mjs        boots the server, drives the real client crypto end-to-end