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

postboi

v0.0.6

Published

<div align="center"> <img src="https://raw.githubusercontent.com/darbymanning/postboi/refs/heads/main/static/logo.svg" alt="Postboi" width="250" />

Downloads

24

Readme

Gotta emails son? This here ya boi

CI npm


Postboi is a dead simple email library that works anywhere you can run JavaScript/TypeScript, but it's optimized for SvelteKit. It's got a provider-based architecture so you can swap email providers whenever you fancy, plus some genuinely useful form handling that turns your FormData into tidy HTML emails without you having to write a single line of HTML yourself.

The core email sending functionality is framework-agnostic and works in Node.js, Bun, Deno, or edge runtimes.

Features

  • 🔌 Provider-based - swap email providers without changing your code
  • 📝 Smart FormData parsing - automatically converts FormData to HTML tables
  • 🎯 Grouped fields - organize form fields with fieldset→field syntax
  • 📎 Attachments - attach files directly from form inputs or file objects
  • 🛡️ Type-safe - full TypeScript support with proper error handling
  • zero bullshit - no unnecessary abstractions, just works

Providers

Currently supported:

  • ZeptoMail — fully implemented and tested
  • ... oh, yeah, that's it 😅

Want to add another provider? Quit being a baby and open a PR.

Installation

# if you're cool
bun add postboi

# or if you used to be cool
pnpm add postboi

# or if you're weird
yarn add postboi

# or if you smell like used knickers
npm install postboi

Quick Start

Basic Usage with ZeptoMail

import Postboi from "postboi/zepto"

const mail = new Postboi({
	token: "your-zeptomail-api-token",
	default_from: "[email protected]",
	default_to: "[email protected]",
})

// simple string body
await mail.send({
	to: "[email protected]",
	subject: "hello",
	body: "hello world",
})

With SvelteKit Form Actions

// +page.server.ts
import Postboi from "postboi/zepto"
import { ZEPTO_TOKEN, EMAIL_FROM_ADDRESS, EMAIL_TO_ADDRESS } from "$env/static/private"
import { fail } from "@sveltejs/kit"

const mail = new Postboi({
	token: ZEPTO_TOKEN,
	default_from: EMAIL_FROM_ADDRESS,
	default_to: EMAIL_TO_ADDRESS,
})

export const actions = {
	async default({ request }) {
		const form_data = await request.formData()

		try {
			await mail.send({ body: form_data })
			return { success: true }
		} catch (error) {
			if (mail.is_error(error)) {
				return fail(400, { error: error.error.message })
			}
			return fail(400, { error: String(error) })
		}
	},
}
<!-- +page.svelte -->
<script lang="ts">
	import { enhance } from "$app/forms"
</script>

<form method="POST" use:enhance enctype="multipart/form-data">
	<input type="hidden" name="_subject" value="Contact Form" />

	<input name="contact→name" placeholder="Name" required />
	<input name="contact→email" type="email" placeholder="Email" required />
	<textarea name="details→message" placeholder="Message" />

	<input type="file" name="details→attachments" multiple />

	<button type="submit">Send</button>
</form>

That's it. The FormData automatically becomes a nice HTML table in the email.

FormData Magic

Postboi handles FormData intelligently. Here's what it does:

Special Fields

These fields get extracted and don't appear in the email body:

  • _to — recipient address (overrides default)
  • _from — sender address (overrides default)
  • _subject — email subject
  • _reply_to — reply-to address
  • _cc — cc addresses (comma-separated or array)
  • _bcc — bcc addresses (comma-separated or array)

All special field values can be base64 encoded and will be automatically decoded.

Grouped Fields

Use fieldset→field syntax to group related fields:

<input name="contact→name" />
<input name="contact→email" />
<input name="contact→phone" />

<input name="order→product" />
<input name="order→quantity" />

This creates sections with headers in the email:

Contact
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Name:     John Doe
Email:    [email protected]
Phone:    +44 1234 567890

Order
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Product:  Widget Pro
Quantity: 2

Attachments

Files are automatically detected and attached:

<input type="file" name="details→files" multiple />

Or programmatically:

await mail.send({
	to: "[email protected]",
	body: "check these out",
	attachments: [file1, file2], // File objects or array
})

Email Address Formats

Postboi accepts multiple email formats because flexibility is good:

// plain string
'[email protected]'

// object with display name
{ address: '[email protected]', name: 'User Name' }

// Display-name format (RFC 5322)
'User Name <[email protected]>'

// arrays for multiple recipients
['[email protected]', '[email protected]']

// mixed formats in arrays
[
  '[email protected]',
  { address: '[email protected]', name: 'User Two' },
  'User Three <[email protected]>'
]

API Reference

Postboi Class (ZeptoMail Provider)

import Postboi from 'postboi/zepto'

const mail = new Postboi({
  token: string                    // ZeptoMail API token (required)
  default_from?: string            // default sender address
  default_to?: string              // default recipient address
})

// send email
await mail.send(options: SendOptions): Promise<SendResponse>

// check if error is a ZeptoMail error
mail.is_error(error: unknown): error is SendError

SendOptions

interface SendOptions {
	to?: Email | Email[] // recipient(s)
	from?: Email // sender
	reply_to?: Email | Email[] // reply-to address(es)
	cc?: Email | Email[] // cc recipient(s)
	bcc?: Email | Email[] // bcc recipient(s)
	subject?: string // email subject (default: "Mail sent from website")
	body: string | FormData // email body or formdata to parse
	formatter?:
		| {
				// customize label formatting
				fieldset?: ((label: string) => string) | null | false
				name?: ((label: string) => string) | null | false
		  }
		| null
		| false // set to null/false to disable formatting
	attachments?: File | File[] // file attachments
}

Email Type

type Email =
	| string // plain address or "Name <address>"
	| { address: string; name?: string }

Error Handling

try {
	await mail.send({ to: "bad@email", body: "test" })
} catch (error) {
	if (mail.is_error(error)) {
		// ZeptoMail error with structure
		console.error(error.error.code) // error code
		console.error(error.error.message) // error message
		console.error(error.error.request_id) // request ID for support
	} else {
		// generic error
		console.error(error)
	}
}

Development

# install dependencies
bun install

# start dev server
bun run dev

# type checking
bun run check

# linting
bun run lint

# run tests
bun run test

# build library
bun run build

Testing

The test suite uses Vitest and mocks the ZeptoMail client:

bun run test        # run tests once
bun run test:unit   # watch mode

Contributing

PRs welcome! Especially for new email providers. Make sure you:

  • Follow the existing code style (snake_case, no semicolons)
  • Add tests for new features
  • Run bun run check and bun run lint before pushing