Notifications for B2B SaaS

Notifications,made simple.

One API call to send them. A ready-made feed your users can read. Push to every device they own. Live in your product this quarter, not after six weeks of wiring.

  • REST + WebSocket
  • JS SDK
  • 19 languages
  • No personal data

One

Easy to send

One request. We deal with the fan-out, the retries and the ordering.

Two

Easy to drop in

It lives inside your app, not in some separate tool your users have to go and visit.

Three

You get the feed

Not just delivery. The history, what's been read, the unread count. Everything you need to draw a bell icon.

One API call puts a notification in the feed., on the phone., in the browser. Most teams need two or three vendors to do that.

01The problem we built it for

Nineteen languages.One forty-line switch statement.

This one happened to us. You pick a notification vendor. You need nineteen languages. The vendor has nowhere sensible to put them, so all nineteen end up jammed into one template, one branch per language, each with its own nested ifs.

  • ✕It lives in the vendor's web UI, so you can't review it in a pull request, diff it, or test it.
  • ✕A translator can't touch it without editing code.
  • ✕You can't see which languages are missing or out of date. You find out by reading the switch.
  • ✕One typo breaks all nineteen languages at once.

So the web team gives up and writes all the copy again, in their own files. Now the same sentence exists twice, kept by two different people, and the apps slowly drift until they tell the same person two different things.

Before · one template, nineteen branches

