Skip to main content
Keystone is a demo project-management product. Its customers are companies, and its notifications reach their own employees across projects. This page shows you how to run it, what each screen does, and how you can build it using SuprSend.

Keystone

A B2B project workspace built on Next.js with the Node SDK, drop-in Inbox, and embedded template editor.
GitHub code repository
Stack: Next.js 16 (App Router) · React 19 · TypeScript · Tailwind v4 · shadcn/Radix UI. SuprSend SDKs: @suprsend/node-sdk, @suprsend/react, @suprsend/react-editor.

What the app does

Companies use Keystone to run their projects. Each project has people on it, each in a role, and what you can see and change depends on that role. Use Viewing as in the top bar to switch between people. The app ships with two customers, Acme and Globex. A switcher in the top bar changes which one you are working in.

How Keystone maps to SuprSend

One SuprSend account holds the whole product. Each customer is a workspace of its own, with its own keys, people, templates, workflows and delivery logs. Inside a workspace, each project is two records with the same ID: a tenant, which carries the project’s notification settings, and an object, which carries its team. A person is a user subscribed to that object with a role, so one person can hold a different role on each project. Notifying the object reaches everyone on the project, and each workflow’s conditions decide who actually receives it.
Keystone concepts mapped to SuprSend entities: customer to workspace, project to tenant and object, person to user
To go deeper on modelling your customers in SuprSend, and on the concepts this app uses, read Designing notifications for multi-tenant B2B applications.

App structure

The repository is organised like this:

Run it locally

Prerequisites: Node.js 20.9+ and a SuprSend account. Sign up free at app.suprsend.com.
1

Install

2

Create the workspaces

Get an account Service Token: Account Settings → Service Tokens. Copy the env template and paste the token into SUPRSEND_SERVICE_TOKEN.
This creates two workspaces in your account, Acme and Globex, and fills each Workspace Key in .env.local.
3

Paste the remaining keys

In the dashboard, switch to each workspace and open Developers → API Keys.<WS> is ACME or GLOBEX.
4

Use the ready-made notifications

The repo ships a working set of preference categories, email and inbox templates, and workflows, in suprsend/. Push them and both workspaces have something to send from the first minute. Preview first, then push:
To use your own, create them in the SuprSend dashboard and the app picks them up — it reads whatever is live in the workspace. The rest of this page uses the ready-made set.
5

Run

Open http://localhost:3000.

Add your starting data

Next, add the people and projects the app works with. Both are stored in SuprSend, and the app reads them from there.
1

Add people in SuprSend

People live in SuprSend. In the dashboard, open Acme → Users and add 3–4 people. Give each one a distinct ID such as u_dana_carter, an email, and a name property such as Dana Carter.The name property is what the app shows, so set it for everyone.
SuprSend dashboard user u_dana_carter with an email channel, an in-app inbox channel and a custom name property set to Dana Carter

A person in the SuprSend dashboard, with an email and a name property

A real employee would arrive from your own product’s signup. The dashboard stands in for that here.
2

Set the customer's brand

Open Administer → Brand. Set name, colours, logo and links. Templates read these through {{$brand.*}}, so emails carry the customer’s own name and colours. A new project copies the brand as it is created.The brand is stored on the workspace’s default tenant, which you can also see in the dashboard under Tenants.
SuprSend dashboard tenant Acme with id default, showing its logo, colours and an email layout preview

Acme's brand on its default tenant in the SuprSend dashboard

3

Create a project

Click + Project. Name it, pick a Project Manager (required) and at least one Team Member, then click Create project.This creates the tenant, the object and the role subscriptions in one request.
4

Check it works

  1. On the project page, change the state from In-flight to Paused.
  2. Open Viewing as and pick the Team Member. Open the bell: the notification is there.
  3. Switch Viewing as back to Admin and open Administer → Logs. The same send is listed with its status.
Email needs an email vendor in the dashboard (Vendors). The inbox works straight away.
To try every notification in turn, follow the test checklist in the repo.

App navigation

Top bar

Project screens

Projects list in Acme showing each project's state and manager

Administer screens

Admins see these screens.
Preferences with each notification's default, frequency, recipient roles and channels

Inbox

Inbox popover viewing as Ben Stark, with task, comment and project-started notifications

How each screen talks to SuprSend

The path every screen takes

SuprSend’s backend APIs authenticate with the workspace key and secret, which belong on the server. So every call runs there: open Resources, the browser asks the Keystone server for /api/admin/project-members, and that route calls SuprSend and returns the result. For you that means all SuprSend code lives in app/api and lib/server, and the screens call the app’s own routes. The exception is SuprSend’s browser components — the inbox and the template editor — which call SuprSend themselves using a short-lived token the server signs for them. Hub REST is SuprSend’s REST API on hub.suprsend.com. Keystone calls it directly for project and personal preferences.

Which API each screen uses

Full API lists and credential types: B2B guide → Reference: credentials and APIs.

How one trigger reaches the right people

