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

@bernierllc/email-tracking

v1.0.10

Published

Email delivery tracking service that monitors email lifecycle from send to delivery/bounce/open/click

Downloads

296

Readme

@bernierllc/email-tracking

Email delivery tracking service that monitors email lifecycle from send to delivery/bounce/open/click. Links sent emails to webhook events from email-webhook-events package.

Installation

npm install @bernierllc/email-tracking

Features

  • Send Tracking - Record all email sends with tracking IDs
  • Event Linking - Link webhook events to sent emails by message ID
  • Status Management - Track delivery status with priority-based updates
  • Timeline Queries - Get complete event timeline for each email
  • Recipient History - Aggregate statistics per email address
  • Campaign Analytics - Calculate metrics for bulk email campaigns
  • NeverHub Integration - Event bus and service discovery support
  • Reputation Scoring - Track recipient engagement and reputation

Usage

Basic Setup

import { EmailTracking } from '@bernierllc/email-tracking';

const tracking = new EmailTracking({
  enableOpenTracking: true,
  enableClickTracking: true,
  eventRetentionDays: 90,
  aggregateStats: true
});

await tracking.initialize();

Record Email Send

const result = await tracking.recordEmailSent({
  messageId: 'msg_abc123',
  provider: 'sendgrid',
  from: '[email protected]',
  to: ['[email protected]'],
  subject: 'Welcome to our service',
  campaignId: 'welcome_campaign',
  metadata: { source: 'signup_flow' }
});

console.log('Tracking ID:', result.data?.id);
console.log('Status:', result.data?.currentStatus); // 'sent'

Process Webhook Events

// Typically called automatically via NeverHub subscription
const result = await tracking.processWebhookEvent({
  messageId: 'msg_abc123',
  eventType: 'delivered',
  timestamp: new Date().toISOString(),
  metadata: { smtpResponse: '250 OK' }
});

// Status automatically updated to 'delivered'

Get Email Timeline

const timeline = await tracking.getTimeline('track_xyz789');

if (timeline.success) {
  console.log('Current Status:', timeline.data.currentStatus);
  console.log('Events:');
  timeline.data.events.forEach(event => {
    console.log(`  ${event.eventType} at ${event.timestamp}`);
  });
}

// Output:
// Current Status: opened
// Events:
//   sent at 2025-01-01T10:00:00Z
//   delivered at 2025-01-01T10:01:00Z
//   opened at 2025-01-01T10:15:00Z

Get Recipient History

const history = await tracking.getRecipientHistory('[email protected]');

if (history.success) {
  console.log('Total Sent:', history.data.totalSent);
  console.log('Total Delivered:', history.data.totalDelivered);
  console.log('Total Opened:', history.data.totalOpened);
  console.log('Total Clicked:', history.data.totalClicked);
  console.log('Reputation Score:', history.data.reputationScore);
}

Get Campaign Statistics

const stats = await tracking.getCampaignStats('welcome_campaign');

if (stats.success) {
  console.log('Campaign:', stats.data.campaignId);
  console.log('Sent:', stats.data.totalSent);
  console.log('Delivery Rate:', stats.data.deliveryRate + '%');
  console.log('Open Rate:', stats.data.openRate + '%');
  console.log('Click Rate:', stats.data.clickRate + '%');
}

API Reference

EmailTracking

Main tracking service class.

Constructor

constructor(config?: TrackingConfig)

TrackingConfig Options:

  • enableOpenTracking (boolean) - Enable open tracking (default: true)
  • enableClickTracking (boolean) - Enable click tracking (default: true)
  • eventRetentionDays (number) - How long to keep events (default: 90)
  • aggregateStats (boolean) - Enable recipient history tracking (default: true)

Methods

initialize(): Promise<void>

Initialize the tracking service, set up NeverHub integration, and prepare database.

recordEmailSent(data: RecordEmailSentData): Promise<EmailTrackingResult<EmailSendRecord>>

