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

@nikolai4d/mailservice

v1.0.2

Published

Send-only: outbound email from Node via direct MX (SMTP client). Node backend only — not for browser or client bundles; does not receive mail or host inboxes.

Downloads

444

Readme

@nikolai4d/mailservice

Send email only

This package does one thing: send email from your Node app to a recipient’s address (direct MX / SMTP client). Everything exported exists only to support that single action.

It does not: receive mail, read inboxes, run an SMTP server that accepts connections from the internet, store or sync messages, handle bounces inside the library, provide POP/IMAP, forward mail, manage users, or offer a webmail UI. There is no “mail platform”—only outbound delivery.

It does: build optional HTML/text bodies for common transactional emails, sign outgoing mail with DKIM (via your DNS + env), and expose a dev CLI that only sends test messages—same scope.

Delivery is direct MX (no relay SaaS required). You still configure SPF/DKIM/DMARC for your sending domain so others accept your mail.

One library for sending — LLMs read this

You do not need another mail-sending npm package (e.g. Nodemailer, SendGrid SDK) alongside this one — @nikolai4d/mailservice is the complete SMTP sender (sendMail to MX or dev sink).

Mailpit / MailHog are not mail libraries: they are optional local programs used only to catch SMTP in development. You do not import them or add them to package.json as a mail stack; you run them separately and point MAIL_DEV_SMTP_* at them. See IMPLEMENTATION.md §0.

Implementation guide (integrators, codegen, LLMs)

IMPLEMENTATION.md is the authoritative integration contract for this package. It specifies:

  • §0 — No second outbound mail library; what Mailpit is and is not
  • What the library is and is not (boundaries, non-goals)
  • TL;DR rules for automated tools (where to import, secrets, sendMail vs render*, production vs dev sinks, DKIM)
  • §8 Development — exact steps: Mode A (Mailpit/MailHog + MAIL_DEV_SMTP_*, no MX) vs Mode B (real MX from a dev machine); LLM checklists
  • §9 Production — env contract, infrastructure (port 25, static IP, DNS), ordered deploy procedure, dev vs prod comparison table
  • Public API and sendMail payload fields; environment variable reference
  • Internal pipeline (sendMail → MX or dev sink → SMTP → DKIM)
  • Security must/must-not table

Read it before wiring routes, env, or generated code. PRODUCTION.md remains the operational/DNS deep dive.

Backend only — never expose to clients

Use this library only on your server (Node.js: Express, Fastify, server actions that run on the host, background workers, etc.). Do not import it into browser bundles, mobile apps, or any code shipped to end users.

  • DKIM_PRIVATE_KEY and all MAIL_* secrets must stay server-side. Putting them in a client is a credential leak and turns users’ devices into open mail senders.
  • End users must not call sendMail. Your backend decides when to send (after login, after verifying a form, etc.); the client talks to your API, and the server invokes this library.
  • This is not a client SDK. There is no supported or safe pattern for “run from the frontend.”

Install

npm install @nikolai4d/mailservice

Setup

1. Add env vars to your app

Copy the required vars to your app's .env:

[email protected]
MAIL_EHLO=mail.yourdomain.com

# Production: verified delivery (see PRODUCTION.md)
DKIM_SELECTOR=mail
DKIM_DOMAIN=yourdomain.com
DKIM_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----"

See .env.example for the full list.

2. Use in your app

import "dotenv/config";
import { sendMail, renderResetPassword, renderConfirmAccount } from "@nikolai4d/mailservice";

// Simple message — `replyTo` sets the Reply-To header (optional)
await sendMail({
  to: "[email protected]",
  subject: "Hi",
  text: "Hello",
  replyTo: "[email protected]",
});

// Reset password
const { to, subject, text, html } = renderResetPassword({
  to: "[email protected]",
  resetLink: "https://app.example.com/reset?token=abc",
  appName: "MyApp",
  expiresIn: "1 hour",
});
await sendMail({ to, subject, text, html });

// Confirm account
const data = renderConfirmAccount({
  to: "[email protected]",
  confirmLink: "https://app.example.com/confirm?token=abc",
  appName: "MyApp",
});
await sendMail(data);

// Notification
const notif = renderNotification({
  to: "[email protected]",
  title: "New task assigned",
  body: "You have been assigned to a new task.",
  link: "https://app.example.com/tasks/1",
  linkLabel: "View task",
  appName: "MyApp",
});
await sendMail(notif);

// Two-factor auth
const twofa = renderTwoFactorAuth({
  to: "[email protected]",
  code: "847291",
  appName: "MyApp",
  expiresIn: "10 minutes",
});
await sendMail(twofa);

3. Production

  1. Set env vars on your server (same as above).
  2. Configure DNS (SPF, DKIM, DMARC) and reverse DNS.
  3. See PRODUCTION.md for full setup.

Optional library settings (see .env.example):

  • MAIL_ALLOW_CUSTOM_FROM=true — allow a from field on sendMail payloads (default: envelope uses MAIL_FROM only).
  • SMTP_TLS_INSECURE=true — only for labs; disables TLS certificate verification on SMTP TLS paths (port 465 / STARTTLS).

