PulsePigeonemail infrastructure

A Next.js route handler can initiate transactional email for password resets, verification, or order confirmation. Keep the send on the server boundary and treat the accepted message id as the correlation key for later delivery state.

Use a server-side environment variable for the API key. Never embed the API key in client code, use a NEXT_PUBLIC_ prefix for it, or return it to a browser. Add import 'server-only' to your send helper so a client import fails at build time. Next.js environment-variable guidance explains which values are bundled into client JavaScript.

Pass an idempotency key derived from the event that triggered the send. For a password reset, the key is the reset request id; for an order confirmation, the key is the order id. This way, a retry from any layer of your stack collapses to the same send.

Handle response codes by meaning. A successful send returns 202 Accepted. Fix validation, authentication, permission, and idempotency conflicts before retrying; respect 429 backpressure; and retry 5xx failures with bounded jitter. The accepted response includes an id you can use with message logs and events.

Use the helper below from a trusted server-side order workflow. Configure PULSEPIGEON_API_KEY, PULSEPIGEON_PROJECT_ID, and PULSEPIGEON_FROM_EMAIL privately. The key must be authorized for the configured project and the sender must meet the domain API reference. Derive the recipient and order ID from an authorized stored order, never an arbitrary unauthenticated request. See the developer API reference for the request shape and the SDK method reference for the client alternatives.

The example deliberately performs one HTTP attempt and sets a ten-second timeout. A durable worker owns retry count, backoff, jitter, and Retry-After handling. Persist the receipt event before dispatch and retain the same recipient, body, and key across attempts; changing a body under an existing key produces an idempotency conflict. Do not generate a new key just because an HTTP request timed out.

The published TypeScript SDK makes the same send with less code. Install version 1.0.0 with npm install @blitzglobaltech/pulsepigeon; the second example below uses it with the same idempotency key. The REST helper stays the dependency-free option.

202 Accepted means PulsePigeon accepted the request, not that the message reached the inbox. Save the returned id. Follow signed status webhook guidance before trusting delivery updates, and correlate the webhook payload's message_id with the accepted id. Consult the error reference and email API overview for the full response and retry contracts.

Server-only Next.js helper: lib/sendOrderReceipt.ts

import 'server-only';

type Receipt = { orderId: string; recipientEmail: string };

export async function sendOrderReceipt(receipt: Receipt): Promise<string> {
  const key = process.env.PULSEPIGEON_API_KEY;
  const project = process.env.PULSEPIGEON_PROJECT_ID;
  const sender = process.env.PULSEPIGEON_FROM_EMAIL;
  if (!key || !project || !sender) throw new Error('Email configuration missing');

  const response = await fetch('https://api.pulsepigeon.com/v1/messages', {
    method: 'POST',
    headers: {
      Authorization: 'Bearer ' + key,
      'Content-Type': 'application/json',
      'Idempotency-Key': 'receipt:' + receipt.orderId,
    },
    body: JSON.stringify({
      project_id: project,
      from_email: sender,
      to: [{ email: receipt.recipientEmail }],
      subject: 'Your order receipt',
      text: 'Order ' + receipt.orderId + ' is complete.',
      category: 'billing',
      metadata: { order_id: receipt.orderId },
    }),
    cache: 'no-store',
    signal: AbortSignal.timeout(10_000),
  });

  if (response.status !== 202) {
    throw Object.assign(new Error('Email request failed'), {
      status: response.status,
      retryAfter: response.headers.get('Retry-After'),
    });
  }
  const accepted = await response.json();
  if (!accepted || typeof accepted.id !== 'string' || !accepted.id) {
    throw new Error('Missing accepted message id');
  }
  return accepted.id;
}

Server-only Next.js helper with the TypeScript SDK: lib/sendOrderReceipt.ts

import 'server-only';
import { PulsePigeon } from '@blitzglobaltech/pulsepigeon';

type Receipt = { orderId: string; recipientEmail: string };

export async function sendOrderReceipt(receipt: Receipt): Promise<string> {
  const key = process.env.PULSEPIGEON_API_KEY;
  const project = process.env.PULSEPIGEON_PROJECT_ID;
  const sender = process.env.PULSEPIGEON_FROM_EMAIL;
  if (!key || !project || !sender) throw new Error('Email configuration missing');

  const pulsepigeon = new PulsePigeon(key);
  const accepted = await pulsepigeon.messages.send(
    {
      project_id: project,
      from_email: sender,
      to: [{ email: receipt.recipientEmail }],
      subject: 'Your order receipt',
      text: 'Order ' + receipt.orderId + ' is complete.',
      category: 'billing',
      metadata: { order_id: receipt.orderId },
    },
    { idempotencyKey: 'receipt:' + receipt.orderId },
  );
  return accepted.id;
}