{
  "info": {
    "name": "Commune: React the moment something happens",
    "description": "Checking a dashboard for news means finding out late. Commune tells your own systems the moment a reader subscribes, starts a conversation or replies, or an issue finishes sending, so your CRM, your team chat and your records keep up without anybody watching.\n\nThe walkthrough: https://usecommune.dev/use-cases/react-in-real-time\n\nSet the `apiKey` variable to a key from https://usecommune.com/settings/api-keys, then run the requests in order.",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
  },
  "auth": {
    "type": "bearer",
    "bearer": [
      {
        "key": "token",
        "value": "{{apiKey}}",
        "type": "string"
      }
    ]
  },
  "variable": [
    {
      "key": "baseUrl",
      "value": "https://api.usecommune.com"
    },
    {
      "key": "apiKey",
      "value": ""
    },
    {
      "key": "version",
      "value": "2026-08-26"
    },
    {
      "key": "newsletter",
      "value": "example-letter"
    },
    {
      "key": "send",
      "value": "9a8b7c6d-5e4f-4a3b-2c1d-0e9f8a7b6c5d"
    },
    {
      "key": "attempt",
      "value": "att_2Hf6Vp8sZn"
    }
  ],
  "item": [
    {
      "name": "1. Open the delivery portal and add your endpoint",
      "request": {
        "method": "POST",
        "header": [
          {
            "key": "Commune-Version",
            "value": "{{version}}"
          }
        ],
        "url": {
          "raw": "{{baseUrl}}/newsletters/{{newsletter}}/portal-session",
          "host": [
            "{{baseUrl}}"
          ],
          "path": [
            "newsletters",
            "{{newsletter}}",
            "portal-session"
          ]
        },
        "description": "Destinations live in Commune's delivery portal, not behind an endpoint of their own. Either open [Webhooks](https://usecommune.com/dashboard/webhooks) in your Commune dashboard, or mint a link to the same portal with this request (it needs `webhooks: write`) and open the `url` it returns. In the portal, add a webhook destination pointing at your URL, pick the topics this page uses (`subscriber.created`, `thread.created`, `message.created` and `send.completed`), and copy the signing secret. It starts with `whsec_`; keep it as `COMMUNE_WEBHOOK_SECRET`.\n\nThe `url` is a credential: its `token` lets whoever holds it change where this newsletter's events go. Redirect yourself to it and let it be spent. Do not log it, store it or paste it anywhere; minting another is one request. It takes no body and no `Idempotency-Key`, and each call returns a new link."
      }
    },
    {
      "name": "2. Read the send back for the article's title",
      "request": {
        "method": "GET",
        "header": [
          {
            "key": "Commune-Version",
            "value": "{{version}}"
          }
        ],
        "url": {
          "raw": "{{baseUrl}}/sends/{{send}}?expand=article",
          "host": [
            "{{baseUrl}}"
          ],
          "path": [
            "sends",
            "{{send}}"
          ],
          "query": [
            {
              "key": "expand",
              "value": "article"
            }
          ]
        },
        "description": "The event carries ids and counts but no title. Read the run behind `send_id` with `expand=article` and the article comes back inline, title and all. This is also how you ask again later without having kept the payload. It needs `sending: read`, and expanding the article needs `content: read` too: an article is content, and a key without it is refused with `403`."
      }
    },
    {
      "name": "3. Check what is registered",
      "request": {
        "method": "GET",
        "header": [
          {
            "key": "Commune-Version",
            "value": "{{version}}"
          }
        ],
        "url": {
          "raw": "{{baseUrl}}/newsletters/{{newsletter}}/destinations",
          "host": [
            "{{baseUrl}}"
          ],
          "path": [
            "newsletters",
            "{{newsletter}}",
            "destinations"
          ]
        },
        "description": "When something is not arriving, start here. Each destination lists the `topics` it receives and whether it is `enabled`; a destination the portal switched off keeps its row with a `disabled_at`. `target` is the host it points at, not the full URL, and nothing it authenticates with (the signing secret included) is ever returned. An empty page is not an error: it means no destination was ever added, and events are recorded and delivered nowhere. Needs `webhooks: read`."
      }
    },
    {
      "name": "4. Find a delivery that failed",
      "request": {
        "method": "GET",
        "header": [
          {
            "key": "Commune-Version",
            "value": "{{version}}"
          }
        ],
        "url": {
          "raw": "{{baseUrl}}/newsletters/{{newsletter}}/delivery-attempts?status=failed&destination_id=des_4Nb8Fy1kLd",
          "host": [
            "{{baseUrl}}"
          ],
          "path": [
            "newsletters",
            "{{newsletter}}",
            "delivery-attempts"
          ],
          "query": [
            {
              "key": "status",
              "value": "failed"
            },
            {
              "key": "destination_id",
              "value": "des_4Nb8Fy1kLd"
            }
          ]
        },
        "description": "Every handover of an event to a destination is a row, newest first. A retry is a new row with `attempt` one higher, never an edit, so `status=failed` shows every failed try, including ones a later retry fixed. To follow one event from end to end, filter by `event_id` instead: it is the `Commune-Event-Id` your receiver saw, which is why it is worth logging. Attempts appear shortly after they happen rather than instantly.\n\n`response_status` is what your endpoint answered, or `null` with `failure` saying why nothing did (`timeout` being the usual one). The body your endpoint answered with is not returned; the portal has it. This one is attempt 11, the last automatic try, so nothing will send it again unless you ask. Needs `sending: read`."
      }
    },
    {
      "name": "5. Replay it once you have fixed the cause",
      "request": {
        "method": "POST",
        "header": [
          {
            "key": "Commune-Version",
            "value": "{{version}}"
          },
          {
            "key": "Idempotency-Key",
            "value": "{{$guid}}"
          }
        ],
        "url": {
          "raw": "{{baseUrl}}/delivery-attempts/{{attempt}}/replay",
          "host": [
            "{{baseUrl}}"
          ],
          "path": [
            "delivery-attempts",
            "{{attempt}}",
            "replay"
          ]
        },
        "description": "This delivers the same event (same `id`, same body) to the same destination once more, the same as the retry button in the portal. The attempt you name is left as it was; the new one appears in the log shortly afterwards with `manual: true`, so filter by the `event_id` in the answer to find it. It is a write, so send an `Idempotency-Key`, and it needs `sending: write`.\n\nReplay after you have fixed whatever made your endpoint fail, not during an outage: automatic retries already cover that, and replaying something your receiver did process is a duplicate it has to absorb (your dedupe on `id` does). A disabled destination answers `422`; switch it back on in the portal first."
      }
    }
  ]
}