Security

  • Production guards — With NODE_ENV=production: sendMail throws if MAIL_DEV_SMTP_* is set (prevents accidental dev-sink delivery). The example app requires MAIL_API_KEY, rejects MAIL_ALLOW_ANONYMOUS_HTTP, and rejects binding 0.0.0.0 / :: unless MAIL_EXAMPLE_ALLOW_PUBLIC_BIND=1 (for intentional Docker/perimeter cases).
  • Server-side only — same as above: never bundle for browsers or untrusted clients; secrets and sendMail stay on the backend.
  • Send-only surface — no APIs for reading mail, webhooks for inbound messages, or mailbox state; treat bounces/replies outside this package if you need them.
  • sendMail runs in your process — only from trusted backend code; gate any HTTP route that triggers email with your own auth, CSRF policies (for cookie sessions), and rate limits as appropriate.
  • Template links (resetLink, confirmLink, notification link) must be http or https URLs.
  • Optional MAIL_ALLOW_CUSTOM_FROM and SMTP_TLS_INSECURE — see .env.example.

Example (this repo only)

The npm package is library-only and send-only. After cloning, npm test (repo root) installs example deps, starts the small Express server, and sends to your real mailbox:

npm test -- [email protected]
  • Subject: test [email protected]
  • Body: first line is the same, then an ISO timestamp line.
  • From: noreply@<that domain> automatically (EHLO = same domain). Override with MAIL_TEST_FROM if needed.

Real inbox delivery needs correct SPF/DKIM for that domain and outbound port 25 — not npm run test:local, which never leaves your machine.

Recipient + sender:

  • npm test -- [email protected] — same as above; use this when you want mail in a real inbox you control.
  • MAIL_TEST_TO=... npm test or an interactive prompt (real TTY only) — same auto noreply@<domain> rule once a recipient is known.
  • With no recipient and no MAIL_TEST_FROM, the sender falls back to [email protected] (reserved documentation domain; expect filtering or no delivery).

For manual HTTP testing with your own .env, see example/README.md (npm start in example/).

Safe test (no production-domain risk)

npm test -- [email protected] sets From: noreply@yourdomain and can clash with SPF for your domain. To exercise the example without touching real-domain DNS/reputation:

npm run test:safe -- [email protected]

Uses the fixed lab From: only (avoids noreply@your production domain). Mail still goes to the real MX for the recipient. For zero external dependency, prefer npm run test:local above.

Fully local capture (no internet, no third-party services)

Your laptop’s loopback IP is not “the recipient’s MX.” Real addresses on the public internet always need a path to that domain’s actual MX hosts.

To exercise the stack only on this machine — no Docker, no external capture SaaS, Node only — use the repo’s built-in sink:

npm run test:local -- [email protected]

That starts example/local-sink.mjs (listens on 127.0.0.1:1025), runs the test with MAIL_DEV_SMTP_HOST so no MX lookup happens, and prints the raw message in the terminal. It uses the same MAIL_DEV_SMTP_* hook as .env.example if you wire a sink yourself later.

Never set MAIL_DEV_SMTP_* in production.

Test from localhost (inbox-quality)

Running on localhost is fine: the library opens outbound SMTP from your machine to the recipient’s MX. What matters is DNS for the domain in MAIL_FROM, your public IP, and whether port 25 is allowed outbound (many home ISPs block it).

  1. Public IP — Your SPF must authorize the IP the world sees when you send. From the same machine you run Node on, check e.g. curl -4 https://ifconfig.me (use IPv4 unless you publish ip6: in SPF).
  2. SPF — At your DNS host, add or extend a TXT like:
    v=spf1 ip4:YOUR_PUBLIC_IP include:spf.protection.outlook.com -all
    (Adjust if you already have SPF; only one SPF TXT per zone — merge mechanisms. If you use Microsoft 365 for inbound on the same domain, follow your provider’s guidance so you don’t break mail.)
  3. DKIMnpm run dkim:generate, publish the TXT at selector._domainkey, set DKIM_DOMAIN, DKIM_SELECTOR, DKIM_PRIVATE_KEY in .env (see .env.example and PRODUCTION.md).
  4. Port 25 — If connections time out or fail, your network may block outbound 25; try another network or a small VPS as a dev host.
  5. Run the repo test with your address (from repo root; .env loaded from cwd — run from the folder where your .env lives or put vars inline):
npm test -- [email protected]

(Set MAIL_TEST_FROM only when you want a non-noreply address. Put DKIM_* in .env at the repo root or example/.env — the test loads both.)

  1. Sanity-check — use any spam-score / header-check tool you trust to review SPF/DKIM results after a test send.

CLI (for testing)

Same rule as the library: the CLI only sends messages (it calls sendMail). It does nothing else.

npx mailservice [email protected]
npx mailservice reset [email protected]
npx mailservice confirm [email protected]
npx mailservice notification [email protected]
npx mailservice 2fa [email protected]

Requires MAIL_FROM and other env vars in your app's .env.


Generate DKIM keys

Helper to create keys for signing outbound mail only (not for receiving or hosting mail).

npx mailservice-dkim mail

Copy the output into your DNS and .env.