{"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\nbrew install hookdeck/hookdeck/hookdeck\nhookdeck listen 3000 --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.484","qualityRationale":"deterministic score 0.48 from registry signals: · indexed on github topic:agent-skills · 69 github stars · SKILL.md body (6,733 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-02T06:55:47.182Z","embedding":null,"createdAt":"2026-04-18T22:13:58.392Z","updatedAt":"2026-05-02T06:55:47.182Z","lastSeenAt":"2026-05-02T06:55:47.182Z","tsv":"'/hookdeck/webhook-skills/blob/main/skills/webhook-handler-patterns/references/error-handling.md)':595 '/hookdeck/webhook-skills/blob/main/skills/webhook-handler-patterns/references/handler-sequence.md)':580 '/hookdeck/webhook-skills/blob/main/skills/webhook-handler-patterns/references/idempotency.md)':587 '/hookdeck/webhook-skills/blob/main/skills/webhook-handler-patterns/references/retry-logic.md)':603 '/hookdeck/webhook-skills/tree/main/skills/hookdeck-event-gateway)':664 '/hookdeck/webhook-skills/tree/main/skills/resend-webhooks)':626 '/hookdeck/webhook-skills/tree/main/skills/sendgrid-webhooks)':614 '/hookdeck/webhook-skills/tree/main/skills/stripe-webhooks)':638 '/hookdeck/webhook-skills/tree/main/skills/webhook-handler-patterns)':652 '/webhooks/postmark':102,104,164,203,511 '/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':513 'addit':118,491 'allowlist':476 'also':568 '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,542 'base':410 'bash':406,503 'basic':90,108,421 'batch':201 'best':441,546 'block':330 'bounc':21,49,213,215,325,326,328 'bouncedat':344 'break':223,234,245,258,268,279,292,308 'brew':504 'call':398 'case':212,224,235,246,259,269,280,293 'chang':28,58,273,374 'changedat':378 'check':121,467 'cli':502 'click':23,52,247,250,255,356,357,359,361 'click-through':254 'clickedat':364 'code':68 'common':317,530 'complaint':25,55,228,337 'complet':550 'configur':94,156,448,535 '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':539 'default':309 'deliv':50,262,369 'deliveredat':371 'deliveri':18,47,260,267,366,367 'descript':322,335 'detail':372 'develop':495 'duplic':589 'ecdsa':619 '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,591,597,654 'errorcod':402 'essenti':67 'event':19,43,48,126,143,147,182,187,192,197,208,312,318,320,531,660 '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':548,549 'exist':131 'expect':122,129,469 'express':89,553 'express.json':105,165,204 'fail':399 'failur':606 'fastapi':556 'field':130,324,470 'flow':583 'fromful':386 'gateway':487,661 'generat':457 'github.com':579,586,594,602,613,625,637,651,663 'github.com/hookdeck/webhook-skills/blob/main/skills/webhook-handler-patterns/references/error-handling.md)':593 'github.com/hookdeck/webhook-skills/blob/main/skills/webhook-handler-patterns/references/handler-sequence.md)':578 'github.com/hookdeck/webhook-skills/blob/main/skills/webhook-handler-patterns/references/idempotency.md)':585 'github.com/hookdeck/webhook-skills/blob/main/skills/webhook-handler-patterns/references/retry-logic.md)':601 'github.com/hookdeck/webhook-skills/tree/main/skills/hookdeck-event-gateway)':662 'github.com/hookdeck/webhook-skills/tree/main/skills/resend-webhooks)':624 'github.com/hookdeck/webhook-skills/tree/main/skills/sendgrid-webhooks)':612 'github.com/hookdeck/webhook-skills/tree/main/skills/stripe-webhooks)':636 'github.com/hookdeck/webhook-skills/tree/main/skills/webhook-handler-patterns)':650 'grace':596 'handl':16,53,111,190,303,567,592,604,617,629,641,655 'handler':15,40,560,573,576,648 'hard/soft':327 'hmac':644 'hmac-sha256':643 'hookdeck':489,501,507,659 'hookdeck-event-gateway':658 'hookdeck/hookdeck/hookdeck':506 'htmlbodi':389 'http':451 'https':446 'idempot':584,653 'implement':59,474,551 'inbound':281,283,380,381 'includ':81 'incom':290,382 'infrastructur':667 'inspect':521 'instal':505,569 '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,516 'logic':600,657 'long':458 'mail':232 'mark':340 'mayb':306 'messageid':403 'method':543 'multipl':191 'never':447 'next.js':554 'one':196 'open':22,51,236,239,345,346,348,350 'originallink':365 'overview.md':524 'password':436,439,462 'password@yourdomain.com':101 'path':510 'pattern':561,574,649 'payload':123,140,465 'per':198 'platform':354 'postmark':2,7,13,29,38,70,98,194,412,477,527,538 'postmark-webhook':1 'practic':442,547 'prefer':278 'prevent':588 'process':6,20,45,142,289,472,582,590 'process.env.postmark':173 'product':564,665 'production-readi':563 'provid':515 'proxi':117 'publish':478 'random':459 'rang':481 'rate':257 'readi':565 'receiv':4,145,185,384 'receivedat':353 'recipi':339 'recommend':557 'recordtyp':321 'recoveri':598 'references/overview.md':525 'references/setup.md':534 'references/verification.md':541 'relat':607 'remov':230 'req':106,166,205 'req.body':127,183,209 'req.query.token':170 'request':199,522 'requir':349,360,514 'res':107,167,206 'res.sendstatus':150,188,315 'res.status':136,177 'resend':622,627 'resend-webhook':621 'resourc':523 'retri':307,599,656 'return':135,176 'secret':417 'secur':440,492,545 'send':138,179,195 'sendgrid':610,615 'sendgrid-webhook':609 'sequenc':577 'server':115 'set':11,36 'setup.md':533 'sha256':645 'signatur':74,632 'skill':35,575,608 'skill-postmark-webhooks' 'smtp':294,298,390,393,396 'source-hookdeck' 'spam':24,54,227,336,342 'spamcomplaint':225,338 'stripe':634,639 'stripe-webhook':633 'strong':455 'structur':124,141,466 'subject':287,387 'subscript':27,57,272,277,373 'subscriptionchang':270,375 'success':368 'suppressionreason':379 'svix':631 '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':605 'troubleshoot':63 'tunnel':517 'type':313,319,333,532 'typecod':334 'ui':519 '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,620 'verification.md':540 'web':114,518 'webhook':3,8,14,30,39,64,77,85,95,157,174,413,430,435,449,486,498,528,536,559,566,572,581,611,616,623,628,635,640,647,666 'webhook-handler-pattern':558,571,646 '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-02T06:55:47.182Z"}],"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":69,"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-02-25T14:00:40Z","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":"5eda2f02657b88c936cbef04dfe66d00b63565ca","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-02T06:55:47.182Z"}}