PulseKit documentation
One HTTP call, multi-channel delivery, retries, rate limiting, and a real-time audit trail. Everything below matches the live API and SDK contracts.
Installation
The SDK talks to the hosted API. A project API key is the only credential you need — it is shown exactly once when you create a project.
npm install pulsekit-sdk
Published as pulsekit-sdk (v0.1.1) — works with any Node 18+ runtime, no framework required.
Quickstart
Create a project, copy the API key, then send your first event. The call returns a receipt or — on transient failure — null, so your own retry policy is one branch.
import { PulseKit, PulseKitError } from 'pulsekit-sdk'
const pulse = new PulseKit({ apiKey: 'pk_test_...' })
const receipt = await pulse.notify({
event: 'payment.failed',
user: 'user_123',
data: { amount: 499, reason: 'card_declined' },
userName: 'Priya', // optional — email greeting
})
// → { eventId: '3f2c…', receivedAt: '2026-09-18T18:00:00.000Z' }
// → null on transient failure (5xx / 429 / network / timeout)A 4xx response throws PulseKitError — the caller made a mistake, fix the request. Anything transient (429, 5xx, network, timeout) returns null.
SDK reference
new PulseKit(options) takes an apiKey (required), an optional baseUrl (default the hosted API), and a 10s request timeout.
| Field | Type | Required | Sent as | Notes |
|---|---|---|---|---|
| event | string | yes | event_name | |
| user | string | yes | user_id | Never shown in emails |
| data | object | — | payload | Defaults to {} |
| to | string | — | to | Per-event email recipient override |
| userName | string | — | user_name | Email greeting; stored on the email delivery row, never on the event |
REST API
Ingest an event with POST /api/v1/events, authenticating with the project key via a Bearer header.
curl -X POST https://api.getpulsekit.cloud/api/v1/events \
-H "Authorization: Bearer pk_test_..." \
-H "Content-Type: application/json" \
-d '{
"event_name": "payment.failed",
"user_id": "user_123",
"payload": { "amount": 499, "reason": "card_declined" }
}'// 202 Accepted
{
"success": true,
"data": {
"eventId": "3f2c1a…",
"receivedAt": "2026-09-18T18:00:00.000Z"
}
}If the project has zero channels enabled, the event is still accepted — but data also carries warning: "no_channels_enabled" so the caller learns nothing will deliver.
| Code | Meaning |
|---|---|
| 202 | Event accepted and enqueued |
| 400 | event_name or user_id missing |
| 401 | Invalid or missing API key |
| 429 | Rate limit exceeded — Retry-After: 60 |
| 500 | Server error |
Rate limiting
Sliding-window rate limiting, enforced per project by an atomic Redis Lua script — not per IP.
| Setting | Value |
|---|---|
| Default | 30 requests / minute per project |
| Configurable | 5–30 req/min |
| When blocked | 429 with Retry-After: 60, consumes no quota |
Delivery channels
Channels are enabled per project in the dashboard. The worker only delivers to channels that are explicitly configured.
| Channel | Delivery mechanism | Notes |
|---|---|---|
| Resend | Branded HTML, humanised payload, optional userName greeting; resolved recipient stored per attempt | |
| Slack | Incoming webhook | Redirects are treated as failures — no false delivered logs |
| In-app | Row inserted into notifications | Unread count, mark-as-read and mark-all-read via PATCH |
A per-event to overrides the email recipient for that event only. Failures on one channel never re-deliver the others.
Retries & audit
Channel-level failures (Resend rejects, Slack 4xx) are logged once as failed, never retried. Catastrophic failures retry up to five times with exponential backoff and jitter, then move to a dead-letter queue with a sentinel failed row.
delivery_logs is append-only — failures and give-ups are recorded, never updated.
Delivery statuses: pending · delivered · failed · rate_limited · deduplicated (reserved)
Real-time feed
The WebSocket server shares the Express HTTP server on the same port wss://api.getpulsekit.cloud. The worker publishes a delivery_update message to Redis after every attempt; the dashboard's LiveFeed filters by project and reconnects with backoff.
Connecting directly from your own frontend is not part of the current API surface — the live feed is a dashboard feature.