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

paperplane-mailer

v1.1.9

Published

4-way mail client/server

Readme

Paperplane mailer

  • 4-way email handling in node.js
  • Build-your-own-mail-server, or just the parts that you need
  • Direct mail delivery (no paid/free-tier proxies)
  • Supports common extensions (pipelining, chunking, utf8, ...)
  • SpamAssasin client built-in
  • Built to be lightweight and performant
  • Built in the big '25 with modern language features and practices

Sending emails

import { Mail, SMTPClient } from "paperplane-mailer"

// DKIM key can be generated with
// npx paperplane-mailer gen [file?]
const client = new SMTPClient('mydomain.com', './dkim.key', /*selector*/'mail')

const mail = new Mail({
	From: 'Me <[email protected]>',
	'Content-Type': 'text/html'
}, `<html><h1>Hello, world!</h1></html>`)

// .send(from, [to...], mail)
client.send('[email protected]', [ '[email protected]' ], mail).then(failed => {
	if(failed.length){
		for(const f of failed)
			console.error('× Failed to send to %s', f)
		return
	}
	console.info('Mail sent to all recipients successfully!')
})

Receiving emails

import { Mail, SMTPServer, SpamAssassin, uniqueId } from "paperplane-mailer"

// Create an SMTP server with a basic filter [ 'mydomain.com' ]
// All incoming mail not meant for [email protected] is rejected for us
// Leave arguments empty to disable filter
const smtpServer = new SMTPServer('mydomain.com')

const tlsOptions = {
	key: fs.readFileSync('mydomain.key'),
	cert: fs.readFileSync('mydomain.cert')
}
await smtpServer.listen(tlsOptions)
console.info('SMTP servers listening on :25, :465, :587')

// We configure spam-detection using spamassasin and spamhaus
const spamc = new SpamAssassin('127.0.0.1')

smtpServer.onIncoming = async (_, from, toArr, mail, rawMail, ip) => {
	// The from parameter can be used to identify the sender but is not always the same as the `From` header that users see. Keep that in mind, and use the from header if in doubt
	console.log('\x1b[35mIncoming from %s to %s\nIP: %s, headers: %d, body: %d bytes',
		mail.get('from') ?? from, toArr, ip, mail.headerCount, mail.body.length)

	// Mail is checked by spamassasin. IP (if specified) is checked by zen.spamhaus.org
	const spam = await spamc.check(rawMail, ip)

	// SpamAssasin by default doesn't strongly penalize invalid DKIM
	// Despite it being an industry standard and very important in verifying email authenticity
	// Here we automatically spam any email without a valid DKIM signature
	if(!spam.symbols.includes('DKIM_VALID') || spam.spam){
		console.warn('Message flagged as spam with score %d and symbols:\n  %s',
			spam.score, spam.symbols.join(' ')+(spam.blocked ? ' SPAMHAUS_IP_BLOCKED':''))
		return
	}else{
		console.info('Message passed spam test with score %d and symbols:\n  %s',
			spam.score, spam.symbols.join(' '))
	}
	// toArr is guaranteed to all match our filter ['mydomain.com']

	// Normalize the email (make sure we have a correct `Date` header, `Message-ID`, ...)
	mail.normalize()

	let count = 0
	for(let to of toArr){
		// Convert [email protected] to user
		to = Mail.getLocal(to) || to

		// This example uses in-memory inboxes, see further below
		const inbox = inboxes.get(to)
		if(!inbox) continue

		inbox.add(mail)
		count++
	}
	console.log('Stored to %d inboxes', count)
}

const inboxes = new Map()

class Inbox extends Map{
	constructor({ password = '' }){
		super()
		this.password = password
	}
	add(mail){
		const id = uniqueId() // Unique identifier in the format: paperplane-<unix_timestamp>-r4nDomBaSe64...
		this.set(id, mail)
		return id
	}
}

inboxes.set('john', new Inbox({ password: 'password123' }))

Downloading emails to a client

import { Mail, POPServer } from "paperplane-mailer"

/* Variables from previous example omitted for brevity */

const popServer = new POPServer('mydomain.com')

await popServer.listen(tlsOptions)
console.log('\x1b[32mPOP servers listening on :110, :995\x1b[m')

popServer.onAuthenticate = (user, pass) => {
	// TODO: password hashing, timing safe equal, etc...
	const inbox = inboxes.get(user = Mail.getLocal(user) || user)
	if(!inbox || inbox.password !== pass) return null
	return { inbox, username: user }
}

popServer.onGetMessages = (auth) => {
	const { inbox, username } = auth

	// Return array of message IDs
	// Conceptually they could be any string as they are just passed to onFetchMessage
	return [...inbox.keys()]
}
popServer.onFetchMessage = (auth, id) => {
	const { inbox, username } = auth

	// Return the Mail object for this message ID, or null
	// This callback is only invoked with message IDs returned by `onGetMessages` with the same auth object so mail being null is a rare edge-case
	return inbox.get(id)
}

Sending emails from a client

import { Mail, SMTPServer } from 'paperplane-mailer'

/* Variables from previous examples omitted for brevity */
//const smtpServer = ...

// Similar to popServer.onAuthenticate
smtpServer.onAuthenticate = (user, pass) => {
	const inbox = inboxes.get(user = Mail.getLocal(user) || user)
	if(!inbox || inbox.password !== pass) return null
	return { inbox, username: user }
}

smtpServer.onOutgoing = (auth, from, toArr, mail) => {
	const { inbox, username } = auth

	// from is guaranteed to match our filter ['mydomain.com']
	// Unlike onIncoming, `from` here actually means the sender
	// Mail.getLocal('[email protected]') returns 'abc'
	// We can return a string to indicate to the sender that delivery failed for that reason
	if(Mail.getLocal(from) != auth.user) return 'Not allowed to send from that email'
	
	// Normalize the email, also setting the `From` header based on the value we just checked
	mail.normalize(from)

	cli.send(from, toArr, mail).then(failed => {
		// IDEA: "Undelivered mail returned to sender"?
	})
}