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

@ketrics/notification-sdk

v0.3.0

Published

SDK for publishing notification events to EventBridge

Readme

Notification SDK

SDK for publishing notification events to AWS EventBridge within the Ketrics platform.

Overview

The Notification SDK provides a type-safe client for publishing notification events from Ketrics backend services (tenant-api, platform-api) to EventBridge. Events are consumed by the notification-lambda, which handles delivery across multiple channels (inbox, email, push).

Architecture Position

┌─────────────────────┐      ┌─────────────────┐      ┌─────────────────────┐
│     tenant-api      │──┐   │                 │      │                     │
└─────────────────────┘  │   │   EventBridge   │      │  notification-lambda│
                         ├──▶│  (event bus)    │─────▶│                     │
┌─────────────────────┐  │   │                 │      │  - inbox-service    │
│    platform-api     │──┘   └─────────────────┘      │  - email-service    │
└─────────────────────┘                               │  - push-service     │
        ▲                                             └─────────────────────┘
        │ uses
┌───────┴───────────┐
│  notification-sdk │
└───────────────────┘

Key Responsibilities

  • Construct properly formatted notification event payloads
  • Publish events to the configured EventBridge event bus
  • Generate unique notification IDs for idempotency
  • Abstract EventBridge API complexity from consuming services

Boundaries

