IAnswerable

Send Useful Alerts to Slack

05 Aug 2026

An alerts channel that fires on every deploy, every warning, and every retry gets muted within a week, and then it’s not an alerts channel — it’s a channel nobody reads until something’s actually on fire and three days too late. Build for signal, not for logging everything you technically could.

The basic webhook

An incoming webhook is the fastest path — no bot, no OAuth flow, just a URL you POST JSON to:

await fetch(process.env.SLACK_WEBHOOK_URL, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ text: `🔴 Payment webhook failed for order ${orderId}` }),
})

That’s enough for a first version. It gets unreadable fast once you have more than a couple of alert types, because a wall of plain-text messages in a channel gives you nothing to scan.

Format with Block Kit

Block Kit gives you structured messages — fields, color-coded sections, buttons — instead of a text blob. This is what makes an alerts channel scannable at 2am instead of something you have to read line by line to parse:

await fetch(process.env.SLACK_WEBHOOK_URL, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    blocks: [
      {
        type: 'section',
        text: { type: 'mrkdwn', text: `*Payment webhook failed*\nOrder \`${orderId}\`` },
      },
      {
        type: 'context',
        elements: [{ type: 'mrkdwn', text: `Stripe error: ${errorCode} • <${dashboardUrl}|View in dashboard>` }],
      },
    ],
  }),
})

A context block for metadata (error codes, links, timestamps) keeps the main message short and puts the supporting detail where it’s available but not shouting for attention.

Thread follow-ups, don’t spam new messages

When the same issue fires repeatedly — a flaky third-party API retrying every minute — don’t post a new top-level message each time. Reply in a thread on the original alert, so the channel shows one line per distinct incident instead of fifty lines for one ongoing problem:

const { ts, channel } = await postAlert(initialMessage)
await threadStore.set(alertKey, { ts, channel })

// on repeat:
const thread = await threadStore.get(alertKey)
if (thread) {
  await fetch('https://slack.com/api/chat.postMessage', {
    method: 'POST',
    headers: { Authorization: `Bearer ${process.env.SLACK_BOT_TOKEN}`, 'Content-Type': 'application/json' },
    body: JSON.stringify({ channel: thread.channel, thread_ts: thread.ts, text: 'Still failing, 4th retry.' }),
  })
}

This needs the bot token API (chat.postMessage), not the incoming webhook, since replying in a thread requires referencing the original message’s timestamp — a plain webhook can’t do that.

Avoid alert fatigue on purpose

The test that matters

Ask: if this fires at 3am, does the on-call person need to wake up for it? If not, it doesn’t belong in the channel that pages people — route it to a lower-priority channel or a daily digest instead. The value of a Slack alerts channel is entirely a function of how much you can trust that everything in it is worth acting on. One noisy alert type erodes that trust for every other alert sharing the channel.