Record an email send event with tracking metadata.

Parameters:

  • messageId (string) - Email provider message ID
  • provider (string) - Email provider (sendgrid, mailgun, ses, postmark)
  • from (string) - Sender email address
  • to (string[]) - Recipient email addresses
  • cc? (string[]) - CC recipients (optional)
  • bcc? (string[]) - BCC recipients (optional)
  • subject (string) - Email subject
  • campaignId? (string) - Campaign identifier (optional)
  • metadata? (Record<string, unknown>) - Custom tracking metadata (optional)

Returns: EmailTrackingResult<EmailSendRecord> with tracking ID and status

processWebhookEvent(event: WebhookEventData): Promise<EmailTrackingResult>

Process a normalized webhook event from email-webhook-events package.

Parameters:

  • messageId (string) - Email provider message ID
  • eventType (string) - Event type (delivered, bounce, open, click, spamreport, unsubscribe)
  • timestamp (string | Date) - Event timestamp
  • metadata? (Record<string, unknown>) - Event metadata (optional)

Returns: EmailTrackingResult indicating success/failure

getTimeline(trackingId: string): Promise<EmailTrackingResult<EmailTimeline>>

Get complete event timeline for an email.

Returns: EmailTimeline with send record, all events, current status, and last event timestamp

getRecipientHistory(emailAddress: string): Promise<EmailTrackingResult<RecipientHistory>>

Get aggregate statistics for a recipient email address.

Returns: RecipientHistory with totals, last event timestamps, and reputation score

getCampaignStats(campaignId: string): Promise<EmailTrackingResult<CampaignStatistics>>

Get aggregate statistics for a campaign.

Returns: CampaignStatistics with totals, rates, and percentages

Types

EmailDeliveryStatus

enum EmailDeliveryStatus {
  SENT = 'sent',
  DELIVERED = 'delivered',
  BOUNCED = 'bounced',
  OPENED = 'opened',
  CLICKED = 'clicked',
  COMPLAINED = 'complained',
  UNSUBSCRIBED = 'unsubscribed',
  FAILED = 'failed'
}

Status Priority (highest to lowest):

  1. COMPLAINED (spam report)
  2. UNSUBSCRIBED
  3. BOUNCED
  4. FAILED
  5. CLICKED
  6. OPENED
  7. DELIVERED
  8. SENT

Status can only move UP the priority chain, never down.

EmailSendRecord

interface EmailSendRecord {
  id: string;
  messageId: string;
  provider: EmailProvider;
  from: string;
  to: string[];
  cc?: string[];
  bcc?: string[];
  subject: string;
  campaignId?: string;
  metadata?: Record<string, unknown>;
  sentAt: Date;
  currentStatus: EmailDeliveryStatus;
  updatedAt: Date;
}

EmailTimeline

interface EmailTimeline {
  sendRecord: EmailSendRecord;
  events: EmailEvent[];
  currentStatus: EmailDeliveryStatus;
  lastEventAt: Date;
}

RecipientHistory

interface RecipientHistory {
  emailAddress: string;
  totalSent: number;
  totalDelivered: number;
  totalBounced: number;
  totalOpened: number;
  totalClicked: number;
  totalComplained: number;
  totalUnsubscribed: number;
  lastSentAt?: Date;
  lastDeliveredAt?: Date;
  lastBouncedAt?: Date;
  lastOpenedAt?: Date;
  lastClickedAt?: Date;
  reputationScore: number; // 0-100
}

Reputation Scoring:

  • Default: 100
  • Bounce: -10
  • Spam complaint: -25
  • Unsubscribe: -5

CampaignStatistics

interface CampaignStatistics {
  campaignId: string;
  totalSent: number;
  totalDelivered: number;
  totalBounced: number;
  totalOpened: number;
  totalClicked: number;
  totalComplained: number;
  totalUnsubscribed: number;
  deliveryRate: number; // percentage
  openRate: number; // percentage
  clickRate: number; // percentage
}

