npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@zyphr-dev/node-sdk

v0.1.59

Published

Official Zyphr SDK for Node.js, React, and React Native

Readme

@zyphr-dev/node-sdk

Official Zyphr SDK for Node.js, React, and React Native. Type-safe access to multi-channel notifications, subscriber management, webhooks, and authentication.

npm version license

Installation

npm install @zyphr-dev/node-sdk
# or
yarn add @zyphr-dev/node-sdk

Requirements: Node.js 18+. Zero runtime dependencies.

Quick Start

import { Zyphr } from '@zyphr-dev/node-sdk';

const zyphr = new Zyphr({
  apiKey: process.env.ZYPHR_API_KEY!,
});

// Send an email
await zyphr.emails.sendEmail({
  to: [{ email: '[email protected]', name: 'Jane' }],
  subject: 'Welcome!',
  html: '<h1>Hello, {{name}}!</h1>',
  templateData: { name: 'Jane' },
});

// Send a push notification
await zyphr.push.sendPush({
  subscriberId: 'sub_123',
  payload: { title: 'New message', body: 'You have a new notification' },
});

// Send an SMS
await zyphr.sms.sendSms({
  to: '+15551234567',
  message: 'Your verification code is 123456',
});

// Send an in-app notification
await zyphr.inbox.sendInApp({
  subscriberId: 'sub_123',
  title: 'Welcome aboard',
  body: 'Thanks for signing up!',
});

Configuration

const zyphr = new Zyphr({
  apiKey: 'zy_live_xxxx',            // Required — your API key
  baseUrl: 'https://custom.api.url', // Optional — defaults to https://api.zyphr.dev/v1
});

| Option | Type | Default | Description | |--------|------|---------|-------------| | apiKey | string | — | Required. API key (zy_live_* or zy_test_*) | | baseUrl | string | https://api.zyphr.dev/v1 | API base URL override |

API Reference

Emails — zyphr.emails

Send transactional emails with template support, tracking, and scheduling.

// Send a single email
await zyphr.emails.sendEmail({
  to: [{ email: '[email protected]', name: 'Jane' }],
  from: { email: '[email protected]', name: 'My App' },
  subject: 'Order Confirmed',
  html: '<h1>Order #{{orderId}} confirmed</h1>',
  templateData: { orderId: '12345' },
  tag: 'order-confirmation',
  trackingEnabled: true,
});

// Send batch emails (up to 100)
await zyphr.emails.sendBatchEmail({
  messages: [
    { to: [{ email: '[email protected]' }], subject: 'Hello A', html: '...' },
    { to: [{ email: '[email protected]' }], subject: 'Hello B', html: '...' },
  ],
});

// Get email details and tracking
const email = await zyphr.emails.getEmail('email_id');
const events = await zyphr.emails.getEmailEvents('email_id');
const tracking = await zyphr.emails.getEmailTracking('email_id');

// List emails with filtering
const { data } = await zyphr.emails.listEmails('delivered', 'welcome', 20, 0);

Send options: to, from, replyTo, cc, bcc, subject, html, text, templateId, templateData, tag, metadata, headers, trackingEnabled, sendAt, delay.

SMS — zyphr.sms

Send SMS messages with scheduling and provider configuration.

// Send SMS
await zyphr.sms.sendSms({
  to: '+15551234567',
  message: 'Your code is 123456',
});

// Batch send (up to 100)
await zyphr.sms.sendBatchSms({
  messages: [
    { to: '+15551234567', message: 'Hello from Zyphr' },
    { to: '+15559876543', message: 'Hello from Zyphr' },
  ],
});

// Manage SMS provider configuration
const config = await zyphr.sms.getSmsConfig();
await zyphr.sms.upsertSmsConfig({
  provider: 'twilio',
  accountSid: 'AC...',
  authToken: '...',
  fromNumber: '+15550001234',
});
await zyphr.sms.verifySmsConfig();

