The channels

Every way your app talks to people.

Five channels, one shape. Each resolves its provider from your environment, runs the same hooks, and throws the same normalised errors. Learn one channel and you already know the other four.

The one that started it

Email

Hosted sending with a single token, or 20+ providers of your own behind the same mail(). FormData becomes tidy HTML, delivery events come back normalised, and the dev inbox catches everything you send locally.

  • Hosted forms, scheduling, broadcasts, suppressions and webhooks included
  • Swap providers with one line of config, keep every call site
  • A local dev inbox instead of a nervous test send to yourself
  • Postboi
  • Resend
  • Postmark
  • SES
  • Mailgun
  • 16 more
Read the docs →
mail()
import { mail } from "postboi"

await mail({
  to: "ada@example.com",
  subject: "Your order shipped",
  body: "<p>Track it any time.</p>"
})

For the message that has to land now

SMS

Numbers are normalised to E.164 and never guessed: anything ambiguous throws instead of texting a stranger. Segments are counted before your provider bills them, so the surprise em dash that doubles a bill isn't a surprise.

  • UK-native and global providers, with honest prices shown at init
  • GSM-7 and UCS-2 segment maths on every send
  • Scheduling that rejects rather than silently sending now
  • The SMS Works
  • Twilio
  • Amazon SNS
Read the docs →
sms()
import { sms } from "postboi"

// National formats resolve via your default country
await sms({ to: "07788 223344", message: "Your code is 4291" })

// Schedule where the provider supports it
await sms({
  to: "+44 7788 223344",
  message: "Your table is ready in 15 minutes",
  scheduled_at: { hours: 2 }
})

Templates first, by design

WhatsApp

WhatsApp only allows free-form text within 24 hours of the user's last reply. Most transactional messages happen outside that window, so templates are the front door here, not a fallback buried in provider options.

  • Twilio or Meta's Cloud API, one call either way
  • whatsapp.closed(error) catches the routine failure, so you can hand off
  • Named or positional template variables, both supported
  • Twilio
  • Meta Cloud API
Read the docs →
whatsapp()
import { whatsapp } from "postboi"

try {
  // Free-form text works within 24h of the user's last reply
  await whatsapp({ to, message: "Thanks, on its way!" })
} catch (error) {
  // Window closed? Send the approved template instead
  if (!whatsapp.closed(error)) throw error
  await whatsapp({ to, template: "order_shipped", variables: { name } })
}

The channel that costs nothing

Push

Web Push has no vendor at all: the browser picks the push service, the subscription is the address, and delivery is free. The encryption is ours, WebCrypto only, verified byte for byte against the spec's published test vector.

  • subscribe() in the browser, push() on the server, one import each
  • FCM for Android, APNs direct for Apple, Huawei for the phones without Play Services
  • push.expired(error) makes the routine token cleanup one line
  • Web Push
  • Firebase Cloud Messaging
  • Apple Push Notification service
  • Huawei Push Kit
Read the docs →
push()
// In the browser: one call to subscribe, same in every framework
import { subscribe } from "postboi/push"
const subscription = await subscribe({ key: vapid_public_key })

// On the server: one call to notify
import { push } from "postboi"
await push({ to: subscription, title: "Order shipped", message: "On its way" })

Your app's ops channel

Chat

Slack, Discord, Teams and Telegram, each with a webhook URL or bot token as the only credential. slack() and discord() are separate imports, so posting to several platforms needs no provider choice at all.

  • One env var per platform and your app can talk to the team
  • Titles render natively on every platform
  • Dead legacy Teams URLs are rejected loudly, never silently
  • Slack
  • Discord
  • Microsoft Teams
  • Telegram
Read the docs →
slack() · discord()
import { slack, discord } from "postboi"

// One env var per platform, and your app can talk to the team
await slack({ title: "Deploy", message: "Finished in 42s" })

// Another platform is another import, not a provider choice
await discord({ message: "New release is live 🎉" })

One call. Cheapest first.

send() fans out to every channel you have an address for, or walks a fallback chain and stops at the first success. The spread isn't marginal: push is free and an SMS into Western Europe is 3p, so preferring a cheaper channel saves the whole message, not a slice of it.

Tap a channel to make it fail. Watch the chain route around it.

Delivered on push. 4 channels were never attempted, never billed.

Indicative UK prices. Your provider sets the real ones.

send() · fallback chain
import { send } from "postboi"

const result = await send({
  to: {
    push: subscription,
    email: "ada@example.com",
    sms: "+44 7788 223344"
  },
  channels: "cheapest",
  subject: "Your code",
  message: "Your code is 4291"
})

result.delivered // "push" : sms was never attempted, never billed

The fan-out runs in your process. No hosted orchestrator in the send path, nobody metering the routing, and a WhatsApp message outside its 24-hour window simply hands off to SMS.

Development can't cost you money.

In development, texts and WhatsApp messages are logged, never sent, even with a fully configured provider. A stray email is embarrassing. A stray text costs money, reaches a real handset, and cannot be recalled. So the safe path is the one you get by doing nothing.

Need real delivery locally? Opt out explicitly with POSTBOI_SMS_DEV=send. Chat posts for real, because posting to your own Slack is usually the point.

terminal · NODE_ENV=development
postboi: development, texts are logged, not sent.

postboi (mock sms): +447788223344
  from: POSTBOI
  cost: 1 segment (gsm7)

Your code is 4291

Where the meter would usually be.

Notification platforms charge for orchestration: per notification, per subscriber, or per contact. Postboi is a library, so the orchestration is yours and the meter never appears.

PostboiKnockCourierOneSignalsent.dm
Metered onNothing. Your providers bill at costNotificationsNotificationsSubscribersContacts
Fan-out runsIn your processTheir serversTheir serversTheir serversTheir servers
EmailHosted or bring your ownBring your ownBring your ownBring your ownNot offered
Open sourceYesSDKs onlySDKs onlyNoNo

Checked August 2026. Their pricing pages have the fine print. Ours doesn't exist, because there's nothing to price.

The Postboi mascot

Five channels. One boi.

One command between you and a message that actually arrives.

Start sending, free