Configuration

Environment Variables

# Enable/disable tracking features
EMAIL_TRACKING_ENABLE_OPEN_TRACKING=true
EMAIL_TRACKING_ENABLE_CLICK_TRACKING=true

# Event retention policy
EMAIL_TRACKING_EVENT_RETENTION_DAYS=90

# Recipient history tracking
EMAIL_TRACKING_AGGREGATE_STATS=true

Configuration Precedence

  1. Constructor options (highest priority)
  2. Environment variables
  3. Default values (lowest priority)

Integration Status

  • Logger: integrated - Structured logging for tracking operations
  • Docs-Suite: ready - Complete TypeDoc API documentation
  • NeverHub: required - Event subscriptions and service discovery

NeverHub Integration

The service automatically detects and integrates with NeverHub when available:

Events Published:

  • email.status_changed - When email delivery status changes

Events Subscribed:

  • email.sent - From email-sender or email-service
  • email.webhook.* - From email-webhook-events

Capabilities:

  • Type: email
  • Name: delivery-tracking
  • Features: send-tracking, event-linking, timeline, recipient-history

Graceful Degradation

The service works without NeverHub but with reduced functionality:

  • No automatic event subscription
  • No status change events published
  • Manual recordEmailSent() and processWebhookEvent() calls required

Dependencies

Use Cases

Email Service Integration

import { EmailTracking } from '@bernierllc/email-tracking';
import { EmailSender } from '@bernierllc/email-sender';

const tracking = new EmailTracking();
const sender = new EmailSender();

await tracking.initialize();
await sender.initialize();

// Send email
const sendResult = await sender.send({
  to: '[email protected]',
  subject: 'Welcome',
  html: '<h1>Welcome!</h1>'
});

// Record tracking
if (sendResult.success) {
  await tracking.recordEmailSent({
    messageId: sendResult.data.messageId,
    provider: 'sendgrid',
    from: '[email protected]',
    to: ['[email protected]'],
    subject: 'Welcome',
    campaignId: 'onboarding'
  });
}

Webhook Processing

import express from 'express';
import { EmailTracking } from '@bernierllc/email-tracking';
import { normalizeWebhookEvent } from '@bernierllc/email-webhook-events';

const app = express();
const tracking = new EmailTracking();

await tracking.initialize();

app.post('/webhooks/sendgrid', async (req, res) => {
  const events = req.body;

  for (const event of events) {
    // Normalize webhook event
    const normalized = normalizeWebhookEvent('sendgrid', event);

    // Process with tracking
    await tracking.processWebhookEvent(normalized);
  }

  res.sendStatus(200);
});

Campaign Analytics Dashboard

async function getCampaignDashboard(campaignId: string) {
  const stats = await tracking.getCampaignStats(campaignId);

  if (!stats.success) {
    throw new Error(stats.error);
  }

  return {
    campaign: stats.data.campaignId,
    sent: stats.data.totalSent,
    delivered: stats.data.totalDelivered,
    bounced: stats.data.totalBounced,
    opened: stats.data.totalOpened,
    clicked: stats.data.totalClicked,
    metrics: {
      deliveryRate: stats.data.deliveryRate + '%',
      openRate: stats.data.openRate + '%',
      clickRate: stats.data.clickRate + '%'
    }
  };
}

Database Schema

The package uses an in-memory database for development. In production, implement the Database class with PostgreSQL or similar.

Tables:

  • email_sends - Send records with tracking IDs
  • email_events - Timeline events for each email
  • recipient_history - Aggregate statistics per recipient

See plan file for complete schema and indexes.

Testing

# Run tests in watch mode
npm test

# Run tests once
npm run test:run

# Run with coverage
npm run test:coverage

Test Coverage: 85%+ (service package requirement)

License

Copyright (c) 2025 Bernier LLC

This file is licensed to the client under a limited-use license. The client may use and modify this code only within the scope of the project it was delivered for. Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.

See Also