Notify docs
Getting started

Your first notification.

Create a type, send it to a user, read it from their inbox. About ten minutes, and no phone, browser permission or Apple account needed: the inbox works on its own.

You need a ProductCraft workspace (sign up at console.productcraft.co) and Node.js 20.3 or later. Not on Node? Every step is a plain HTTP call: see Python, Go, Ruby and curl.


1

Turn Notify on and create a key

  1. In the console, open Workspace › Services and enable Notify. Until it is on, the API answers 403 SERVICE_NOT_ENABLED.
  2. Create a workspace API key under Workspace › API keys with the Send notifications preset (send, write recipients, mint recipient tokens, read the message log) plus notify.type.manage for this walkthrough.
  3. Put it in your server’s environment as PRODUCTCRAFT_API_KEY, with the workspace id as PRODUCTCRAFT_WORKSPACE_ID, and npm install @productcraft/notify.
lib/notify.ts
// lib/notify.ts
import { Notify } from "@productcraft/notify";

// Reads PRODUCTCRAFT_API_KEY and PRODUCTCRAFT_WORKSPACE_ID from the
// environment. Server only: never ship this key to a browser or an app.
export const notify = new Notify();

2

Create a category and a notification type

A category is what users switch on and off (“Comments”). A notification type is one kind of message (comment.created): its category, a template per channel and language, and its routing. This one only goes to the inbox; you add push and email later without touching the send call.

The console does the same under Notify › Notification types, with a preview. Keeping types in code is covered in Notification types as code.

setup.ts
// Categories are what your users switch on and off.
await notify.categories.create({
  key: "comments",
  name: "Comments",
  description: "Replies and mentions on your posts",
  defaults: { inbox: "on", email: "on", push: "on", webpush: "on" },
});

// A notification type: its category, a template per channel and locale,
// and routing. Safe to run on every deploy: the same body changes nothing.
await notify.notificationTypes.replaceByKey("comment.created", {
  name: "New comment",
  category_key: "comments",
  routing: { always: ["inbox"], fallback: [] },
  templates: [
    {
      channel: "inbox",
      locale: "en",
      body: {
        title: "{{author}} commented on {{post_title}}",
        body: "“{{excerpt}}”",
        action_url: "https://app.example.com/posts/{{post_id}}",
      },
    },
  ],
});

3

Identify the user and send

Recipients are keyed by your own user id, so you never store ours. The send returns 202 as soon as it is stored; delivery happens in the background. The idempotencyKey makes the call safe to retry: use an id from your own event, as here.

send.ts
// Your own user id is the key. Call it on sign-up and when the profile changes.
await notify.recipients.identify("user_123", {
  email: "ada@example.com",
  name: "Ada",
  locale: "en",
  timezone: "Europe/Lisbon",
});

const sent = await notify.notifications.send(
  {
    type: "comment.created",
    recipients: ["user_123"],
    data: { author: "Grace", post_title: "Hello", excerpt: "Lovely post!", post_id: "p_42" },
  },
  // Tie the key to your own event: a retry, even from another process,
  // returns the same notification instead of sending twice.
  { idempotencyKey: "comment-created:c_981" },
);
if ("id" in sent) console.log(sent.id, sent.status, sent.skipped);

4

Read it the way your app will

Browsers and apps never hold your API key. Your server mints a recipient token for the signed-in user (one hour by default) and the client calls Notify with only that. Minting a token for an id Notify has not seen creates a bare recipient.

read.ts
// On your server: a short-lived token for the signed-in user.
const { token } = await notify.recipientTokens.mint({ recipient: "user_123" });

// In the browser or app: the token only, never your API key.
const me = notify.client({ token });
const page = await me.inbox.list({ status: "unread" });
for (const item of page.data) console.log(item.title, item.action_url);
const counts = await me.inbox.counts();
console.log("unread:", counts.unread);

In the console, Notify › Message log now shows the notification with one inbox delivery, delivered.


5

Next