Postmark Webhooks
When to Use This Skill
- Setting up Postmark webhook handlers for email event tracking
- Processing email delivery events (bounce, delivered, open, click)
- Handling spam complaints and subscription changes
- Implementing email engagement analytics
- Troubleshooting webhook authentication issues
Essential Code
Authentication
Postmark does NOT use signature verification. Instead, webhooks are authenticated by including credentials in the webhook URL itself.
// Express - Basic Auth in URL
// Configure webhook URL in Postmark as:
// https://username:password@yourdomain.com/webhooks/postmark
app.post('/webhooks/postmark', express.json(), (req, res) => {
// Basic auth is handled by your web server or proxy
// Additional validation can check expected payload structure
const event = req.body;
// Validate expected fields exist
if (!event.RecordType || !event.MessageID) {
return res.status(400).send('Invalid payload structure');
}
// Process event
console.log(`Received ${event.RecordType} event for ${event.Email}`);
res.sendStatus(200);
});
// Alternative: Token in URL
// Configure webhook URL as:
// https://yourdomain.com/webhooks/postmark?token=your-secret-token
app.post('/webhooks/postmark', express.json(), (req, res) => {
const token = req.query.token;
if (token !== process.env.POSTMARK_WEBHOOK_TOKEN) {
return res.status(401).send('Unauthorized');
}
const event = req.body;
console.log(`Received ${event.RecordType} event`);
res.sendStatus(200);
});
Handling Multiple Events
// Postmark sends one event per request (not batched)
app.post('/webhooks/postmark', express.json(), (req, res) => {
const event = req.body;
switch (event.RecordType) {
case 'Bounce':
console.log(`Bounce: ${event.Email} - ${event.Type} - ${event.Description}`);
// Update contact as undeliverable
break;
case 'SpamComplaint':
console.log(`Spam complaint: ${event.Email}`);
// Remove from mailing list
break;
case 'Open':
console.log(`Email opened: ${event.Email} at ${event.ReceivedAt}`);
// Track engagement
break;
case 'Click':
console.log(`Link clicked: ${event.Email} - ${event.OriginalLink}`);
// Track click-through rate
break;
case 'Delivery':
console.log(`Delivered: ${event.Email} at ${event.DeliveredAt}`);
// Confirm delivery
break;
case 'SubscriptionChange':
console.log(`Subscription change: ${event.Email} - ${event.ChangedAt}`);
// Update subscription preferences
break;
case 'Inbound':
console.log(`Inbound email from: ${event.Email} - Subject: ${event.Subject}`);
// Process incoming email
break;
case 'SMTP API Error':
console.log(`SMTP API error: ${event.Email} - ${event.Error}`);
// Handle API error, maybe retry
break;
default:
console.log(`Unknown event type: ${event.RecordType}`);
}
res.sendStatus(200);
});
Common Event Types
| Event |
RecordType |
Description |
Key Fields |
| Bounce |
Bounce |
Hard/soft bounce or blocked email |
Email, Type, TypeCode, Description |
| Spam Complaint |
SpamComplaint |
Recipient marked as spam |
Email, BouncedAt |
| Open |
Open |
Email opened (requires open tracking) |
Email, ReceivedAt, Platform, UserAgent |
| Click |
Click |
Link clicked (requires click tracking) |
Email, ClickedAt, OriginalLink |
| Delivery |
Delivery |
Successfully delivered |
Email, DeliveredAt, Details |
| Subscription Change |
SubscriptionChange |
Unsubscribe/resubscribe |
Email, ChangedAt, SuppressionReason |
| Inbound |
Inbound |
Incoming email received |
Email, FromFull, Subject, TextBody, HtmlBody |
| SMTP API Error |
SMTP API Error |
SMTP API call failed |
Email, Error, ErrorCode, MessageID |
Environment Variables
# For token-based authentication
POSTMARK_WEBHOOK_TOKEN="your-secret-token-here"
# For basic auth (if not using URL-embedded credentials)
WEBHOOK_USERNAME="your-username"
WEBHOOK_PASSWORD="your-password"
Security Best Practices
- Always use HTTPS - Never configure webhooks with HTTP URLs
- Use strong credentials - Generate long, random tokens or passwords
- Validate payload structure - Check for expected fields before processing
- Implement IP allowlisting - Postmark publishes their IP ranges
- Consider using a webhook gateway - Like Hookdeck for additional security layers
Local Development
For local webhook testing, use Hookdeck CLI:
npx hookdeck-cli listen 3000 postmark --path /webhooks/postmark
No account required. Provides local tunnel + web UI for inspecting requests.
Resources
- overview.md - What Postmark webhooks are, common event types
- setup.md - Configure webhooks in Postmark dashboard
- verification.md - Authentication methods and security best practices
- examples/ - Complete implementations for Express, Next.js, and FastAPI
Recommended: webhook-handler-patterns
For production-ready webhook handling, also install the webhook-handler-patterns skill:
Related Skills
1---
2name: postmark-webhooks
3description: >
4 Receive and process Postmark webhooks. Use when setting up Postmark webhook
5 handlers, handling email delivery events, processing bounces, opens, clicks,
6 spam complaints, or subscription changes.
7license: MIT
8metadata:
9 author: hookdeck
10 version: "0.1.0"
11 repository: https://github.com/hookdeck/webhook-skills
12---
13
14# Postmark Webhooks
15
16## When to Use This Skill
17
18- Setting up Postmark webhook handlers for email event tracking
19- Processing email delivery events (bounce, delivered, open, click)
20- Handling spam complaints and subscription changes
21- Implementing email engagement analytics
22- Troubleshooting webhook authentication issues
23
24## Essential Code
25
26### Authentication
27
28Postmark does NOT use signature verification. Instead, webhooks are authenticated by including credentials in the webhook URL itself.
29
30```javascript
31// Express - Basic Auth in URL
32// Configure webhook URL in Postmark as:
33// https://username:password@yourdomain.com/webhooks/postmark
34
35app.post('/webhooks/postmark', express.json(), (req, res) => {
36 // Basic auth is handled by your web server or proxy
37 // Additional validation can check expected payload structure
38
39 const event = req.body;
40
41 // Validate expected fields exist
42 if (!event.RecordType || !event.MessageID) {
43 return res.status(400).send('Invalid payload structure');
44 }
45
46 // Process event
47 console.log(`Received ${event.RecordType} event for ${event.Email}`);
48
49 res.sendStatus(200);
50});
51
52// Alternative: Token in URL
53// Configure webhook URL as:
54// https://yourdomain.com/webhooks/postmark?token=your-secret-token
55
56app.post('/webhooks/postmark', express.json(), (req, res) => {
57 const token = req.query.token;
58
59 if (token !== process.env.POSTMARK_WEBHOOK_TOKEN) {
60 return res.status(401).send('Unauthorized');
61 }
62
63 const event = req.body;
64 console.log(`Received ${event.RecordType} event`);
65
66 res.sendStatus(200);
67});
68```
69
70### Handling Multiple Events
71
72```javascript
73// Postmark sends one event per request (not batched)
74app.post('/webhooks/postmark', express.json(), (req, res) => {
75 const event = req.body;
76
77 switch (event.RecordType) {
78 case 'Bounce':
79 console.log(`Bounce: ${event.Email} - ${event.Type} - ${event.Description}`);
80 // Update contact as undeliverable
81 break;
82
83 case 'SpamComplaint':
84 console.log(`Spam complaint: ${event.Email}`);
85 // Remove from mailing list
86 break;
87
88 case 'Open':
89 console.log(`Email opened: ${event.Email} at ${event.ReceivedAt}`);
90 // Track engagement
91 break;
92
93 case 'Click':
94 console.log(`Link clicked: ${event.Email} - ${event.OriginalLink}`);
95 // Track click-through rate
96 break;
97
98 case 'Delivery':
99 console.log(`Delivered: ${event.Email} at ${event.DeliveredAt}`);
100 // Confirm delivery
101 break;
102
103 case 'SubscriptionChange':
104 console.log(`Subscription change: ${event.Email} - ${event.ChangedAt}`);
105 // Update subscription preferences
106 break;
107
108 case 'Inbound':
109 console.log(`Inbound email from: ${event.Email} - Subject: ${event.Subject}`);
110 // Process incoming email
111 break;
112
113 case 'SMTP API Error':
114 console.log(`SMTP API error: ${event.Email} - ${event.Error}`);
115 // Handle API error, maybe retry
116 break;
117
118 default:
119 console.log(`Unknown event type: ${event.RecordType}`);
120 }
121
122 res.sendStatus(200);
123});
124```
125
126## Common Event Types
127
128| Event | RecordType | Description | Key Fields |
129|-------|------------|-------------|------------|
130| Bounce | `Bounce` | Hard/soft bounce or blocked email | Email, Type, TypeCode, Description |
131| Spam Complaint | `SpamComplaint` | Recipient marked as spam | Email, BouncedAt |
132| Open | `Open` | Email opened (requires open tracking) | Email, ReceivedAt, Platform, UserAgent |
133| Click | `Click` | Link clicked (requires click tracking) | Email, ClickedAt, OriginalLink |
134| Delivery | `Delivery` | Successfully delivered | Email, DeliveredAt, Details |
135| Subscription Change | `SubscriptionChange` | Unsubscribe/resubscribe | Email, ChangedAt, SuppressionReason |
136| Inbound | `Inbound` | Incoming email received | Email, FromFull, Subject, TextBody, HtmlBody |
137| SMTP API Error | `SMTP API Error` | SMTP API call failed | Email, Error, ErrorCode, MessageID |
138
139## Environment Variables
140
141```bash
142# For token-based authentication
143POSTMARK_WEBHOOK_TOKEN="your-secret-token-here"
144
145# For basic auth (if not using URL-embedded credentials)
146WEBHOOK_USERNAME="your-username"
147WEBHOOK_PASSWORD="your-password"
148```
149
150## Security Best Practices
151
1521. **Always use HTTPS** - Never configure webhooks with HTTP URLs
1532. **Use strong credentials** - Generate long, random tokens or passwords
1543. **Validate payload structure** - Check for expected fields before processing
1554. **Implement IP allowlisting** - Postmark publishes their IP ranges
1565. **Consider using a webhook gateway** - Like Hookdeck for additional security layers
157
158## Local Development
159
160For local webhook testing, use Hookdeck CLI:
161
162```bash
163npx hookdeck-cli listen 3000 postmark --path /webhooks/postmark
164```
165
166No account required. Provides local tunnel + web UI for inspecting requests.
167
168## Resources
169
170- [overview.md](references/overview.md) - What Postmark webhooks are, common event types
171- [setup.md](references/setup.md) - Configure webhooks in Postmark dashboard
172- [verification.md](references/verification.md) - Authentication methods and security best practices
173- [examples/](examples/) - Complete implementations for Express, Next.js, and FastAPI
174
175## Recommended: webhook-handler-patterns
176
177For production-ready webhook handling, also install the webhook-handler-patterns skill:
178
179- [Handler sequence](https://github.com/hookdeck/webhook-skills/blob/main/skills/webhook-handler-patterns/references/handler-sequence.md) - Webhook processing flow
180- [Idempotency](https://github.com/hookdeck/webhook-skills/blob/main/skills/webhook-handler-patterns/references/idempotency.md) - Prevent duplicate processing
181- [Error handling](https://github.com/hookdeck/webhook-skills/blob/main/skills/webhook-handler-patterns/references/error-handling.md) - Graceful error recovery
182- [Retry logic](https://github.com/hookdeck/webhook-skills/blob/main/skills/webhook-handler-patterns/references/retry-logic.md) - Handle transient failures
183
184## Related Skills
185
186- [sendgrid-webhooks](https://github.com/hookdeck/webhook-skills/tree/main/skills/sendgrid-webhooks) - SendGrid webhook handling with ECDSA verification
187- [resend-webhooks](https://github.com/hookdeck/webhook-skills/tree/main/skills/resend-webhooks) - Resend webhook handling with Svix signatures
188- [stripe-webhooks](https://github.com/hookdeck/webhook-skills/tree/main/skills/stripe-webhooks) - Stripe webhook handling with HMAC-SHA256
189- [webhook-handler-patterns](https://github.com/hookdeck/webhook-skills/tree/main/skills/webhook-handler-patterns) - Idempotency, error handling, retry logic
190- [hookdeck-event-gateway](https://github.com/hookdeck/webhook-skills/tree/main/skills/hookdeck-event-gateway) - Production webhook infrastructure