Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

285 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Postboi

Every channel, zero config

CI npm runtime Bun framework Svelte


Postboi is a framework-agnostic messaging library optimised for SvelteKit — email first, and now SMS, WhatsApp, push and chat behind the same API. Works with a variety of providers and turns your FormData into tidy HTML emails, with zero configuration.

📖 Full documentation: docs.postboi.app

Features

  • ☁️ Send with no provider account - postboi init, sign in, send. The Postboi provider brings managed sending, domains, lists & broadcasts, suppressions and a message log — one token, no DNS, no card
  • 👨‍💻 Zero configuration - works out of the box with minimal setup
  • 🔌 Provider-based - or bring your own (Resend, SES, Mailgun, Postmark, …) and swap it 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
  • 📮 Hosted forms - no backend? point any HTML form at a hosted endpoint and submissions land in your inbox, spam-checked
  • 🎨 Bring your own templates - body takes any HTML, and the optional postboi/maizzle helper renders Maizzle templates straight into it
  • 📬 Webhooks - receive delivery events (delivered / opened / clicked / bounced) normalized across providers, signatures verified — including which client and device opened the mail
  • 📈 Per-send tracking & one-click unsubscribe - tracking: { opens, clicks } and unsubscribe_url (RFC 8058 headers) on any provider that supports them
  • Schedule & cancel - scheduled_at for future sends, cancel(id) to call them off
  • 📥 Local dev inbox - mail you send in development lands in a mailbox at /__postboi instead of a real inbox — rendered HTML, headers, attachments. No code changes, no second tool, and no way to accidentally mail a customer from your laptop
  • 🍯 Invisible spam protection - a zero-config honeypot, plus invisible captcha — fully managed on the Postboi provider, or bring your own Turnstile key
  • 🧩 <Captcha /> component - one prop-free tag inside your own form, for Svelte, React, Vue and Astropostboi sync bakes in the key
  • 🛡️ Type-safe - full TypeScript support with normalized error handling
  • 💬 Every channel, one shape - sms(), whatsapp(), push(), slack(), discord(), teams(), telegram() and bluesky() resolve, hook and error exactly like mail() — Twilio, The SMS Works, Meta, Web Push, FCM and friends behind them
  • 📡 Multi-channel send() - one call fans out to everything in to, or walks channels: "cheapest" (push → chat → email → whatsapp → sms) and stops at the first success — the fan-out runs in your process, so nobody meters it

Quick start

bunx postboi init

Pick Postboi at the first prompt and you're sending in under a minute. The CLI opens your browser, authorises the device, and writes a single env var — no provider account, no API keys to copy, no DNS, no card:

# .env  (gitignored — the only secret)
POSTBOI_TOKEN=…
import { mail } from "postboi"

await mail({ to: "contact@example.com", subject: "Hi", body: "<p>Hello</p>" })

That's the whole setup. Mail goes out from your you@send.postboi.email address (set reply_to to get replies) until you verify a domain of your own in the dashboard. init also:

  • writes defaults, hooks and the publishable captcha key to a committed postboi.config.ts — everything but the token lives in version control
  • types from to the addresses your account can actually send from, so a wrong one is a type error instead of a runtime from_not_allowed
  • wires managed captcha (<Captcha /> works with no keys) and your webhook secrets

Beyond mail(), the token unlocks message status, recipient lists, broadcasts and double opt-in, your contacts (the audience), suppressions, and batching with idempotency keys — same import, no extra SDK:

import { mail } from "postboi"

await mail.recipients.add("Newsletter", "Ada Lovelace <ada@example.com>")
await mail.contacts.add("ada@example.com", { data: { plan: "pro" } }) // one contact, shared across lists

Full details: The Postboi provider.

Bring your own provider

Prefer Resend, SES, Mailgun, Postmark…? Pick Bring your own provider instead and the CLI collects that provider's credentials. Secrets go to your env file, everything else to the committed config — best case, still a single env var:

// postboi.config.ts  (committed)
import { config } from "postboi"

export default config({
	provider: "resend",
	default: { from: "no-reply@example.com" },
})
# .env  (gitignored — secrets only)
RESEND_API_KEY=re_xxxxxxxx

Every example below is identical either way: mail() picks up whichever provider is configured — no provider import, no constructor.

On SvelteKit, a form action is a one-liner:

// +page.server.ts
import { mail } from "postboi/kit"

export const actions = { default: mail }

Or skip the server file entirely with remote functions (experimental — set kit.experimental.remoteFunctions: true; postboi init adds the required optimizeDeps: { exclude: ["postboi/remote"] } to vite.config for you):

<script>
	import { mail } from "postboi/remote"
</script>

<form {...mail}>
	<input {...mail.fields.contact.name.as("text")} required />
	<input {...mail.fields.contact.email.as("email")} required />
	<button disabled={!!mail.pending}>Send</button>
</form>

{#if mail.result?.success}<p>Thanks!</p>{/if}

Nested fields (contact.name) group in the email exactly like the classic contact→name syntax, spam protection and attachments included. For a custom provider or forced fields, build your own with remote(...) from postboi/kit.

Topic Docs
Quick start — the CLI (postboi init) docs.postboi.app/quick-start
The Postboi provider docs.postboi.app/provider
Manual setup (no CLI) docs.postboi.app/manual-setup
SvelteKit form actions docs.postboi.app/sveltekit
FormData → HTML tables docs.postboi.app/formdata
All providers & their options docs.postboi.app/providers
Hooks, global config, retries, bulk send docs.postboi.app/config
API reference docs.postboi.app/api

Cloudflare Workers work the same way — bindings are read as env vars, and the postboi/vite plugin bundles postboi.config.ts in place of the filesystem auto-load. See Cloudflare Workers.

Beyond email

Every channel is the same three moves: bunx postboi init --sms (or --whatsapp, --push, --chat), credentials land in env, then call the function. Same hooks, same normalized errors, same zero config:

import { sms, whatsapp, push, slack, send } from "postboi"

await sms({ to: "+447788223344", message: "Your code is 4291" })
await whatsapp({ to: "+447788223344", template: "order_shipped", variables: { name: "Ada" } })
await push({ to: subscription, title: "Order shipped", message: "On its way" })
await slack({ message: "Deploy finished" })

// …or one call that stops at the first (cheapest) channel that works:
await send({
	to: { push: subscription, sms: "+447788223344" },
	channels: "cheapest",
	message: "Your code is 4291",
})

In development, texts and WhatsApp messages are logged, never sent — the same no-way-to-mail-a-customer-from-your-laptop guarantee the dev inbox gives email, but stricter, because a stray text costs money and can't be recalled.

Channel Docs
Multi-channel send() docs.postboi.app/send
SMS (and RCS) docs.postboi.app/sms
WhatsApp docs.postboi.app/whatsapp
Push (Web Push, FCM) docs.postboi.app/push
Chat (Slack, Discord, …) docs.postboi.app/slack

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

The docs site is the SvelteKit app at the repo root — bun run dev serves it locally.

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

Releasing

Maintainers: npm run release -- <patch|minor|major> publishes the library and creates the GitHub release. See RELEASING.md for the full process, including snapshotting the versioned docs.

About

Zero-config messaging for TypeScript. mail(), sms(), whatsapp(), push(), slack() — one shape, any provider, framework-agnostic, SvelteKit-first.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Contributors

Languages