Skip to main content
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.deliveryDelayed: 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://yourapp.com/api/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

Implement an HTTPS endpoint for receiving webhooks

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);

  // Handle different event types
  switch (event.type) {
    case 'email.delivered':
      console.log(`Email ${event.data.id} was delivered.`);
      break;
    case 'email.bounced':
      console.log(`Email ${event.data.id} bounced.`);
      break;
    case 'email.complained':
      console.log(`Email ${event.data.id} marked as spam.`);
      break;
    default:
      console.log(`Unhandled event type: ${event.type}`);
  }

  return NextResponse.json({ received: true });
}
In this example, we create an API route that listens for POST requests from Nuntly webhooks. It verifies the request signature using the stored signing secret to ensure authenticity. Depending on the event type received, it logs appropriate messages. You can expand the switch statement to handle additional event types as needed.