Skip to content

Formatting Slack Messages

Slack does not render HTML — send it <b>CRITICAL</b> and readers see the literal tags. Instead it has two native formats, both fully supported by pg_relay because the webhook transport delivers the message payload verbatim: mrkdwn, Slack's markdown dialect, for formatting inside any text; and Block Kit, structured JSON layout for messages with real visual structure — headers, aligned fields, severity colors, buttons. This chapter is the reference for both, written around the alerting use case: a status, details, fix steps, and a link to a complete guide.

mrkdwn — formatting inside text

Anything that ends up in a Slack text field (or a mrkdwn text object in Block Kit) understands:

You write Slack shows
*bold* bold
_italic_ italic
~strike~ ~~strike~~
`code` code (inline, monospace, highlighted)
```multi-line``` a code block — ideal for diagnostic SQL
> quoted line an indented quote bar
:rotating_light: :warning: :white_check_mark: 🚨 ⚠️ ✅
a literal newline a line break

Three characters are special and must be escaped when they appear as content: & as &amp;, < as &lt;, > as &gt; — because <...> is how Slack encodes links (below). There is no automatic list rendering; write numbered steps as plain 1. / 2. lines, which reads perfectly well.

You write Slack shows
https://example.com/guide the bare URL, auto-linked
<https://example.com/guide> the same, explicitly
<https://example.com/guide\|Fix procedure> Fix procedure as a clickable label

The labelled form is the one to reach for in alerts: a long catalog URL stays out of the visual flow, and the label can carry the check id so the message reads like documentation. Anchors survive intact — a deep link such as

<https://pg-health-framework.pebbleit.com.au/latest/catalog/checks/c07-toast-corruption-detection/#pghf07-005-orphaned-toast-tables|PGHF07-005 — Orphaned TOAST tables (fix procedure)>

lands the reader on that exact section of the guide, not the top of the page. A useful pattern for operational alerts is both forms together: the labelled link for clicking, and the bare URL on the next line for copy/paste into a ticket.

Slack may expand a bare link into a preview card (an "unfurl"). For alert traffic that bloats the channel — set "unfurl_links": false in the message payload to keep links clickable but compact.

Block Kit — structured layout

Block Kit is a blocks array in the chat.postMessage payload. For alerts, the essential blocks:

Block Renders as Alert use
header large bold title bar (plain text + emoji, ≤150 chars) 🚨 PG Healthcheck — CRITICAL
section with fields up to 10 short mrkdwn cells in two aligned columns Alert / Status / Check / Database
section with text a paragraph of mrkdwn (≤3000 chars) the details, the fix steps
divider a horizontal rule separating summary from detail
actions with button clickable buttons; a url button opens a link; style primary (green) or danger (red) Open fix procedure → the guide's deep link
context small muted text/icons alert id, source system, timestamp

Two framing details complete an alert:

  • The severity sidebar — the colored bar down the message's left edge — comes from wrapping the blocks in an attachments entry with a color: for example #d61111 for CRITICAL, #e8a317 for WARNING, #2eb67d for OK.
  • Always set a top-level text with a one-line summary. It is the fallback shown in desktop and mobile push notifications, and anywhere blocks cannot render.

A complete healthcheck alert

This payload — delivered verbatim by the Processor — renders with a red sidebar, a header bar, two columns of fields, a divider, detail sections with a SQL code block, a red button into the fix guide, and a muted footer:

{
  "channel": "C0XXXXXXX",
  "text": "CRITICAL: Orphaned TOAST tables on relay_dev (PGHF07-005)",
  "unfurl_links": false,
  "attachments": [{
    "color": "#d61111",
    "blocks": [
      {"type": "header",
       "text": {"type": "plain_text", "text": "🚨 PG Healthcheck — CRITICAL", "emoji": true}},
      {"type": "section", "fields": [
        {"type": "mrkdwn", "text": "*Alert:*\nOrphaned TOAST tables"},
        {"type": "mrkdwn", "text": "*Status:*\n`CRITICAL`"},
        {"type": "mrkdwn", "text": "*Check:*\n`PGHF07-005`"},
        {"type": "mrkdwn", "text": "*Database:*\n`relay_dev`"}]},
      {"type": "divider"},
      {"type": "section", "text": {"type": "mrkdwn",
       "text": "*Details*\n2 TOAST tables have no owning relation — debris from an interrupted `DROP TABLE`. They consume disk and are scanned by autovacuum for no benefit."}},
      {"type": "section", "text": {"type": "mrkdwn",
       "text": "*How to fix*\n1.  Identify the orphans:\n```SELECT c.oid::regclass FROM pg_class c WHERE c.relkind = 't' AND NOT EXISTS (SELECT 1 FROM pg_class t WHERE t.reltoastrelid = c.oid);```\n2.  Follow the catalog's drop procedure"}},
      {"type": "actions", "elements": [
        {"type": "button", "style": "danger",
         "text": {"type": "plain_text", "text": "Open fix procedure"},
         "url": "https://pg-health-framework.pebbleit.com.au/latest/catalog/checks/c07-toast-corruption-detection/#pghf07-005-orphaned-toast-tables"}]},
      {"type": "context", "elements": [
        {"type": "mrkdwn", "text": "Alert id `HC-2215`  •  pg-health-framework  •  2026-08-22 05:20 UTC"}]}
    ]
  }]
}

Slack's structural limits are generous for alerting: up to 50 blocks per message, 3000 characters per text object, 10 fields per section. If a message ever needs more than that, it has stopped being an alert — link to the complete guide instead, which is the point of the button.

Where the formatting happens

The Processor never composes or interprets any of this — it posts the notifier-rendered message verbatim, which is why every Slack feature works without pg_relay changes. Composition lives in pg_relay_notifier: plain body_text (with any mrkdwn in it) becomes the message text, and its Slack provider renders or passes through Block Kit payloads for the structured layouts above — see its own documentation for the composition API.


Continue to Mattermost — the same recipe pattern pointed at a self-hosted, Slack-compatible server that works with no internet connection at all.