Keystone names the project object as the recipient and passes the project as tenant_id. SuprSend works out who receives it:
SuprSend fans that out to everyone subscribed to the object, then each workflow’s conditions compare the recipient’s $recipient.subscription.role with the roles the category targets. That is how one trigger reaches only the Project Manager for a completed task, but the whole team for a started project. Change the roles on the Preferences screen and delivery follows.
SuprSend dashboard object acme-bms of type project, listing six subscribed users each with a role property such as Team Member or Project Manager

A project object in the SuprSend dashboard. Each member is subscribed with a role

Every notification also carries project_link, which points back at the project’s page in the app.

Two calls that use the REST API

  • Project and personal preferences: both come from the REST API, called directly from lib/server/. Project settings are signed with the workspace key and secret; a person’s own settings use a token signed for that person. Use the same approach when you need them.
  • Preference categories: a save sends the whole category tree. The app reads what is live, applies your one change to it, and sends all of it back, so every other category stays as it was.

How the inbox and editor get their token

Change or extend it

Add a customer workspace

Everything here is configuration.
  1. Create the workspace in the dashboard.
  2. Add its keys to .env.local under a new prefix: SUPRSEND_INITECH_WORKSPACE_KEY, NEXT_PUBLIC_SUPRSEND_INITECH_PUBLIC_KEY, and so on.
  3. Restart the dev server. The app reads the workspace list once per process.
  4. To give it notifications, run npm run push:assets -- <workspace slug>.

Add a role

A role lives in three places: Add a role choice to a category and it appears in the app’s recipient pickers.

Edit templates, workflows or categories

Edit the files in suprsend/, then push. Push to one workspace by naming it:
Pushing overwrites the live copy. Template edits made in the dashboard, and the defaults set in Administer → Preferences, go back to the repo files. Pull first if you want to keep them.
To pull workflows or categories, use workflow or category in the same command.

Add a notification

  1. Add its category, template and workflow to suprsend/, then push.
  2. Trigger it from an API route. lib/server/hub.ts wraps the three calls Keystone uses: triggerObjectWorkflow for a whole project, triggerWorkflow for one person, and trackEvent for event-triggered workflows.

Add a channel

Set up the channel’s vendor in SuprSend, then add that channel’s content to the template, both in the dashboard. Every template covers email and inbox, and two also carry Slack content.

Check your changes

Deploy

The app runs anywhere Next.js runs, for example Vercel. Add the same variables from .env.local to the deployment. Put a password on it. Set DEMO_PASSWORD and every page and API route asks for it (proxy.ts). A deployed demo reads and writes your real SuprSend account.

Reusing this pattern in your own product

Two routes sign the tokens that SuprSend’s browser components need. In Keystone, anyone can pick who they are with Viewing as, so both hand a token to whoever asks. Your product knows who is signed in, so tie both to that session before you ship them. The same applies to roles. Viewing as decides who you are, and the API routes trust the actor cookie the browser sets, so check the signed-in user’s role in every route under app/api/ before you ship.

Troubleshooting

A customer appears only when SUPRSEND_<WS>_WORKSPACE_KEY matches a workspace in your account and _WORKSPACE_SECRET is set. Fix the keys, then restart the dev server.
The message ends with the status code, usually 401. SUPRSEND_SERVICE_TOKEN is wrong or expired. Create a new one in Account Settings → Service Tokens.
Node.js is older than 20.9, which Next.js 16 requires. The scripts also read .env.local through --env-file. Upgrade Node.js.
“Missing signing-key env for …” — a signing key variable is empty.A 401 from the editor — you used the ws_signk_… ID. Use the signing_key_… UID.“public key not configured” — NEXT_PUBLIC_SUPRSEND_<WS>_PUBLIC_KEY is empty for this customer.No bell — the bell shows when you view as a project member with a project open.
Every project needs one. The picker lists the people in your workspace, so add them in the SuprSend dashboard first.
The user has no name property. Add it in the dashboard, or the app falls back to the distinct ID.
Triggers run after the save, so the action always completes. Open the SuprSend dashboard Logs to see the request and its status.
Their project role isn’t among the category’s recipient roles, they are in its excluded list, or they opted out. Check Administer → Preferences, then the project’s Project Settings.For example, Project Paused reaches Team Members and Project Sponsors by default, not the Project Manager.
Total cost has to be above the category’s threshold, which ships at 30000. Raise the cost, or lower the threshold in Preferences.
Known issue. tagged-on-project goes to one person, but its condition reads the recipient’s project role, which only exists when a notification is sent to the project object. There is no fix yet.
Add an email vendor in the dashboard (Vendors), and an email on the person. Open Administer → Logs for the failure reason.
Logs show the current project only, from the newest 1,000 messages in the workspace. Open the project first.
npm run push:assets overwrote them. Pull before you push — see Edit templates, workflows or categories.
DEMO_PASSWORD is set. Enter it on the gate page.

Reference

Environment variables

Scripts

Next steps

B2B modelling guide

Why each customer is its own workspace.

Workspaces vs tenants

When to use the other model.

React inbox

The inbox used in the top bar.

Embedded template editor

The editor used in Message Templates.