Realtime
Novu
Novu's server API, workflows run through your app's bridge or from seeded templates, the Inbox API and socket the browser uses, and emails sent through the Resend emulator.
danwo's Novu emulator answers Novu's API and the Inbox's socket, US and EU. Your server triggers
workflows with @novu/api, code-first workflows run through your app's bridge, and the browser's
@novu/react Inbox shows each message live.
Set up
Add novuService with your API key and, for code-first workflows, the bridge URL. To send the
workflows' emails somewhere a test can read them, give it a Resend service:
import { appEnvironment, defineConfig, NOVU_DEFAULTS, novuService, resendService } from "@danwo/sdk"
const url = "http://localhost:3000"
export default defineConfig({
testDir: "tests",
readiness: { mode: "page+backend", settle: true },
environment: () => {
const resend = resendService()
return appEnvironment({
app: {
command: "npm run dev",
url,
env: {
NOVU_SECRET_KEY: NOVU_DEFAULTS.apiKey,
NEXT_PUBLIC_NOVU_APPLICATION_IDENTIFIER: NOVU_DEFAULTS.applicationIdentifier,
},
},
services: [
resend,
novuService({
email: resend,
seed: {
bridgeUrl: `${url}/api/novu`,
subscribers: [{ subscriberId: "ada", email: "ada@example.com" }],
},
}),
],
})
},
})With no seed, the API key and application identifier are NOVU_DEFAULTS'.
Seed it
| Field | What it seeds |
|---|---|
apiKey, applicationIdentifier | The environment's keys. |
bridgeUrl | Your app's @novu/framework endpoint: workflows are discovered and run through it. |
hmac | Require the Inbox's subscriber hash, as with HMAC encryption turned on. |
subscribers | Subscribers with names, email, phone and data. |
workflows | Dashboard-style workflows, with in-app, email, SMS, push, chat, delay and digest steps. |
Use it in a test
Trigger a notification from the app, then check the Inbox showed it and the email went out:
import { type NovuFixtures, novuProject, type ResendFixtures, test as base } from "@danwo/sdk"
const test = base.extend<NovuFixtures & ResendFixtures>()
test("tells Ada her order shipped", async ({ app, screen, expect, settle, novu }) => {
await app.open("/orders/o_1")
await screen.getByRole("button", { name: "Mark shipped" }).click()
await settle()
await expect(screen.getByText("Order o_1 is on its way")).toBeVisible()
const message = await novuProject(novu).waitForMessage("ada", { channel: "in_app" })
expect(message.content).toBe("Order o_1 is on its way")
expect(novuProject(novu).messages({ channel: "email", subscriberId: "ada" })).toHaveLength(1)
})novuProject(novu) | What it's for |
|---|---|
subscribers(), executions() | Subscribers, and workflow runs. |
messages(filter), inbox(id) | Every step's message, and a subscriber's Inbox. |
waitForMessage(id) | Wait for a subscriber's next message. |
openInboxes() | The Inbox sockets open now. |
idle() | Wait until every run has finished; settle does too. |
What's covered
| Area | Covered |
|---|---|
| Events | Trigger, bulk, broadcast and cancel. |
| Subscribers | Subscribers, their credentials and notifications. |
| Topics | Topics and their subscriptions. |
| Workflows | Code-first through your bridge, or seeded; delays and digests run straight through. |
| Inbox | Sessions, notifications, counts, read and seen, archive, preferences, the socket. |
| Email steps sent through a Resend service, so they land in its inbox. |
Faults
| Call | Faults the matrix tries |
|---|---|
POST /v1/events/trigger | 429, 500, connection reset, answer lost after Novu started the run |
| A read | 500, connection reset |
| The Inbox socket | the upgrade refused, and the connection dropped for one person |
Webhooks
Novu sends your app no webhooks here. Its calls to your bridge are signed with
novu-signature, an HMAC of the timestamp and body under the API key, so @novu/framework
accepts them.
Fidelity
Live. danwo fidelity novu runs 2 scenarios against a real environment: an unknown key, and
creating, reading, patching and deleting a subscriber. Both match.
Not covered yet
- Delays and digests that wait: they run straight through
- SMS, push and chat providers: those steps are recorded, not sent anywhere
- Layouts, translations and the dashboard's workflow editor
Next
Pusher
Pusher Channels, the API your server triggers events on and the socket pusher-js keeps open, with private and presence channels, subscription counting, signed webhooks and Beams.
Tiptap
Tiptap Cloud collaboration, the socket editors sync documents over and the documents REST API, with a test that reads what a document says and makes the server misbehave.