CtrlK
BlogDocsLog inGet started
Tessl Logo

postmark-webhooks

Use when setting up Postmark webhooks for tracking email delivery, bounces, opens, clicks, spam complaints, or subscription changes — includes webhook configuration, payload handling, and security.

87

1.10x
Quality

81%

Does it follow best practices?

Impact

99%

1.10x

Average score across 3 eval scenarios

SecuritybySnyk

Passed

No known issues

SKILL.md
Quality
Evals
Security

Postmark Webhooks

Overview

Postmark webhooks deliver real-time event data to your endpoint via HTTP POST. Use webhooks to track what happens after you send an email.

EventTriggerCommon Use
DeliveryEmail accepted by recipient serverConfirm delivery, update status
BounceEmail rejected by recipient serverClean lists, alert support
SpamComplaintRecipient marked as spamRemove from lists, investigate
OpenRecipient opened email (tracking pixel)Engagement analytics
ClickRecipient clicked a tracked linkEngagement analytics, conversion tracking
SubscriptionChangeRecipient unsubscribedUpdate preferences, comply with regulations

Quick Start

  1. Create a webhook via API or Postmark dashboard (Server → Webhooks)
  2. Set your endpoint URL — must accept HTTP POST and return 200
  3. Select event triggers — choose which events to receive
  4. Handle payloads — parse the JSON body for each event type
  5. Respond with 200 — acknowledge receipt immediately

Webhook API

Endpoints

EndpointMethodDescription
/webhooksGETList all webhooks for a message stream
/webhooks/{webhookid}GETGet a specific webhook
/webhooksPOSTCreate a webhook
/webhooks/{webhookid}PUTUpdate a webhook
/webhooks/{webhookid}DELETEDelete a webhook

Create a Webhook

const postmark = require('postmark');
const client = new postmark.ServerClient(process.env.POSTMARK_SERVER_TOKEN);

const webhook = await client.createWebhook({
  Url: 'https://yourdomain.com/webhooks/postmark',
  MessageStream: 'outbound',
  HttpAuth: {
    Username: 'webhook-user',
    Password: 'webhook-secret'
  },
  HttpHeaders: [
    { Name: 'X-Custom-Header', Value: 'my-value' }
  ],
  Triggers: {
    Open: { Enabled: true, PostFirstOpenOnly: false },
    Click: { Enabled: true },
    Delivery: { Enabled: true },
    Bounce: { Enabled: true, IncludeContent: true },
    SpamComplaint: { Enabled: true, IncludeContent: true },
    SubscriptionChange: { Enabled: true }
  }
});

console.log('Webhook created:', webhook.ID);

cURL

curl "https://api.postmarkapp.com/webhooks" \
  -X POST \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -H "X-Postmark-Server-Token: $POSTMARK_SERVER_TOKEN" \
  -d '{
    "Url": "https://yourdomain.com/webhooks/postmark",
    "MessageStream": "outbound",
    "Triggers": {
      "Open": { "Enabled": true, "PostFirstOpenOnly": false },
      "Click": { "Enabled": true },
      "Delivery": { "Enabled": true },
      "Bounce": { "Enabled": true, "IncludeContent": true },
      "SpamComplaint": { "Enabled": true, "IncludeContent": true },
      "SubscriptionChange": { "Enabled": true }
    }
  }'

Trigger Options

TriggerOptions
OpenEnabled, PostFirstOpenOnly (true = only first open per recipient)
ClickEnabled
DeliveryEnabled
BounceEnabled, IncludeContent (include original email content)
SpamComplaintEnabled, IncludeContent
SubscriptionChangeEnabled

Webhook Payloads

All payloads include RecordType, MessageID, MessageStream, and Metadata (from the original send). Use RecordType to route events:

app.post('/webhooks/postmark', (req, res) => {
  res.sendStatus(200); // respond immediately

  const event = req.body;
  switch (event.RecordType) {
    case 'Delivery':          handleDelivery(event); break;
    case 'Bounce':            handleBounce(event); break;
    case 'SpamComplaint':     handleSpamComplaint(event); break;
    case 'Open':              handleOpen(event); break;
    case 'Click':             handleClick(event); break;
    case 'SubscriptionChange': handleSubscriptionChange(event); break;
  }
});

Bounce Types

TypeCodeAction
HardBounce1Permanent — remove address from all lists
SoftBounce4096Temporary — Postmark retries; monitor
Transient2Temporary — retry may succeed
SpamNotification512Marked as spam at recipient's server
Blocked16Blocked by recipient server
DMARCPolicy100000Rejected due to DMARC policy

See references/payload-examples.md for full JSON payloads for all 6 event types.

See references/handler-examples.md for complete Node.js and Python implementations, async processing, deduplication, and metadata correlation.

Security

Always verify that requests are genuinely from Postmark using HTTP Basic Auth, custom headers, or IP allowlisting.

See references/security.md for full implementation examples.

Bounce Management

Use the Bounces API and Suppression Management API alongside webhooks for comprehensive bounce handling.

See references/bounce-management.md for the Bounces API, suppression management, and bounce rate thresholds.

Webhook Management

See references/webhook-setup.md for list, update, delete, and retry schedule details.

Common Mistakes

MistakeFix
Not responding 200Always return HTTP 200 — even if processing fails. Process asynchronously.
Slow webhook handlingRespond 200 immediately, then process in background (queue, worker)
No authenticationUse HTTP Basic Auth or custom headers to verify webhook source
Ignoring bounce typesHandle HardBounce differently from SoftBounce — hard bounces require permanent suppression
Not handling partial dataSome fields may be missing — always check for presence before accessing
Duplicate handlingWebhooks may be delivered more than once — use MessageID for deduplication
Missing MessageStream filterSpecify MessageStream when creating webhooks to avoid cross-stream events
Not tracking metadataInclude Metadata when sending to correlate webhook events with your records

Notes

  • Webhooks are configured per message stream — create separate webhooks for outbound and broadcast
  • Always respond HTTP 200 immediately — process webhook data asynchronously
  • Postmark retries failed webhook deliveries up to 10 times over ~10.5 hours with escalating intervals: 1 min, 5 min, 10 min, 10 min, 10 min, 15 min, 30 min, 1 hr, 2 hrs, 6 hrs. A 403 response immediately stops all retries. This retry schedule cannot be customized
  • Use MessageID to correlate webhook events with sent emails
  • Metadata from the original send is included in all webhook payloads
  • Open tracking requires a tracking pixel in HTML — it does not work with plain text emails
  • Click tracking requires TrackLinks to be enabled on the sent email
  • Bounce webhooks fire for bounces and blocks — check the Type field to distinguish
  • Spam complaints, unsubscribes, and manual deactivations have their own event types (not Bounce)
  • Individual open/click data is stored for 45 days; aggregated statistics are stored indefinitely
Repository
ActiveCampaign/postmark-skills
Last updated
Created

Is this your skill?

If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.