This SDK only publishes events. It does not:

  • Deliver notifications (handled by notification-lambda)
  • Store notification state
  • Query notification history
  • Handle authentication/authorization (caller's responsibility)

Business Logic

Problem Solved

The platform needs to notify users about various events (ticket updates, announcements, direct messages) across multiple delivery channels. The SDK decouples notification publishing from delivery, enabling:

  • Async, non-blocking notification dispatch
  • Independent scaling of producers and consumers
  • Consistent event schema across all sources

Core Workflows

Ticket Notifications:

  1. Tenant admin creates ticket → notify platform admins
  2. Platform admin replies → notify ticket creator
  3. Status changes → notify relevant parties
  4. Assignment changes → notify assignee
  5. SLA breach detected → notify platform admins (high priority)

Announcements:

  1. Platform admin publishes announcement
  2. SDK sends one event per target tenant
  3. Lambda delivers to all users in each tenant

Direct Messages:

  1. Admin sends direct message to specific user
  2. SDK publishes single event targeting that user

Business Rules

  • Priority mapping: Actions auto-assign priority (created=MEDIUM, status_changed=LOW, sla_breach=HIGH)
  • Message truncation: Reply content is capped at 100 characters in notification body
  • Sender exclusion: Actors are automatically excluded from receiving their own notifications
  • Internal notes: Platform admin internal notes do not trigger notifications
  • Graceful degradation: Notification failures are logged but never block the triggering operation

Input/Output Expectations

Input: Structured params objects with tenant context, entity IDs, and action metadata Output: Events published to EventBridge with standardized NotificationEventDetail schema

Edge Cases Handled

  • Missing event bus configuration → operations silently skip notification
  • Empty recipient list for announcements → warning logged, no events sent
  • Optional fields (message, newStatus, assigneeName) → appropriate defaults applied

Technical Details

Technology Stack

  • Runtime: Node.js 24+
  • Language: TypeScript (ES2022 target)
  • AWS SDK: @aws-sdk/client-eventbridge ^3.696.0
  • Build: TypeScript compiler (tsc)
  • Testing: Jest with ts-jest

File Structure

notification-sdk/
├── src/
│   ├── index.ts        # Public API exports and usage documentation
│   ├── client.ts       # NotificationClient class implementation
│   └── types.ts        # TypeScript type definitions for all events
├── package.json        # Package metadata and scripts
├── tsconfig.json       # TypeScript compiler configuration
└── jest.config.js      # Test configuration

Key Classes and Functions

NotificationClient (src/client.ts)

Main entry point. Wraps EventBridgeClient and provides typed methods for each notification type.

const client = new NotificationClient({
  eventBusName: 'ketrics-notifications-dev',
  source: 'ketrics.tenant-api',  // or 'ketrics.platform'
  region: 'us-east-1',
});

Methods:

  • sendTicketNotification(params) - Ticket lifecycle events
  • sendAnnouncement(params) - Platform announcements
  • sendDirectMessage(params) - User-to-user messages
  • sendSlaBreachNotification(params) - SLA breach alerts
  • sendRawNotification(detailType, detail) - Advanced use cases

buildTicketNotificationContent() (src/client.ts:232-296)

Private method that maps ticket actions to notification content:

| Action | Priority | Type | Subject Template | |--------|----------|------|------------------| | created | MEDIUM | TICKET_CREATED | "New Support Ticket: {ticketRef}" | | replied | MEDIUM | TICKET_REPLIED | "Reply on {ticketRef}" | | status_changed | LOW | TICKET_STATUS_CHANGED | "Status Updated: {ticketRef}" | | assigned | MEDIUM | TICKET_ASSIGNED | "Ticket Assigned: {ticketRef}" | | sla_breach | HIGH | SLA_BREACH | "SLA Breach Warning: {ticketRef}" |

Type Definitions

Event Detail Types (src/types.ts)

interface NotificationEventDetail {
  notificationId: string;    // Unique ID for idempotency
  tenantId: string;          // Tenant context
  timestamp: string;         // ISO 8601
  recipients: NotificationRecipients;
  type: NotificationType;
  priority: NotificationPriority;
  subject: string;
  body: string;
  relatedEntity?: RelatedEntity;
  sender: NotificationSender;
  channels?: NotificationChannels;
}

Recipient Types:

  • USER - Specific user IDs
  • TENANT_ADMINS - All admins in a tenant
  • ALL_TENANT_USERS - Every user in a tenant
  • PLATFORM_ADMINS - Platform-level administrators

Detail Types (EventBridge routing):

  • notification.ticket - Support ticket events
  • notification.announcement - Platform announcements
  • notification.direct-message - Direct messages

Configuration

| Option | Type | Required | Description | |--------|------|----------|-------------| | eventBusName | string | Yes | EventBridge event bus name | | source | EventSource | Yes | ketrics.tenant-api or ketrics.platform | | region | string | No | AWS region (uses SDK default if omitted) |

Environment Variables

The SDK itself has no environment variables. Configuration is passed via constructor. Consuming services typically use:

NOTIFICATION_EVENT_BUS_NAME=ketrics-notifications-dev
AWS_REGION=us-east-1

External Integrations

  • AWS EventBridge: Single integration point. Uses PutEventsCommand to publish events.

Data Flow

Inbound

Data enters via typed method parameters from tenant-api or platform-api:

await client.sendTicketNotification({
  tenantId: 'tenant-uuid',
  ticketId: 'ticket-uuid',
  ticketRef: 'TKT-0001',
  action: 'replied',
  actorId: 'user-uuid',
  actorName: 'John Doe',
  actorType: 'PLATFORM_ADMIN',
  message: 'Here is my response...',
  recipientUserIds: ['target-user-uuid'],
  excludeUserIds: ['user-uuid'],
});

Transformations

  1. Action mapping: buildTicketNotificationContent() converts action to priority/type/subject/body
  2. ID generation: notificationId created from entity ID + action + timestamp
  3. Recipient resolution: recipientUserIds presence determines USER vs TENANT_ADMINS targeting
  4. Serialization: Detail object serialized to JSON for EventBridge

Outbound

EventBridge event structure:

{
  "EventBusName": "ketrics-notifications-dev",
  "Source": "ketrics.tenant-api",
  "DetailType": "notification.ticket",
  "Detail": "{\"notificationId\":\"ticket-123-replied-1706123456789\",...}"
}

Announcement Fan-out

For announcements targeting multiple tenants, the SDK sends one event per tenant:

// Internally loops and sends:
for (const tenantId of params.targetTenantIds) {
  await this.putEvent({
    detailType: 'notification.announcement',
    detail: { ...detail, tenantId, notificationId: `${baseId}-${tenantId}` }
  });
}

Error Handling

Error Scenarios

| Scenario | Behavior | |----------|----------| | EventBridge API error | Exception propagates to caller | | Network timeout | Exception propagates to caller | | Invalid parameters | TypeScript compile-time errors | | Missing event bus config | Caller should check config and not instantiate client |

Retry Logic

The SDK does not implement retries. Calling services implement their own retry strategies. Currently, both tenant-api and platform-api wrap calls in try/catch and log failures without retrying--notifications are considered best-effort.

Example from platform-api/src/services/ticket-notification-service.ts:85-91:

} catch (error) {
  logger.error("Failed to send ticket reply notification", {
    error: error instanceof Error ? error.message : "Unknown error",
    ticketId: params.ticketId,
  });
}