// List and inspect
const list = await zyphr.sms.listSms(1, 20, 'delivered');
const detail = await zyphr.sms.getSms('sms_id');

Push Notifications — zyphr.push

Send push notifications to individual devices, subscribers, or topic subscribers.

// Send to a subscriber (all their devices)
await zyphr.push.sendPush({
  subscriberId: 'sub_123',
  payload: {
    title: 'New Order',
    body: 'Order #12345 has been placed',
    icon: 'https://cdn.example.com/icon.png',
    clickAction: 'https://app.example.com/orders/12345',
    customData: { orderId: '12345' },
  },
  priority: 'high',
  ttl: 3600,
});

// Send to a topic
await zyphr.push.sendPushToTopic('product-updates', {
  payload: { title: 'New Feature', body: 'Check out our latest update' },
});

// Topic management
await zyphr.push.subscribePushTopic('alerts', { deviceIds: ['device_1'] });
await zyphr.push.unsubscribePushTopic('alerts', { deviceIds: ['device_1'] });

// Stats and listing
const stats = await zyphr.push.getPushStats();
const pushes = await zyphr.push.listPush('sub_123');

In-App Inbox — zyphr.inbox

Manage in-app notifications for the @zyphr-dev/inbox-react component library.

// Send in-app notification
await zyphr.inbox.sendInApp({
  subscriberId: 'sub_123',
  title: 'New comment',
  body: 'Someone replied to your post',
  category: 'comments',
  cta: { label: 'View Comment', action: '/posts/456#comments' },
  icon: 'https://cdn.example.com/comment.svg',
});

// Batch send (up to 100)
await zyphr.inbox.sendBatchInApp({
  notifications: [
    { subscriberId: 'sub_123', title: 'Hello', body: '...' },
    { subscriberId: 'sub_456', title: 'Hello', body: '...' },
  ],
});

// Read operations
const unread = await zyphr.inbox.getUnreadCount('sub_123');
const notifications = await zyphr.inbox.listInbox('sub_123');

// Actions
await zyphr.inbox.markInboxRead('notification_id');
await zyphr.inbox.markAllInboxRead({ subscriberId: 'sub_123' });
await zyphr.inbox.archiveInboxNotification('notification_id');
await zyphr.inbox.deleteInboxNotification('notification_id');

Subscribers — zyphr.subscribers

Manage subscribers, their preferences, and consent records.

// Create/upsert subscriber
const subscriber = await zyphr.subscribers.createSubscriber({
  externalId: 'user_123',
  email: '[email protected]',
  name: 'Jane Doe',
  timezone: 'America/New_York',
  metadata: { plan: 'pro', company: 'Acme' },
});

// Get by ID or external ID
const sub = await zyphr.subscribers.getSubscriber('sub_id');
const subByExt = await zyphr.subscribers.getSubscriberByExternalId('user_123');

// Update
await zyphr.subscribers.updateSubscriber('sub_id', {
  name: 'Jane Smith',
  metadata: { plan: 'enterprise' },
});

// List with filtering
const subs = await zyphr.subscribers.listSubscribers('subscribed', undefined, 50, 0);

// Preferences
const prefs = await zyphr.subscribers.getSubscriberPreferences('sub_id');
await zyphr.subscribers.setSubscriberPreferences('sub_id', {
  preferences: [
    { categoryId: 'marketing', channel: 'email', enabled: false },
  ],
});

// Unsubscribe/resubscribe
await zyphr.subscribers.unsubscribeSubscriber('sub_id');
await zyphr.subscribers.resubscribeSubscriber('sub_id');

// Consent management
await zyphr.subscribers.recordSubscriberConsent('sub_id', {
  type: 'opt_in', source: 'signup_form',
});
const consent = await zyphr.subscribers.getSubscriberConsent('sub_id');
const history = await zyphr.subscribers.getSubscriberConsentHistory('sub_id');