1{{! invoice_approved · edited in the vendor's web UI }}
2{{#switch user.locale}}
3 {{#case "en"}}
4 {{#if count == 1}}{{actor}} approved an invoice
5 {{else}}{{actor}} approved {{count}} invoices{{/if}}
6 {{/case}}
7 {{#case "pl"}}
8 {{#if count == 1}}{{actor}} zatwierdził(a) fakturę
9 {{else if (mod count 10) >= 2 && (mod count 10) <= 4}}
10 {{actor}} zatwierdził(a) {{count}} faktury
11 {{else}}{{actor}} zatwierdził(a) {{count}} faktur{{/if}}
12 {{/cas}}
13 {{#case "de"}}
14 {{#if count == 1}}{{actor}} hat eine Rechnung freigegeben
15 {{else}}{{actor}} hat {{count}} Rechnungen freigegeben{{/if}}
16 {{/case}}
17 {{#case "ar"}}
18 {{! six plural forms. good luck }}
19 {{/case}}
20 {{#case "fr"}} …
21 {{#case "es"}} …
22 {{#case "ja"}} …

+ 16 more cases, each with its own nested ifs

With Bzzt · one row per language

Template · invoice.approved17 / 19 up to date
en{actor} approved invoice {invoice}✓
plFaktura {invoice} została zatwierdzona przez {actor}✓
deRechnung {invoice} wurde von {actor} freigegeben✓
frLa facture {invoice} a été approuvée par {actor}✓
es{actor} aprobó la factura {invoice}✓
it{actor} ha approvato la fattura {invoice}✓
pt{actor} aprovou a fatura {invoice}✓
nl{actor} heeft factuur {invoice} goedgekeurd✓
sv{actor} godkände faktura {invoice}✓
da{actor} godkendte faktura {invoice}✓
nb{actor} godkjente faktura {invoice}✓
fi{actor} hyväksyi laskun {invoice}✓
csFaktura {invoice} byla schválena uživatelem {actor}✓
skNo copy yetMissing
hu{actor} jóváhagyta a(z) {invoice} számlát✓
ro{actor} a aprobat factura {invoice}✓
tr{actor}, {invoice} numaralı faturayı onayladıStale
ja{actor}さんが請求書{invoice}を承認しました✓
arوافق {actor} على الفاتورة {invoice}✓

Copy is data, not code. One row per language. Reviewable, diffable, and a translator can edit it. Plus a grid showing exactly what's missing.

02What it does

Six things, each one properly configurable.

Everything here works today. Each claim comes with the reason you can believe it.

01 Send

One call reaches one person or a whole company.

Address a user, or every user at a customer. We take it from there.

attempt 1 → id nt_7c1e… sent

attempt 2 → id nt_7c1e… already sent

Why it holds

A retry can't send the same thing twice. Ids come from the content, not from the attempt.

02 Embed

Works with any stack.

A REST API and a live socket, plus a JavaScript SDK if you want it.

RESTWebSocketJS SDK{ text, data }

Why it holds

You get the finished text and the raw data, so you can render whichever suits you.

03 Write

Nineteen languages, one row each.

Edit copy with a live preview, one language at a time.

1 nowe zadanie

3 nowe zadania

5 nowych zadań

Why it holds

Proper plural rules, not find-and-replace. Polish has four plural forms. Arabic has six.

04 Deliver

In-app feed plus push to every device.

Phones and browsers, from the same call that fills the feed.

feediOSAndroidweb push

Why it holds

Notifications turn up as your app, with your icon and name. We set that up with you.

05 Operate

A console your customers run themselves.

Types, templates, broadcasts and a delivery log, without filing a ticket with you.

push · ios · 14:02

✕ Token expired. The user reinstalled the app.

Why it holds

The delivery log tells you what a failure actually means, not just an error code.

06 Prove

A permanent record of what was sent.

To who, when, and in which language.

user_c41e · pl · 14 Oct 2025, 09:12

“Masz 5 nowych zadań w projekcie Q4”

Why it holds

It keeps the finished sentence, so a year later you can show exactly what someone read.

03Why teams switch

Most tools hand you their idea of a notification.We take yours.

As customisable as we can make it. When something's missing we'd rather add it than tell you to work around it.

  1. 01

    It turns up as your app, not ours.

    A push lands with your icon, your name, your wording. Nothing hints that anyone else was involved, because nobody was. Each customer sends through their own account with Apple and Google, so it really is their app doing the talking.

    →We set that up with you when you join. Nobody on your team needs to know what Firebase is, let alone configure it.

  2. 02

    Your translators stop waiting for engineers.

    Copy gets edited in the console, one language at a time, with a grid showing what's missing or stale. Someone who writes for a living can fix a Polish sentence without a developer, a release, or a ticket.

  3. 03

    It looks like your product, because you build it.

    We hand back the finished text and the data behind it. Your designers build the feed so it matches your product. You're not dropping in someone else's component and then fighting its CSS.

  4. 04

    Almost nothing is fixed by us.

    You decide what a notification even is. The types, the data each one carries, which inbox it lands in, what fields a template fills, what labels you hang on your users. A template isn't forced into a title and a body, because your type says what shape its content has.

  5. 05

    Nobody gets told the same thing twice.

    If a send gets retried because the network hiccupped, it can't reach the same phone twice. Sounds obvious. It's also the single most common complaint about notification systems.

  6. 06

    Read it once, read it everywhere.

    Open a notification on your phone and the badge clears on your laptop. Read state is shared across every device someone uses.

04Small on purpose

Few features.Lots of options.

Notification tools grow by piling on features. Campaign builders. Behavioural segmentation. Send-time optimisation. A/B tests. Digests. Quiet hours.

Most teams use almost none of it. They pay for all of it anyway, in the price, in setup time, and in a console nobody can find their way around.

We do the bits you actually use, each one properly configurable, with nothing to ignore. You should be sending real notifications the day you sign up, not after a discovery call.

You defineyour notification typesyour data on each typeyour inboxesyour template fieldsyour user labels

We left this out so the rest stays simple.

Every one of these is a choice

—Campaign builders, segmentation

Different product, different buyer. Your marketing team already has one.

—Analytics dashboards, open rates

Your product already has analytics. We'd be building a worse version of it.

—Per-user notification preferences

The type decides which channels fire. One place to look instead of two that can disagree.

—Quiet hours, send-time optimisation

Needs everyone's timezone. We don't hold personal data, and we're not going to start for a feature hardly anyone turns on.

—Digests and bundlingRoadmap

Genuinely on the roadmap. We'd rather build it when someone needs it than guess the rules now.

—Email and SMS

Push and in-app only. Email means addresses, bounces, unsubscribes and deliverability. That's a whole product.

05Privacy

No names.No emails.No phone numbers.

We don't hold anything about your users. Just an id you already use, a language, and whatever labels you decide to add. There's very little for a privacy review to even look at.

The whole user record

{
  "id": "user_8f2c",
  "locale": "pl",
  "labels": { "plan": "pro", "region": "eu" }
}
Never storednameemailphonetimezone

06Who it's for

Built for product engineers at B2B SaaS companies. Can you ship it this quarter, and does it keep your customers properly separated? Yes.

A good fit if

  • ✓Your customers are companies, not individuals.
  • ✓Each of them wants their own branding, their own push setup and their own settings.
  • ✓You ship in more than one language and want translators to own the copy.
  • ✓You'd rather build the feed to match your product than restyle someone else's widget.

Probably not us if

  • —You sell to consumers. Your needs are simpler and there are cheaper options.
  • —You want campaigns, segments or subject-line tests. That's a marketing tool for a growth team. Different product.
  • —You need email or SMS. We do push and in-app, properly, and nothing else.

Send your first notification today.

Not after a discovery call and a solutions engineer. Sign up, make one request, and watch it land in the feed, on the phone and in the browser.

terminal
$ curl -X POST $BZZT_API/v1/notifications \
    -H "Authorization: Bearer $BZZT_KEY" \
    -d '{"type":"invoice.approved",
         "to":"user_8f2c"}'

{ "status": "accepted", "channels": ["feed", "ios", "web"] }