Webhooks

Subscribe to real-time email delivery events. PostaSend sends signed HTTP POST requests to your endpoint for each event.

Creating a Webhook

Create a webhook endpoint in the PostaSend dashboard or via the API. Your endpoint must be publicly accessible and respond with a 200 OK within 5 seconds. PostaSend will retry failed deliveries up to 5 times with exponential backoff over 24 hours.

typescript
const webhook = await client.webhooks.create({
  url: 'https://yourapp.com/webhooks/postasend',
  events: [
    'email.delivered',
    'email.opened',
    'email.clicked',
    'email.bounced',
    'email.complained',
  ],
  // Optional: only receive events for specific tags
  // tagFilter: ['transactional'],
});

console.log(webhook.id);     // 'wh_abc123'
console.log(webhook.secret); // Store this securely!

Webhook Payload

Each webhook event has a consistent envelope structure with type, timestamp, and data fields. The data field varies by event type. All timestamps are ISO 8601 UTC strings.

typescript
// Example: email.delivered event payload
{
  "id": "evt_xyz789",
  "type": "email.delivered",
  "timestamp": "2025-06-15T10:23:45.123Z",
  "data": {
    "messageId": "msg_abc123",
    "to": "user@example.com",
    "subject": "Welcome!",
    "tags": ["welcome", "transactional"],
    "deliveredAt": "2025-06-15T10:23:44.987Z",
    "provider": "ses"
  }
}

Verifying Signatures

Every webhook request includes a Mailstack-Signature header containing an HMAC-SHA256 signature of the raw request body. Always verify this signature before processing the event to prevent spoofed requests.

typescript
import crypto from 'crypto';
import type { NextRequest } from 'next/server';

export async function POST(req: NextRequest) {
  const payload = await req.text();
  const signature = req.headers.get('Mailstack-Signature') ?? '';
  const secret = process.env.POSTASEND_WEBHOOK_SECRET!;

  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':
      await handleDelivered(event.data);
      break;
    case 'email.bounced':
      await handleBounce(event.data);
      break;
  }

  return new Response('OK', { status: 200 });
}

Event Types

PostaSend emits the following webhook events. You can subscribe to all events or a specific subset when creating your webhook endpoint.

typescript
type EmailEventType =
  | 'email.queued'      // Email accepted into queue
  | 'email.sending'     // Delivery attempt started
  | 'email.delivered'   // SMTP accepted by destination
  | 'email.opened'      // Recipient opened the email
  | 'email.clicked'     // Recipient clicked a tracked link
  | 'email.bounced'     // Hard or soft bounce received
  | 'email.complained'  // Spam complaint received
  | 'email.unsubscribed' // Unsubscribe request processed

Retries and Failure Handling

If your endpoint returns a non-2xx status or times out, PostaSend will retry the delivery using exponential backoff: 1 min, 5 min, 30 min, 2 hours, 24 hours. After 5 failures, the webhook is marked as failing and you'll receive an alert. You can replay failed events from the dashboard.