// Categories
const category = await zyphr.subscribers.createCategory({
  name: 'Product Updates', slug: 'product-updates',
});

Templates — zyphr.templates

Create and manage reusable email templates with variable substitution.

// Create template
const template = await zyphr.templates.createTemplate({
  name: 'welcome-email',
  subject: 'Welcome, {{name}}!',
  html: '<h1>Hello {{name}}</h1><p>Welcome to {{company}}.</p>',
  variables: ['name', 'company'],
});

// Render template (preview without sending)
const rendered = await zyphr.templates.renderTemplate('template_id', {
  data: { name: 'Jane', company: 'Acme' },
});

// CRUD operations
const t = await zyphr.templates.getTemplate('template_id');
await zyphr.templates.updateTemplate('template_id', { subject: 'New subject' });
await zyphr.templates.deleteTemplate('template_id');
const all = await zyphr.templates.listTemplates(20, 0);

Webhooks — zyphr.webhooks

Configure webhook endpoints, monitor deliveries, and manage circuit breakers.

// Create webhook
const webhook = await zyphr.webhooks.createWebhook({
  url: 'https://api.example.com/webhooks',
  events: ['email.delivered', 'email.bounced', 'subscriber.created'],
  active: true,
});

// Delivery management
const deliveries = await zyphr.webhooks.listWebhookDeliveries(
  'webhook_id', 'failed', undefined, undefined, undefined, undefined, 20, 0
);
await zyphr.webhooks.retryWebhookDelivery('webhook_id', 'delivery_id');
await zyphr.webhooks.bulkRetryWebhookDeliveries('webhook_id', {
  status: 'failed', startDate: new Date('2026-01-01'),
});

// Replay events
await zyphr.webhooks.replayWebhookEvents('webhook_id', {
  startDate: new Date('2026-01-01'), endDate: new Date('2026-01-02'),
});

// Testing
await zyphr.webhooks.sendWebhookTestEvent('webhook_id', {
  eventType: 'email.delivered',
});

// Metrics and health
const metrics = await zyphr.webhooks.getWebhookMetrics('webhook_id');
const circuit = await zyphr.webhooks.getWebhookCircuitState('webhook_id');
await zyphr.webhooks.closeWebhookCircuit('webhook_id');

// Secret rotation
await zyphr.webhooks.rotateWebhookSecret('webhook_id');

// Metadata
const eventTypes = await zyphr.webhooks.listWebhookEventTypes();
const versions = await zyphr.webhooks.listWebhookVersions();
const ips = await zyphr.webhooks.getWebhookIps();

Topics — zyphr.topics

Group subscribers by interest for targeted notifications.

// Create topic
await zyphr.topics.createTopic({
  key: 'product-updates',
  name: 'Product Updates',
  description: 'New features and releases',
});

// Manage subscribers
await zyphr.topics.addTopicSubscribers('product-updates', {
  subscriberIds: ['sub_123', 'sub_456'],
});
await zyphr.topics.removeTopicSubscribers('product-updates', {
  subscriberIds: ['sub_789'],
});

// CRUD
const topic = await zyphr.topics.getTopic('product-updates');
await zyphr.topics.updateTopic('product-updates', { name: 'Product News' });
await zyphr.topics.deleteTopic('old-topic');
const topics = await zyphr.topics.listTopics(50, 0);

Devices — zyphr.devices

Register and manage push notification devices.

// Register device
const device = await zyphr.devices.registerDevice({
  userId: 'user_123',
  platform: 'ios',
  token: 'apns_token_xxx',
  metadata: { deviceName: 'iPhone 16 Pro' },
});

// List devices
const devices = await zyphr.devices.listDevices('user_123', 'ios', 20, 0);

// Stats
const stats = await zyphr.devices.getDeviceStats();

// Cleanup
await zyphr.devices.deleteDevice('device_id');
await zyphr.devices.deleteUserDevices('user_123');

Authentication — zyphr.auth.*

