🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

salamailer

Package Overview
Dependencies
Maintainers
1
Versions
1
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

salamailer

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

latest
npmnpm
Version
1.0.0
Version published
Weekly downloads
63
-43.24%
Maintainers
1
Weekly downloads
 
Created
Source

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 you@firm.fi
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.5 (built-in node:sqlite).

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

  • Register (/register) — password is stretched locally; it never leaves the browser.
  • Compose (/compose) — pick Passphrase for a fully-offline demo. Send.
  • Open the link from the console/data/outbox.log in an incognito window, enter the passphrase → decrypts in the browser.
  • 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.
  • Sent (/app): resend link/code, revoke (real delete), export receipt, inbox of encrypted replies.
  • 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 you@firm.fi   # password prompted, never stored
salamailer send --to client@x.fi --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 partner@co.fi --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

Keywords

mcp

FAQs

Package last updated on 27 Jul 2026

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts