Webhook-skills postmark-webhooks
install
source · Clone the upstream repo
git clone https://github.com/hookdeck/webhook-skills
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/hookdeck/webhook-skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/postmark-webhooks" ~/.claude/skills/hookdeck-webhook-skills-postmark-webhooks && rm -rf "$T"
manifest:
skills/postmark-webhooks/SKILL.mdsource content
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 | | Hard/soft bounce or blocked email | Email, Type, TypeCode, Description |
| Spam Complaint | | Recipient marked as spam | Email, BouncedAt |
| Open | | Email opened (requires open tracking) | Email, ReceivedAt, Platform, UserAgent |
| Click | | Link clicked (requires click tracking) | Email, ClickedAt, OriginalLink |
| Delivery | | Successfully delivered | Email, DeliveredAt, Details |
| Subscription Change | | Unsubscribe/resubscribe | Email, ChangedAt, SuppressionReason |
| Inbound | | Incoming email received | Email, FromFull, Subject, TextBody, HtmlBody |
| 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:
brew install hookdeck/hookdeck/hookdeck hookdeck listen 3000 --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:
- Handler sequence - Webhook processing flow
- Idempotency - Prevent duplicate processing
- Error handling - Graceful error recovery
- Retry logic - Handle transient failures
Related Skills
- sendgrid-webhooks - SendGrid webhook handling with ECDSA verification
- resend-webhooks - Resend webhook handling with Svix signatures
- stripe-webhooks - Stripe webhook handling with HMAC-SHA256
- webhook-handler-patterns - Idempotency, error handling, retry logic
- hookdeck-event-gateway - Production webhook infrastructure