Logging Approach

The SDK itself does not log. Calling services are responsible for logging:

  • Success: Info-level logs with entity IDs
  • Failure: Error-level logs with error details and context

Usage

Installation

npm install @ketrics/notification-sdk

Or as a local dependency in the monorepo:

{
  "dependencies": {
    "@ketrics/notification-sdk": "file:../notification-sdk"
  }
}

Basic Usage

import { NotificationClient } from '@ketrics/notification-sdk';

const client = new NotificationClient({
  eventBusName: 'ketrics-notifications-dev',
  source: 'ketrics.tenant-api',
  region: 'us-east-1',
});

// Ticket created notification
await client.sendTicketNotification({
  tenantId: 'tenant-uuid',
  ticketId: 'ticket-uuid',
  ticketRef: 'TKT-0001',
  action: 'created',
  actorId: 'user-uuid',
  actorName: 'John Doe',
  actorType: 'TENANT_ADMIN',
  message: 'I need help with...',
});

// Platform announcement
await client.sendAnnouncement({
  title: 'System Maintenance',
  body: 'Scheduled maintenance on Saturday 2-4 AM UTC.',
  priority: 'MEDIUM',
  targetTenantIds: ['tenant-1', 'tenant-2'],  // or omit for all
  senderId: 'admin-uuid',
  senderName: 'Platform Team',
});

// Direct message
await client.sendDirectMessage({
  tenantId: 'tenant-uuid',
  recipientUserId: 'user-uuid',
  subject: 'Account Update',
  body: 'Your account has been upgraded.',
  senderId: 'admin-uuid',
  senderName: 'Support Team',
  senderType: 'PLATFORM_ADMIN',
});

// SLA breach (high priority, all channels)
await client.sendSlaBreachNotification({
  tenantId: 'tenant-uuid',
  ticketId: 'ticket-uuid',
  ticketRef: 'TKT-0001',
  ticketSubject: 'Urgent: System Down',
  hoursOverdue: 4.5,
});

Singleton Pattern (Recommended)

Both tenant-api and platform-api use a singleton pattern with lazy initialization:

let notificationClient: NotificationClient | null = null;

function getNotificationClient(): NotificationClient | null {
  if (!config.notifications?.eventBusName) {
    return null;  // Notifications disabled
  }
  if (!notificationClient) {
    notificationClient = new NotificationClient({
      eventBusName: config.notifications.eventBusName,
      source: 'ketrics.tenant-api',
      region: config.aws.region,
    });
  }
  return notificationClient;
}

Development Commands

npm install          # Install dependencies
npm run build        # Compile TypeScript to dist/
npm run test         # Run tests
npm run test:watch   # Run tests in watch mode
npm run test:coverage # Run tests with coverage
npm run lint         # Run ESLint
npm run lint:fix     # Auto-fix lint issues
npm run typecheck    # Type-check without emitting

Testing

Tests should mock the EventBridgeClient:

import { NotificationClient } from '@ketrics/notification-sdk';
import { EventBridgeClient } from '@aws-sdk/client-eventbridge';

jest.mock('@aws-sdk/client-eventbridge');

const mockSend = jest.fn().mockResolvedValue({});
(EventBridgeClient as jest.Mock).mockImplementation(() => ({
  send: mockSend,
}));

describe('NotificationClient', () => {
  it('sends ticket notification to EventBridge', async () => {
    const client = new NotificationClient({
      eventBusName: 'test-bus',
      source: 'ketrics.tenant-api',
    });

    await client.sendTicketNotification({
      tenantId: 'tenant-1',
      ticketId: 'ticket-1',
      ticketRef: 'TKT-0001',
      action: 'created',
      actorId: 'user-1',
      actorName: 'Test User',
      actorType: 'TENANT_ADMIN',
    });

    expect(mockSend).toHaveBeenCalledWith(
      expect.objectContaining({
        input: expect.objectContaining({
          Entries: expect.arrayContaining([
            expect.objectContaining({
              EventBusName: 'test-bus',
              Source: 'ketrics.tenant-api',
              DetailType: 'notification.ticket',
            }),
          ]),
        }),
      })
    );
  });
});