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
- In the console, open Workspace › Services and enable Notify. Until it is on, the API answers
403 SERVICE_NOT_ENABLED. - 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.managefor this walkthrough. - Put it in your server’s environment as
PRODUCTCRAFT_API_KEY, with the workspace id asPRODUCTCRAFT_WORKSPACE_ID, andnpm install @productcraft/notify.
// 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.
// 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.
// 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.
// 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
- Show the inbox in your UI: React, Next.js or anything else.
- Add a channel: web push (no accounts needed), email, iOS or Android.
- Understand the model: core concepts.