# Postmark unsubscribe or reactivation notifications on your phone

> Get a low-priority push notification when someone unsubscribes from a Postmark message stream, or an address is suppressed or reactivated.

- Company: Postmark (https://www.justpush.io/recipes/postmark)
- Event: `SubscriptionChange` (Subscription Change)
- Tags: Email delivery, Email deliverability
- Install: https://studio.justpush.io/recipes/postmark/subscription-change
- Web page: https://www.justpush.io/recipes/postmark/subscription-change

## Setup

1. Click Install in Studio and sign in. The recipe is added to your account and you get a personal webhook URL.
2. In Postmark, open your Server, pick the Message Stream (for example Default Transactional Stream) and go to its Webhooks tab.
3. Click Add webhook and paste your webhook URL (shown in Studio after install) as the Webhook URL.
4. Tick Subscription Change and save. Postmark checks that the endpoint answers before it starts sending.
5. Webhooks in Postmark belong to one **Message Stream** of one **Server**, so add this webhook to each stream you want to watch. Unsubscribes mostly happen on broadcast streams.
6. By default you get a push when:
7. a recipient unsubscribes (Low priority);
8. you suppress an address manually (Low) or Postmark does (Normal);
9. an address is reactivated (Low).
10. Suppressions caused by a hard bounce or a spam complaint are skipped, because the [bounce](../bounce/) and [spam complaint](../spam-complaint/) recipes already cover them. Add `?all=1` to the webhook URL to get those too:
11. ```
12. your webhook URL (shown in Studio after install)?all=1
13. ```
14. Postmark doesn't sign webhooks. It supports HTTP basic auth in the URL and custom headers, but JustPush doesn't check either yet, so keep your endpoint URL private. When you save the webhook, Postmark checks that the endpoint answers for each event type; that check may produce one test push.

## Code

Studio calls `handleRequest(request)` with the incoming webhook (`{ method, headers, body, query, raw }`) and sends the message object it returns. Returning `null` sends nothing.

```js
// Postmark → Subscription change webhook
// Fires when an address is added to or removed from a Message Stream's suppression list.
// Pushes unsubscribes, manual suppressions and reactivations. Suppressions caused by a hard
// bounce or spam complaint only push with ?all=1 (the bounce and spam complaint recipes cover those).
// Docs: https://postmarkapp.com/developer/webhooks/subscription-change-webhook

// Why an address was suppressed, as the recipe describes it.
const REASONS = {
    HardBounce: "after a hard bounce",
    SpamComplaint: "after a spam complaint",
}

// Look a key up without tripping over inherited names like "toString".
function pick(table, key, fallback) {
    return Object.prototype.hasOwnProperty.call(table, key) ? table[key] : fallback
}

// A trimmed string, or null.
function text(value) {
    return typeof value === "string" && value.trim() ? value.trim() : null
}

function handleRequest(request) {
    const body = request.body && typeof request.body === "object" ? request.body : {}

    // Guard: only subscription changes with a clear direction.
    if (body.RecordType !== "SubscriptionChange" || typeof body.SuppressSending !== "boolean") {
        console.log(`Ignoring Postmark "${body.RecordType ?? "unknown"}" webhook`)
        return null
    }

    const who = text(body.Recipient) ?? "An address"
    const stream = text(body.MessageStream)
    const where = stream ? ` (${stream} stream)` : ""

    // Reactivated: the address can receive email again.
    if (!body.SuppressSending) {
        return {
            title: "✅ Address reactivated",
            message: `${who} can receive email again${where}`,
            topic: "Postmark",
            priority: -1, // Low: routine list housekeeping
        }
    }

    // ?all=1 on the webhook URL also pushes suppressions caused by bounces and complaints.
    const all = ["1", "true", "yes"].includes(String(request.query?.all ?? "").toLowerCase())
    const bounceReason = pick(REASONS, body.SuppressionReason, null)

    if (bounceReason) {
        if (!all) {
            console.log(`Skipping suppression "${body.SuppressionReason}", covered by the bounce and spam recipes`)
            return null
        }
        return {
            title: "🚫 Address suppressed",
            message: `${who} ${bounceReason}${where}`,
            topic: "Postmark",
            priority: 0, // Normal: you lost a recipient
        }
    }

    // ManualSuppression: an unsubscribe when the recipient did it, otherwise you or Postmark.
    if (body.Origin === "Recipient") {
        return {
            title: "👋 Unsubscribed",
            message: `${who} unsubscribed${where}`,
            topic: "Postmark",
            priority: -1, // Low: routine, but nice to see
        }
    }

    return {
        title: "🚫 Address suppressed",
        message: `${who} was suppressed ${body.Origin === "Admin" ? "by Postmark" : "manually"}${where}`,
        topic: "Postmark",
        priority: body.Origin === "Admin" ? 0 : -1, // Normal when Postmark did it, Low when you did
    }
}
```

## Postmark SubscriptionChange webhook payload (sample)

```json
{
  "Tag": "weekly-digest",
  "Origin": "Recipient",
  "Metadata": {
    "customer_id": "cus_1234"
  },
  "ServerID": 23,
  "ChangedAt": "2026-09-29T08:33:54.9070259Z",
  "MessageID": "883953f4-6105-42a2-a16a-77a8eac79483",
  "Recipient": "sam@example.com",
  "RecordType": "SubscriptionChange",
  "MessageStream": "broadcast",
  "SuppressSending": true,
  "SuppressionReason": "ManualSuppression"
}
```

## FAQ

### Does this work on iPhone and Android?

Yes. Install the JustPush app from the App Store or Google Play and sign in. Every phone signed in to your account gets the notification.

### Do I need to write code?

No. Install the recipe in Studio, paste your webhook URL into Postmark and you are done. The code is there if you want to change the text, the sound or the buttons.

### Can I change what the notification says?

Yes. After install the recipe's code is yours. Edit it in Studio and test it against the sample payload before you save.

### What does it cost?

JustPush is free for 30 days. After that it's $19.99 a year, or $39.99 once. There is no extra charge for recipes.
