JavaScript/TypeScript SDK for Bodhveda
Official JavaScript/TypeScript SDK for Bodhveda.
It offers a simpler way to work with Bodhveda APIs in both browser and server environments.
Index
Installation
npm install @bodhveda/js
Previously published as
bodhveda. That package is deprecated — install@bodhveda/jsinstead.
Quick Start
import { Bodhveda } from "@bodhveda/js";
const bodhveda = new Bodhveda("YOUR_API_KEY");
// Send a notification to a recipient.
// Note: Bodhveda will create the recipient if it does not already exist.
await bodhveda.notifications.send({
recipient_id: "user-123",
payload: { message: "Hello, world!" },
});
// List all notifications for a recipient.
const notifications = await bodhveda.recipients.notifications.list("user-123");
Notifications
Send a notification
Send a notification to a recipient or broadcast to a target.
await bodhveda.notifications.send({
recipient_id: "user-123",
payload: { message: "Hello, world!" },
});
Send with email
Include the optional email block to also send an email. Its presence makes email
eligible (direct sends only — an email block on a broadcast returns 400).
Bodhveda does no templating: you render the subject/HTML/text yourself (e.g. with
@react-email) and pass the result. text is optional and derived from html.
Email fires only when the (target, email) pair is cataloged, the recipient's email
preference is enabled, and the recipient has a primary email
contact.
const res = await bodhveda.notifications.send({
recipient_id: "user-123",
target: { channel: "digest", topic: "none", event: "sent" },
payload: { title: "Your daily digest is ready." },
email: {
subject: "Your daily digest",
html: "<h1>Your daily digest</h1><p>3 new follow-ups today.</p>",
},
});
// res.notification.id — the send is accepted (status "enqueued"); the email is
// resolved asynchronously. Read the outcome back with notifications.get() below.
Get a notification (check the outcome)
The send is asynchronous: it accepts the notification and returns its id
(status: "enqueued"), then the worker resolves in-app delivery and the email.
Fetch the notification by id to see the resolved in-app status and, when the send
included an email block, the email delivery outcome on notification.email.
const notification = await bodhveda.notifications.get(res.notification!.id);
notification.status; // "delivered" | "muted" | "quota_exceeded" | "failed" | "enqueued"
notification.email?.status; // "pending" | "sent" | "delivered" | "bounced" | ...
notification.email?.delivered_at;
Recipients
Create a recipient
Create a new recipient.
await bodhveda.recipients.create({
id: "user-123",
name: "Alice",
});
Create multiple recipients (batch)
Create multiple recipients in a single request.
await bodhveda.recipients.createBatch({
recipients: [
{ id: "user-1", name: "Alice" },
{ id: "user-2", name: "Bob" },
],
});
Get a recipient
Retrieve details of a recipient by ID.
const recipient = await bodhveda.recipients.get("user-123");
Update a recipient
Update recipient details.
await bodhveda.recipients.update("user-123", { name: "Alice Updated" });
Delete a recipient
Delete a recipient by ID.
await bodhveda.recipients.delete("user-123");
Recipient Notifications
List notifications
List notifications for a recipient.
const notifications = await bodhveda.recipients.notifications.list("user-123");
Get unread notification count
Get the count of unread notifications for a recipient.
const { unread_count } = await bodhveda.recipients.notifications.unreadCount(
"user-123"
);
Update notification state
Update the state (e.g., mark as read) of notifications for a recipient.
await bodhveda.recipients.notifications.updateState("user-123", {
ids: [1, 2, 3],
state: { read: true },
});
Delete notifications
Delete notifications for a recipient.
await bodhveda.recipients.notifications.delete("user-123", {
ids: [1, 2, 3],
});
Recipient Preferences
List preferences
List all preferences for a recipient.
const preferences = await bodhveda.recipients.preferences.list("user-123");
Set a preference
Set a notification preference for a recipient. Pass an optional medium
("in_app" or "email") to toggle in-app and email independently for the same
target. It defaults to "in_app" when omitted.
await bodhveda.recipients.preferences.set("user-123", {
target: { channel: "digest", topic: "none", event: "sent" },
medium: "email",
state: { enabled: true },
});
Check a preference
Check the state of a specific preference for a recipient.
const result = await bodhveda.recipients.preferences.check("user-123", {
target: { channel: "digest", topic: "none", event: "sent" },
medium: "email",
});
Recipient Contacts
Contacts are per-medium addresses for a recipient. To send email to a recipient,
add an email contact and mark it primary. Sync this server-side (e.g. on your
/me endpoint) so the address never rides a browser request.
create, list, and update work with a Full access or Recipient access API
key; delete requires Full access.
Add a contact
await bodhveda.recipients.contacts.create("user-123", {
medium: "email",
address: "alice@example.com",
is_primary: true,
});
create is strict — it rejects with a 409 when the contact already exists. To
keep a recipient's primary address current from a server without a
list-then-diff, use setPrimary instead.
Set the primary contact
Idempotently ensure an address is the recipient's primary contact for a
medium — create it if absent, update the existing primary in place if the
address differs (which resets verification), or no-op if it already matches.
Returns the resulting primary contact (200) in every case, so a "keep the
primary email current" sync is a single call.
await bodhveda.recipients.contacts.setPrimary("user-123", {
medium: "email",
address: "alice@example.com",
});
List contacts
const { contacts } = await bodhveda.recipients.contacts.list("user-123");
Update a contact
await bodhveda.recipients.contacts.update("user-123", 1, {
address: "alice.new@example.com",
});
Delete a contact
Requires a Full access API key.
await bodhveda.recipients.contacts.delete("user-123", 1);
Project Preferences
The project preference catalog declares which (target, medium) pairs your
project may send, and the default a recipient inherits until they set a
preference of their own. This is different from
Recipient Preferences, which are a single recipient's
own toggles — manage the catalog with bodhveda.preferences.
Requires a Full access API key, and is a server-side concern. The project
is taken from the API key.
List the catalog
const catalog = await bodhveda.preferences.list();
Create a catalog entry
Strict — rejects with a 409 when an entry for the same
(channel, topic, event, medium) already exists. medium defaults to in_app
when omitted.
const entry = await bodhveda.preferences.create({
channel: "digest",
topic: "none",
event: "sent",
medium: "email",
name: "Daily digest",
description: "Receive a daily summary of activity.",
default_enabled: true,
});
Get / update / delete an entry
The natural key (channel/topic/event/medium) is immutable — update
changes only the name, description and default.
const entry = await bodhveda.preferences.get(123);
await bodhveda.preferences.update(123, {
name: "Weekly digest",
description: "Receive a weekly summary of activity.",
default_enabled: false,
});
// Un-catalogs the (target, medium).
await bodhveda.preferences.delete(123);
Set up a whole catalog at once
upsertMany declaratively merges an entire catalog in one call — the primitive
for a one-off "set up my project's preferences" script. Each item is upserted by
its natural key (new inserted, existing name + description + default updated).
Entries not in the array are left untouched.
await bodhveda.preferences.upsertMany([
{ channel: "digest", topic: "none", event: "sent", medium: "email", name: "Daily digest", default_enabled: true },
{ channel: "posts", topic: "any", event: "new_comment", medium: "email", name: "Comments", default_enabled: true },
]);
Pass { prune: true } to also delete entries absent from the array, making
it the entire desired catalog. Pruning un-catalogs a (target, medium), which
turns a non-in_app medium off for recipients relying on the catalog default —
so it is opt-in.
await bodhveda.preferences.upsertMany(desiredCatalog, { prune: true });
License
MIT