{"id":"d9781882-e241-4025-a563-33630006bb2d","shortId":"HPEVGp","kind":"skill","title":"postmark-webhooks","tagline":"Receive and process Postmark webhooks. Use when setting up Postmark webhook handlers, handling email delivery events, processing bounces, opens, clicks, spam complaints, or subscription changes.","description":"# Postmark Webhooks\n\n## When to Use This Skill\n\n- Setting up Postmark webhook handlers for email event tracking\n- Processing email delivery events (bounce, delivered, open, click)\n- Handling spam complaints and subscription changes\n- Implementing email engagement analytics\n- Troubleshooting webhook authentication issues\n\n## Essential Code\n\n### Authentication\n\nPostmark does NOT use signature verification. Instead, webhooks are authenticated by including credentials in the webhook URL itself.\n\n```javascript\n// Express - Basic Auth in URL\n// Configure webhook URL in Postmark as:\n// https://username:password@yourdomain.com/webhooks/postmark\n\napp.post('/webhooks/postmark', express.json(), (req, res) => {\n  // Basic auth is handled by your web server or proxy\n  // Additional validation can check expected payload structure\n\n  const event = req.body;\n\n  // Validate expected fields exist\n  if (!event.RecordType || !event.MessageID) {\n    return res.status(400).send('Invalid payload structure');\n  }\n\n  // Process event\n  console.log(`Received ${event.RecordType} event for ${event.Email}`);\n\n  res.sendStatus(200);\n});\n\n// Alternative: Token in URL\n// Configure webhook URL as:\n// https://yourdomain.com/webhooks/postmark?token=your-secret-token\n\napp.post('/webhooks/postmark', express.json(), (req, res) => {\n  const token = req.query.token;\n\n  if (token !== process.env.POSTMARK_WEBHOOK_TOKEN) {\n    return res.status(401).send('Unauthorized');\n  }\n\n  const event = req.body;\n  console.log(`Received ${event.RecordType} event`);\n\n  res.sendStatus(200);\n});\n```\n\n### Handling Multiple Events\n\n```javascript\n// Postmark sends one event per request (not batched)\napp.post('/webhooks/postmark', express.json(), (req, res) => {\n  const event = req.body;\n\n  switch (event.RecordType) {\n    case 'Bounce':\n      console.log(`Bounce: ${event.Email} - ${event.Type} - ${event.Description}`);\n      // Update contact as undeliverable\n      break;\n\n    case 'SpamComplaint':\n      console.log(`Spam complaint: ${event.Email}`);\n      // Remove from mailing list\n      break;\n\n    case 'Open':\n      console.log(`Email opened: ${event.Email} at ${event.ReceivedAt}`);\n      // Track engagement\n      break;\n\n    case 'Click':\n      console.log(`Link clicked: ${event.Email} - ${event.OriginalLink}`);\n      // Track click-through rate\n      break;\n\n    case 'Delivery':\n      console.log(`Delivered: ${event.Email} at ${event.DeliveredAt}`);\n      // Confirm delivery\n      break;\n\n    case 'SubscriptionChange':\n      console.log(`Subscription change: ${event.Email} - ${event.ChangedAt}`);\n      // Update subscription preferences\n      break;\n\n    case 'Inbound':\n      console.log(`Inbound email from: ${event.Email} - Subject: ${event.Subject}`);\n      // Process incoming email\n      break;\n\n    case 'SMTP API Error':\n      console.log(`SMTP API error: ${event.Email} - ${event.Error}`);\n      // Handle API error, maybe retry\n      break;\n\n    default:\n      console.log(`Unknown event type: ${event.RecordType}`);\n  }\n\n  res.sendStatus(200);\n});\n```\n\n## Common Event Types\n\n| Event | RecordType | Description | Key Fields |\n|-------|------------|-------------|------------|\n| Bounce | `Bounce` | Hard/soft bounce or blocked email | Email, Type, TypeCode, Description |\n| Spam Complaint | `SpamComplaint` | Recipient marked as spam | Email, BouncedAt |\n| Open | `Open` | Email opened (requires open tracking) | Email, ReceivedAt, Platform, UserAgent |\n| Click | `Click` | Link clicked (requires click tracking) | Email, ClickedAt, OriginalLink |\n| Delivery | `Delivery` | Successfully delivered | Email, DeliveredAt, Details |\n| Subscription Change | `SubscriptionChange` | Unsubscribe/resubscribe | Email, ChangedAt, SuppressionReason |\n| Inbound | `Inbound` | Incoming email received | Email, FromFull, Subject, TextBody, HtmlBody |\n| SMTP API Error | `SMTP API Error` | SMTP API call failed | Email, Error, ErrorCode, MessageID |\n\n## Environment Variables\n\n```bash\n# For token-based authentication\nPOSTMARK_WEBHOOK_TOKEN=\"your-secret-token-here\"\n\n# For basic auth (if not using URL-embedded credentials)\nWEBHOOK_USERNAME=\"your-username\"\nWEBHOOK_PASSWORD=\"your-password\"\n```\n\n## Security Best Practices\n\n1. **Always use HTTPS** - Never configure webhooks with HTTP URLs\n2. **Use strong credentials** - Generate long, random tokens or passwords\n3. **Validate payload structure** - Check for expected fields before processing\n4. **Implement IP allowlisting** - Postmark publishes their IP ranges\n5. **Consider using a webhook gateway** - Like Hookdeck for additional security layers\n\n## Local Development\n\nFor local webhook testing, use Hookdeck CLI:\n\n```bash\nnpx hookdeck-cli listen 3000 postmark --path /webhooks/postmark\n```\n\nNo account required. Provides local tunnel + web UI for inspecting requests.\n\n## Resources\n\n- [overview.md](references/overview.md) - What Postmark webhooks are, common event types\n- [setup.md](references/setup.md) - Configure webhooks in Postmark dashboard\n- [verification.md](references/verification.md) - Authentication methods and security best practices\n- [examples/](examples/) - Complete implementations for Express, Next.js, and FastAPI\n\n## Recommended: webhook-handler-patterns\n\nFor production-ready webhook handling, also install the webhook-handler-patterns skill:\n\n- [Handler sequence](https://github.com/hookdeck/webhook-skills/blob/main/skills/webhook-handler-patterns/references/handler-sequence.md) - Webhook processing flow\n- [Idempotency](https://github.com/hookdeck/webhook-skills/blob/main/skills/webhook-handler-patterns/references/idempotency.md) - Prevent duplicate processing\n- [Error handling](https://github.com/hookdeck/webhook-skills/blob/main/skills/webhook-handler-patterns/references/error-handling.md) - Graceful error recovery\n- [Retry logic](https://github.com/hookdeck/webhook-skills/blob/main/skills/webhook-handler-patterns/references/retry-logic.md) - Handle transient failures\n\n## Related Skills\n\n- [sendgrid-webhooks](https://github.com/hookdeck/webhook-skills/tree/main/skills/sendgrid-webhooks) - SendGrid webhook handling with ECDSA verification\n- [resend-webhooks](https://github.com/hookdeck/webhook-skills/tree/main/skills/resend-webhooks) - Resend webhook handling with Svix signatures\n- [stripe-webhooks](https://github.com/hookdeck/webhook-skills/tree/main/skills/stripe-webhooks) - Stripe webhook handling with HMAC-SHA256\n- [webhook-handler-patterns](https://github.com/hookdeck/webhook-skills/tree/main/skills/webhook-handler-patterns) - Idempotency, error handling, retry logic\n- [hookdeck-event-gateway](https://github.com/hookdeck/webhook-skills/tree/main/skills/hookdeck-event-gateway) - Production webhook infrastructure","tags":["postmark","webhooks","webhook","skills","hookdeck","agent-skills","ai-coding","api-integrations","event-driven","github-webhooks","llm-tools","shopify-webhooks"],"capabilities":["skill","source-hookdeck","skill-postmark-webhooks","topic-agent-skills","topic-ai-coding","topic-api-integrations","topic-event-driven","topic-github-webhooks","topic-llm-tools","topic-shopify-webhooks","topic-stripe-webhooks","topic-webhook-security","topic-webhook-signatures","topic-webhooks"],"categories":["webhook-skills"],"synonyms":[],"warnings":[],"endpointUrl":"https://skills.sh/hookdeck/webhook-skills/postmark-webhooks","protocol":"skill","transport":"skills-sh","auth":{"type":"none","details":{"cli":"npx skills add hookdeck/webhook-skills","source_repo":"https://github.com/hookdeck/webhook-skills","install_from":"skills.sh"}},"qualityScore":"0.485","qualityRationale":"deterministic score 0.48 from registry signals: · indexed on github topic:agent-skills · 71 github stars · SKILL.md body (6,710 chars)","verified":false,"liveness":"unknown","lastLivenessCheck":null,"agentReviews":{"count":0,"score_avg":null,"cost_usd_avg":null,"success_rate":null,"latency_p50_ms":null,"narrative_summary":null,"summary_updated_at":null},"enrichmentModel":"deterministic:skill-github:v1","enrichmentVersion":1,"enrichedAt":"2026-05-18T18:56:55.616Z","embedding":null,"createdAt":"2026-04-18T22:13:58.392Z","updatedAt":"2026-05-18T18:56:55.616Z","lastSeenAt":"2026-05-18T18:56:55.616Z","tsv":"'/hookdeck/webhook-skills/blob/main/skills/webhook-handler-patterns/references/error-handling.md)':596 '/hookdeck/webhook-skills/blob/main/skills/webhook-handler-patterns/references/handler-sequence.md)':581 '/hookdeck/webhook-skills/blob/main/skills/webhook-handler-patterns/references/idempotency.md)':588 '/hookdeck/webhook-skills/blob/main/skills/webhook-handler-patterns/references/retry-logic.md)':604 '/hookdeck/webhook-skills/tree/main/skills/hookdeck-event-gateway)':665 '/hookdeck/webhook-skills/tree/main/skills/resend-webhooks)':627 '/hookdeck/webhook-skills/tree/main/skills/sendgrid-webhooks)':615 '/hookdeck/webhook-skills/tree/main/skills/stripe-webhooks)':639 '/hookdeck/webhook-skills/tree/main/skills/webhook-handler-patterns)':653 '/webhooks/postmark':102,104,164,203,512 '/webhooks/postmark?token=your-secret-token':162 '1':443 '2':453 '200':151,189,316 '3':463 '3000':509 '4':473 '400':137 '401':178 '5':482 'account':514 'addit':118,491 'allowlist':476 'also':569 'altern':152 'alway':444 'analyt':62 'api':295,299,304,391,394,397 'app.post':103,163,202 'auth':91,109,422 'authent':65,69,79,411,543 'base':410 'bash':406,503 'basic':90,108,421 'batch':201 'best':441,547 'block':330 'bounc':21,49,213,215,325,326,328 'bouncedat':344 'break':223,234,245,258,268,279,292,308 'call':398 'case':212,224,235,246,259,269,280,293 'chang':28,58,273,374 'changedat':378 'check':121,467 'cli':502,507 'click':23,52,247,250,255,356,357,359,361 'click-through':254 'clickedat':364 'code':68 'common':317,531 'complaint':25,55,228,337 'complet':551 'configur':94,156,448,536 'confirm':266 'consid':483 'console.log':144,184,214,226,237,248,261,271,282,297,310 'const':125,168,181,207 'contact':220 'credenti':82,429,456 'dashboard':540 'default':309 'deliv':50,262,369 'deliveredat':371 'deliveri':18,47,260,267,366,367 'descript':322,335 'detail':372 'develop':495 'duplic':590 'ecdsa':620 'email':17,42,46,60,238,284,291,331,332,343,347,352,363,370,377,383,385,400 'embed':428 'engag':61,244 'environ':404 'error':296,300,305,392,395,401,592,598,655 'errorcod':402 'essenti':67 'event':19,43,48,126,143,147,182,187,192,197,208,312,318,320,532,661 'event.changedat':275 'event.deliveredat':265 'event.description':218 'event.email':149,216,229,240,251,263,274,286,301 'event.error':302 'event.messageid':134 'event.originallink':252 'event.receivedat':242 'event.recordtype':133,146,186,211,314 'event.subject':288 'event.type':217 'exampl':549,550 'exist':131 'expect':122,129,469 'express':89,554 'express.json':105,165,204 'fail':399 'failur':607 'fastapi':557 'field':130,324,470 'flow':584 'fromful':386 'gateway':487,662 'generat':457 'github.com':580,587,595,603,614,626,638,652,664 'github.com/hookdeck/webhook-skills/blob/main/skills/webhook-handler-patterns/references/error-handling.md)':594 'github.com/hookdeck/webhook-skills/blob/main/skills/webhook-handler-patterns/references/handler-sequence.md)':579 'github.com/hookdeck/webhook-skills/blob/main/skills/webhook-handler-patterns/references/idempotency.md)':586 'github.com/hookdeck/webhook-skills/blob/main/skills/webhook-handler-patterns/references/retry-logic.md)':602 'github.com/hookdeck/webhook-skills/tree/main/skills/hookdeck-event-gateway)':663 'github.com/hookdeck/webhook-skills/tree/main/skills/resend-webhooks)':625 'github.com/hookdeck/webhook-skills/tree/main/skills/sendgrid-webhooks)':613 'github.com/hookdeck/webhook-skills/tree/main/skills/stripe-webhooks)':637 'github.com/hookdeck/webhook-skills/tree/main/skills/webhook-handler-patterns)':651 'grace':597 'handl':16,53,111,190,303,568,593,605,618,630,642,656 'handler':15,40,561,574,577,649 'hard/soft':327 'hmac':645 'hmac-sha256':644 'hookdeck':489,501,506,660 'hookdeck-c':505 'hookdeck-event-gateway':659 'htmlbodi':389 'http':451 'https':446 'idempot':585,654 'implement':59,474,552 'inbound':281,283,380,381 'includ':81 'incom':290,382 'infrastructur':668 'inspect':522 'instal':570 'instead':76 'invalid':139 'ip':475,480 'issu':66 'javascript':88,193 'key':323 'layer':493 'like':488 'link':249,358 'list':233 'listen':508 'local':494,497,517 'logic':601,658 'long':458 'mail':232 'mark':340 'mayb':306 'messageid':403 'method':544 'multipl':191 'never':447 'next.js':555 'npx':504 'one':196 'open':22,51,236,239,345,346,348,350 'originallink':365 'overview.md':525 'password':436,439,462 'password@yourdomain.com':101 'path':511 'pattern':562,575,650 'payload':123,140,465 'per':198 'platform':354 'postmark':2,7,13,29,38,70,98,194,412,477,510,528,539 'postmark-webhook':1 'practic':442,548 'prefer':278 'prevent':589 'process':6,20,45,142,289,472,583,591 'process.env.postmark':173 'product':565,666 'production-readi':564 'provid':516 'proxi':117 'publish':478 'random':459 'rang':481 'rate':257 'readi':566 'receiv':4,145,185,384 'receivedat':353 'recipi':339 'recommend':558 'recordtyp':321 'recoveri':599 'references/overview.md':526 'references/setup.md':535 'references/verification.md':542 'relat':608 'remov':230 'req':106,166,205 'req.body':127,183,209 'req.query.token':170 'request':199,523 'requir':349,360,515 'res':107,167,206 'res.sendstatus':150,188,315 'res.status':136,177 'resend':623,628 'resend-webhook':622 'resourc':524 'retri':307,600,657 'return':135,176 'secret':417 'secur':440,492,546 'send':138,179,195 'sendgrid':611,616 'sendgrid-webhook':610 'sequenc':578 'server':115 'set':11,36 'setup.md':534 'sha256':646 'signatur':74,633 'skill':35,576,609 'skill-postmark-webhooks' 'smtp':294,298,390,393,396 'source-hookdeck' 'spam':24,54,227,336,342 'spamcomplaint':225,338 'stripe':635,640 'stripe-webhook':634 'strong':455 'structur':124,141,466 'subject':287,387 'subscript':27,57,272,277,373 'subscriptionchang':270,375 'success':368 'suppressionreason':379 'svix':632 'switch':210 'test':499 'textbodi':388 'token':153,169,172,175,409,414,418,460 'token-bas':408 'topic-agent-skills' 'topic-ai-coding' 'topic-api-integrations' 'topic-event-driven' 'topic-github-webhooks' 'topic-llm-tools' 'topic-shopify-webhooks' 'topic-stripe-webhooks' 'topic-webhook-security' 'topic-webhook-signatures' 'topic-webhooks' 'track':44,243,253,351,362 'transient':606 'troubleshoot':63 'tunnel':518 'type':313,319,333,533 'typecod':334 'ui':520 'unauthor':180 'undeliver':222 'unknown':311 'unsubscribe/resubscribe':376 'updat':219,276 'url':86,93,96,155,158,427,452 'url-embed':426 'use':9,33,73,425,445,454,484,500 'userag':355 'usernam':100,431,434 'valid':119,128,464 'variabl':405 'verif':75,621 'verification.md':541 'web':114,519 'webhook':3,8,14,30,39,64,77,85,95,157,174,413,430,435,449,486,498,529,537,560,567,573,582,612,617,624,629,636,641,648,667 'webhook-handler-pattern':559,572,647 'your-password':437 'your-secret-token-her':415 'your-usernam':432 'yourdomain.com':161 'yourdomain.com/webhooks/postmark?token=your-secret-token':160","prices":[{"id":"c1f2daff-744c-4619-b3d6-cffdb5db84f8","listingId":"d9781882-e241-4025-a563-33630006bb2d","amountUsd":"0","unit":"free","nativeCurrency":null,"nativeAmount":null,"chain":null,"payTo":null,"paymentMethod":"skill-free","isPrimary":true,"details":{"org":"hookdeck","category":"webhook-skills","install_from":"skills.sh"},"createdAt":"2026-04-18T22:13:58.392Z"}],"sources":[{"listingId":"d9781882-e241-4025-a563-33630006bb2d","source":"github","sourceId":"hookdeck/webhook-skills/postmark-webhooks","sourceUrl":"https://github.com/hookdeck/webhook-skills/tree/main/skills/postmark-webhooks","isPrimary":false,"firstSeenAt":"2026-04-18T22:13:58.392Z","lastSeenAt":"2026-05-18T18:56:55.616Z"},{"listingId":"d9781882-e241-4025-a563-33630006bb2d","source":"skills_sh","sourceId":"hookdeck/webhook-skills/postmark-webhooks","sourceUrl":"https://skills.sh/hookdeck/webhook-skills/postmark-webhooks","isPrimary":true,"firstSeenAt":"2026-05-07T20:44:45.872Z","lastSeenAt":"2026-05-07T22:43:00.743Z"}],"details":{"listingId":"d9781882-e241-4025-a563-33630006bb2d","quickStartSnippet":null,"exampleRequest":null,"exampleResponse":null,"schema":null,"openapiUrl":null,"agentsTxtUrl":null,"citations":[],"useCases":[],"bestFor":[],"notFor":[],"kindDetails":{"org":"hookdeck","slug":"postmark-webhooks","github":{"repo":"hookdeck/webhook-skills","stars":71,"topics":["agent-skills","ai-coding","api-integrations","event-driven","github-webhooks","llm-tools","shopify-webhooks","stripe-webhooks","webhook-security","webhook-signatures","webhooks"],"license":"mit","html_url":"https://github.com/hookdeck/webhook-skills","pushed_at":"2026-05-15T15:30:15Z","description":"Webhook integration skills for AI coding agents (Claude Code, Cursor, Copilot). Step-by-step guidance for setting up webhook receivers, signature verification, and event handling for Stripe, Shopify, GitHub, and more. Built on the Agent Skills specification.","skill_md_sha":"68a747787b293b2515d6fa64c27b9d7abdc8f5b4","skill_md_path":"skills/postmark-webhooks/SKILL.md","default_branch":"main","skill_tree_url":"https://github.com/hookdeck/webhook-skills/tree/main/skills/postmark-webhooks"},"layout":"multi","source":"github","category":"webhook-skills","frontmatter":{"name":"postmark-webhooks","license":"MIT","description":"Receive and process Postmark webhooks. Use when setting up Postmark webhook handlers, handling email delivery events, processing bounces, opens, clicks, spam complaints, or subscription changes."},"skills_sh_url":"https://skills.sh/hookdeck/webhook-skills/postmark-webhooks"},"updatedAt":"2026-05-18T18:56:55.616Z"}}