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.
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.
// 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.
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.
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 processedRetries 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.