Pushrail Docs
Open app
SDKs · Node

Node SDK, Ingestion

Single and batch event emission with @pushrail/sdk, plus error types and TypeScript shapes.

Ingestion

The Node SDK exposes ingestion on pushrail.events. Two methods cover the entire ingestion surface: emit for one event, emitBatch for up to 100. Both return typed results; both raise typed exceptions on failure.

Single events

import { Pushrail } from "@pushrail/sdk";

const pushrail = new Pushrail({ apiKey: process.env.PUSHRAIL_API_KEY! });

const result = await pushrail.events.emit({
  eventType: "subscription.renewed",
  source: "billing-service",
  customerExternalId: "cust_42",
  payload: { subscriptionId: "sub_abc", amountCents: 2900 },
  // Optional fields:
  // occurredAt: "2026-05-16T12:00:00Z",  // defaults to new Date().toISOString()
  // idempotencyKey: "billing:renew:sub_abc:202605",  // defaults to UUIDv7
  // correlationId: req.headers["x-correlation-id"],
  // metadata: { source_region: "us-east-1" },
});

The emit method is fully async. It resolves after Pushrail returns 202 Accepted or rejects on a non-retryable error. Retries on transient failures happen inside the SDK and are transparent to the caller.

Batch emission

const result = await pushrail.events.emitBatch([
  { eventType: "order.completed", source: "checkout", customerExternalId: "c1", payload: { ... } },
  { eventType: "order.completed", source: "checkout", customerExternalId: "c2", payload: { ... } },
]);

console.log(result.accepted, result.rejected);
for (const e of result.events) {
  if (e.status === "rejected") console.warn("Rejected:", e.index, e.error);
}

The batch endpoint accepts up to 100 events; the SDK does not split larger arrays for you. Slice your inputs into 100-event chunks on the caller side. See Batch ingest for partial-failure semantics.

Idempotency

Every emit call gets a UUIDv7 idempotencyKey if you don't supply one. The generated key is returned on the result object so you can log it. For deterministic dedup (across restarts, replays, redrives), supply your own key derived from a stable domain identifier:

await pushrail.events.emit({
  ...event,
  idempotencyKey: `${eventType}:${event.payload.orderId}`,
});

See Idempotency for the dedup-window semantics and key-design guidance.

Error types

The SDK exports typed exceptions, all subclassing PushrailError:

import {
  PushrailError,
  PushrailValidationError, // 400/422, permanent
  PushrailAuthError,       // 401/403, permanent
  PushrailRateLimitError,  // 429, transient, retried automatically
  PushrailServerError,     // 5xx, transient, retried automatically
  PushrailNetworkError,    // I/O fail, transient, retried automatically
} from "@pushrail/sdk";

try {
  await pushrail.events.emit(input);
} catch (err) {
  if (err instanceof PushrailValidationError) {
    // Fix the payload; do not retry.
    logger.warn({ details: err.responseBody }, "validation failed");
  } else if (err instanceof PushrailAuthError) {
    // Rotate or fix the key.
    throw err;
  } else if (err instanceof PushrailError) {
    // Generic catch, includes retried-and-exhausted transient failures.
    throw err;
  }
}

Each exception carries statusCode, responseBody, and requestId so you can correlate to a specific request in your logs and (if needed) in a Pushrail support conversation.

TypeScript types

The SDK exports the input and result shapes:

import type {
  EmitEventInput,
  EmitResult,
  BatchEmitResult,
  PushrailConfig,
} from "@pushrail/sdk";

EmitEventInput is the strongly-typed shape emit accepts. Compose it from your domain model and let TypeScript catch missing fields at the call site rather than at runtime.