Full Auth-as-a-Service API for end-user authentication: registration, login, MFA, magic links, OAuth, and more.

Important: Auth endpoints use Application-scoped credentials (X-Application-Key + X-Application-Secret), which are distinct from the Project-scoped notification API key (zy_live_* / zy_test_*, sent as Authorization: Bearer). Create an application in the Zyphr dashboard first, then pass the credentials when constructing the client:

const zyphr = new Zyphr({
  apiKey: process.env.ZYPHR_API_KEY!,                   // Project-scoped: zy_live_* / zy_test_*
  applicationKey: process.env.ZYPHR_APP_PUBLIC_KEY!,    // Application-scoped public: za_live_pub_* / za_test_pub_*
  applicationSecret: process.env.ZYPHR_APP_SECRET_KEY!, // Application-scoped secret: za_live_sec_* / za_test_sec_*
});

Legacy za_pub_* / za_sec_* application-key prefixes are deprecated but still accepted.

Common mistake: POST /v1/auth/register creates a platform account (for the Zyphr dashboard). To register end users in your application, use POST /v1/auth/users/register via zyphr.auth.registration.registerEndUser().

// Register an end user
const result = await zyphr.auth.registration.registerEndUser({
  email: '[email protected]',
  password: 'SecureP@ss123',
  name: 'New User',
});
// result.data?.user   — the new user
// result.data?.tokens — { accessToken, refreshToken, expiresIn }

// Login
const session = await zyphr.auth.login.loginEndUser({
  email: '[email protected]',
  password: 'SecureP@ss123',
});

// User profile (requires end-user access token)
// Use zyphr.asEndUser(token) to authenticate as the end user
const login = await zyphr.auth.login.loginEndUser({
  email: '[email protected]',
  password: 'SecureP@ss123',
});
const token = login.data?.tokens?.accessToken;
if (!token) throw new Error('No access token returned');

const profile = await zyphr.auth.profile.getEndUser(zyphr.asEndUser(token));
await zyphr.auth.profile.updateEndUser(
  { name: 'Updated Name' },
  zyphr.asEndUser(token),
);

// Email verification (acts on the authenticated end user)
await zyphr.auth.emailVerification.sendEmailVerification(
  { redirectUrl: 'https://myapp.com/verified' },
  zyphr.asEndUser(token),
);

// Password reset
await zyphr.auth.passwordReset.forgotPassword({ email: '[email protected]' });

// Magic links
await zyphr.auth.magicLinks.sendMagicLink({
  email: '[email protected]',
  redirectUrl: 'https://myapp.com/auth/callback',
});

// MFA
const mfaStatus = await zyphr.auth.mfa.getMfaStatus('user_123');
await zyphr.auth.mfa.startMfaEnrollment({ userId: 'user_123' });

Available auth modules: login, registration, sessions, emailVerification, passwordReset, magicLinks, mfa, oauth, phone, webauthn, profile.

See the Auth-as-a-Service guide for the full setup walkthrough.

WaaS — zyphr.waas.*

Webhooks-as-a-Service: multi-tenant webhook delivery infrastructure for your customers.

// Create a WaaS application
const created = await zyphr.waas.applications.createWaaSApplication({
  name: 'My SaaS',
  slug: 'my-saas',
  description: 'Webhook delivery for My SaaS customers',
});
const appId = created.data?.id;
if (!appId) throw new Error('App id missing from response');

// Define event types
await zyphr.waas.eventTypes.createWaaSEventType(appId, {
  eventType: 'order.created',
  name: 'Order Created',
  description: 'Fired when a new order is placed',
  examplePayload: { orderId: '123', total: 99.99 },
});

// Publish events — fans out to all matching tenant endpoints
await zyphr.waas.events.publishWaaSEvent(appId, {
  eventType: 'order.created',
  tenantId: 'cust_abc123',
  data: { orderId: '456', total: 149.99 },
});

