PulsePigeonemail infrastructure

PulsePigeon's published TypeScript SDK covers the curated REST surface, including message sends, events, logs, domains, webhooks, suppressions, contacts, and lists. Install version 1.0.0 from npm with npm install @blitzglobaltech/pulsepigeon for the project-key surface.

Initialize the client once at process start with your API key and reuse it across requests. The client is designed to be long-lived; it does not hold per-request state.

Pass an idempotency key on every send. Derive it from a stable persisted business-event identifier, such as an order ID, reset-request ID, notification ID, or another logical send ID reused across retries. A UUID minted per inbound request can change when a client or upstream retry re-enters the route, which can enqueue a duplicate.

Branch on the documented error classes. Validation, authentication, permission, not-found, and conflict errors require a caller or state correction. PulsePigeonRateLimitError exposes retryAfterSeconds, while PulsePigeonServerError identifies a retryable 5xx response. Your queue owns the attempt limit and schedule. Use the HTTP API error reference and SDK method reference to check those interfaces.

Configure a status-webhook subscription once. Subscriptions are persistent and filter by event type, project, or recipient domain, not by a single send. Correlate each webhook payload's message_id with the id on the accepted send response so support can tell whether that email was delivered, bounced, or complained without polling the API.

Queue worker send with the TypeScript SDK: sendNotification.ts

import {
  PulsePigeon,
  PulsePigeonRateLimitError,
  PulsePigeonServerError,
} from '@blitzglobaltech/pulsepigeon';

// notificationId is the persisted business id, set when the notification was created.
// Do not use the queue's own job id: it can change when the same notification is re-enqueued.
type Job = { notificationId: string; projectId: string; recipientEmail: string };

class RetryLater extends Error {
  constructor(readonly afterSeconds: number | null) {
    super('Retry later');
  }
}

function requireEnv(name: string): string {
  const value = process.env[name];
  if (!value) throw new Error(name + ' is not set');
  return value;
}

const pulsepigeon = new PulsePigeon(requireEnv('PULSEPIGEON_API_KEY'));

export async function sendNotification(job: Job): Promise<string> {
  try {
    const accepted = await pulsepigeon.messages.send(
      {
        project_id: job.projectId,
        from_email: requireEnv('PULSEPIGEON_FROM_EMAIL'),
        to: [{ email: job.recipientEmail }],
        subject: 'Your export is ready',
        text: 'Your export is ready to download.',
        category: 'transactional',
      },
      { idempotencyKey: 'notification:' + job.notificationId },
    );
    return accepted.id;
  } catch (error) {
    // Throw retryable failures so the queue's attempt limit and backoff run;
    // a normal return marks the job complete and the notification is dropped.
    if (error instanceof PulsePigeonRateLimitError) {
      throw new RetryLater(error.retryAfterSeconds);
    }
    if (error instanceof PulsePigeonServerError) {
      throw new RetryLater(null);
    }
    throw error;
  }
}