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.