// Generate a tenant-scoped portal token for embedded UI
const portalToken = await zyphr.waas.portal.generateWaaSPortalToken(appId, {
  tenantId: 'cust_abc123',
});
// Pass portalToken.data?.token to @zyphr-dev/webhook-portal

Available WaaS modules: applications, eventTypes, endpoints, events, deliveries, portal.

Error Handling

The SDK provides typed error classes for different failure scenarios:

import {
  Zyphr,
  ZyphrError,
  ZyphrValidationError,
  ZyphrAuthenticationError,
  ZyphrRateLimitError,
  ZyphrNotFoundError,
  ZyphrPayloadTooLargeError,
} from '@zyphr-dev/node-sdk';

try {
  await zyphr.subscribers.getSubscriber('non_existent_id');
} catch (error) {
  if (error instanceof ZyphrNotFoundError) {
    console.log('Not found:', error.message);        // 404
  } else if (error instanceof ZyphrValidationError) {
    console.log('Invalid input:', error.details);     // 400/422
  } else if (error instanceof ZyphrAuthenticationError) {
    console.log('Check your API key');                // 401/403
  } else if (error instanceof ZyphrPayloadTooLargeError) {
    // 413 — see https://zyphr.dev/resources/api-limits
    console.log(`Body too large: sent ${error.receivedBytes}, max ${error.limitBytes}`);
  } else if (error instanceof ZyphrRateLimitError) {
    console.log('Retry after:', error.retryAfter);    // 429
  } else if (error instanceof ZyphrError) {
    console.log('API error:', error.status, error.code);
  }
}

Error Properties

All error classes extend ZyphrError, which extends Error:

| Property | Type | Description | |----------|------|-------------| | status | number | HTTP status code | | code | string \| undefined | Machine-readable error code | | requestId | string \| undefined | Request ID for support tickets | | details | Record<string, unknown> \| undefined | Validation errors and context | | retryAfter | number \| undefined | Seconds to wait (ZyphrRateLimitError only) | | limitBytes | number \| undefined | Server-enforced body cap (ZyphrPayloadTooLargeError only) | | receivedBytes | number \| undefined | Size of the rejected body (ZyphrPayloadTooLargeError only) |

Branch on error.code, not error.message

error.code carries the API's stable, machine-readable code — branch on it instead of matching message text (which is display copy and may change). The code is the exact value from the API response body (e.g. account_frozen, account_throttled, otp_invalid, email_not_found), not a generic per-status placeholder:

Behavior change (≥ 0.1.30): error.code now reflects the API's actual machine-readable code for every status. Previously the SDK overwrote it with a generic per-status placeholder (authentication_error, rate_limit_exceeded, validation_error, …). If you were matching those generic strings, switch to instanceof for the error class and use error.code for the specific condition. error.status and the error classes are unchanged.

try {
  await zyphr.emails.sendEmail({ /* ... */ });
} catch (error) {
  if (error instanceof ZyphrError) {
    switch (error.code) {
      case 'account_frozen':      // 403 — sending paused for reputation; see the dashboard
      case 'account_throttled':   // 429 — sending throttled; back off and retry
        // ...handle the account's sending state
        break;
      case 'otp_invalid':         // 400 — wrong/expired OTP
      case 'email_not_found':     // 404 — no account for this email (registration fallback)
        // ...
        break;
    }
  }
}

The full catalog of codes per endpoint is at zyphr.dev/docs/resources/error-codes. Reading error.code also flows through the Zyphr MCP server, so agents get the same actionable code.

Bundle

Dual-format build — works with both ESM and CommonJS:

  • ESM: dist/index.js
  • CommonJS: dist/index.cjs
  • Types: dist/index.d.ts

Zero runtime dependencies.

Related Packages

| Package | Description | |---------|-------------| | @zyphr-dev/inbox-react | Drop-in React notification inbox components | | @zyphr-dev/webhook-portal | Embeddable webhook management portal |

License

MIT