Before continuing, make sure you’ve completed the SDK setup and requirements.

Overview

This guide walks you through how to receive real‑time email events. Webhooks let your application react to email lifecycle events in real time:
  • email.sent: Accepted the email for delivery and started the sending process.
  • email.delivered: The email successfully reached the recipient’s mail server.
  • email.opened: The recipient opened the email (if open tracking is enabled).
  • email.clicked: The recipient clicked a link in the email (if click tracking is enabled).
  • email.bounced: The email was rejected by the recipient’s server (hard or soft bounce).
  • email.complained: The recipient marked the email as spam.
  • email.rejected: We refused to send the email (e.g., due to policy or configuration).
  • email.delivery_delayed: Delivery to the recipient is temporarily delayed
  • email.failed: Email could not be delivered due to an issue

Create a webhook

Use the TypeScript SDK to register a new webhook for your organization:
const webhook = await nuntly.webhooks.create({
  name: 'Production Listener',
  endpoint_url: 'https://api.yourapp.com/webhooks/nuntly',
  status: 'enabled',
  events: ['email.delivered', 'email.bounced', 'email.complained'],
});
console.log('Webhook ID:', webhook.id);
// webhook.signing_secret: store the webhook signing secret securely for later

Expose an HTTPS endpoint

For a Next.js 15 (App Router) project:
// app/api/webhooks/nuntly/route.ts
import type { NextRequest, NextResponse } from 'next/server';
import crypto from 'crypto';

export async function POST(req: NextRequest) {
  const payload = await req.text();
  const signature = req.headers.get('x-nuntly-signature') || '';
  const secret = process.env.NUNTLY_SIGNING_SECRET!;

  // Verify signature (HMAC SHA‑256)
  const expected = crypto.createHmac('sha256', secret).update(payload).digest('hex');

  if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
    return new Response('Invalid signature', { status: 401 });
  }

  const event = JSON.parse(payload);

  switch (event.type) {
    case 'email.delivered':
      // update your DB, trigger workflows, etc.
      break;
    case 'email.bounced':
      // handle bounce: do not use this email anymore
      break;
    case 'email.complained':
      // handle complained: do not use this email anymore
      break;
    // add more cases as needed
  }

  return new Response('OK');
}