---
title: "Message cards: what an mssgs bot message can show"
description: "Design bot messages for mssgs: titles, markdown, fields, images, status pills with diff stats, loader and thinking states, colours, and a live card builder."
canonical: https://docs.mss.gs/en/bots
language: en
---

# Design message cards

Everything a bot posts, from a webhook, a command reply or a button update, is a card. A good card says what happened in one glance: a coloured edge, a title, a status, the details underneath.

## What you can do

- **Show a status** A coloured pill like Open, Merged or Passing, with lines added and removed.

- **List the details** Label and value rows that members can copy in one tap.

- **Write in markdown** Bold, inline code, code blocks, quotes and checkboxes.

- **Show that you are working** A spinner while your bot thinks, and its reasoning behind a toggle afterwards.

In the app

#### Pull request #212 opened

Faster search in the channel list

#### Build #1847 passed

#### api.acme.com is down

Four cards as members see them. Each one is a few lines of JSON.

- [Anatomy](#anatomy)

- [All fields](#fields)

- [Status and diff stats](#status)

- [Loader and thinking](#ai)

- [System messages](#system)

- [Colours](#colors)

- [Build one](#try)

## Anatomy of a card

The parts of a card, top to bottom. Leave out what you do not need: a card with only a title is fine.

#### Pull request #212 opened

Faster search in the channel list

- **Header** "Message from" and a name: the webhook's name, or your community's name for a command reply. A reply can add a badge , such as the repository.

- **Title and status** The title , a link when you set title_url , with the status pill beside it.

- **Subtitle** A second bold line, sub_title .

- **Description** The body, in markdown.

- **Fields** Label and value rows, with a copy button.

- **Footer** The time, and lines added and removed if you send them.

```json
{
  "message_container": {
    "type": "embed_message",
    "badge": "acme/web",
    "color": "purple",
    "title": "Pull request #212 opened",
    "title_url": "https://github.com/acme/web/pull/212",
    "status": { "label": "Open", "color": "green", "icon": "pull_request" },
    "sub_title": "Faster search in the channel list",
    "description": "Search now runs **per keystroke** with a 120 ms debounce.",
    "fields": [
      { "field": "Author", "value": "maya" },
      { "field": "Reviewers", "value": "dani, sam" }
    ],
    "additions": 86,
    "deletions": 12,
    "files_changed": 3
  }
}
```

## All fields

These go in message_container . A card needs a description, or a loader. Fields marked "command replies" are dropped when you post through a webhook.

| Field | Type | What it does |
| --- | --- | --- |
| type | string | embed_message (the default) or system_message . |
| badge | string | A small chip after the name in the header, such as acme/web . Command replies |
| avatar_url | string | An image over the card's icon. |
| color | string | The edge colour. See colours below. |
| title | string | The bold first line. |
| title_url | string | Turns the title into a link. |
| sub_title | string | A second bold line under the title. |
| description | string | The body, in markdown. |
| fields | array | [{ "field": "…", "value": "…" }] : label and value rows. |
| image_url , image_base64 | string | An image on the card. |
| images | array | [{ "image_url": "…" }] : a gallery of several images. |
| status | object or string | A coloured pill next to the title. See below. Command replies |
| additions , deletions , files_changed | number | Diff stats in the footer. Command replies |
| loader , loader_text , loader_sub_text | boolean, string | A spinner instead of the body. |
| thinking | string | Reasoning behind a **Show thinking** toggle. Command replies |

## Status and diff stats

A status pill tells the story before anyone reads the text. It sits next to the title, or in the footer when there is no title; diff stats show next to the time. Both work in command replies, and the built-in GitHub integration uses them. A webhook drops them.

#### Pull request #212 merged

#### Build failed on main

#### maya pushed to main

```json
{
  "message_container": {
    "color": "red",
    "title": "Build failed on main",
    "status": { "label": "Failing", "color": "red" },
    "description": "`search.test.js`: 2 of 212 tests failed."
  }
}
```

| Field | Type | What it does |
| --- | --- | --- |
| status | object or string | A bare string is the label: "status": "Open" . |
| status.label | string | The pill text. Without it there is no pill. |
| status.color | string | green , purple , red , orange , yellow , blue or gray . |
| status.icon | string | An optional icon from the list below. |
| additions | number | Lines added, shown as green +86 . |
| deletions | number | Lines removed, shown as red -12 . |
| files_changed | number | Files touched, shown as 3 files . |

### Icons

| Value | Icon | Typical use |
| --- | --- | --- |
| pull_request | git-pull-request | A pull request opened |
| pull_request_closed | git-pull-request-closed | Closed without merging |
| merge , merged | git-merge | Merged |
| commit | git-commit | A pushed commit |
| issue | circle-dot | An issue opened |
| issue_closed | circle-check | An issue closed |
| check | circle-check | Tests passed, a job succeeded |

### A mapping that works for GitHub

The built-in GitHub integration uses these; copy them for your own tools.

| Event | Label | Colour | Icon |
| --- | --- | --- | --- |
| Pull request opened | Open | green | pull_request |
| Draft | Draft | gray | pull_request |
| Merged | Merged | purple | merged |
| Closed unmerged | Closed | red | pull_request_closed |
| Issue opened | Open | green | issue |
| Issue closed | Closed | purple | issue_closed |
| Commit pushed | Commit | gray | commit |
| Tests passed | Passing | green | check |
| Tests failed | Failing | red | none |

## Loader and thinking

For anything that takes a moment, like an AI answer or a long job, post a card with a spinner first, then replace it with the result. The loader works from webhooks and command replies. In a command reply you can also put the model's reasoning in thinking : members see a **Show thinking** toggle instead of a wall of text.

First: the loader

maya: when is the standup?

Then: the answer, reasoning folded away

```json
{
  "message_container": {
    "type": "embed_message",
    "color": "blue",
    "loader": true,
    "loader_text": "Thinking…",
    "loader_sub_text": "Reading the last 50 messages"
  }
}
```

```json
{
  "message_container": {
    "type": "embed_message",
    "color": "blue",
    "sub_title": "maya: when is the standup?",
    "description": "Standup is at **09:30**, in #daily.",
    "thinking": "Checked the pinned messages and the recurring event in #daily…"
  }
}
```

| Field | Type | What it does |
| --- | --- | --- |
| loader | boolean | true shows the spinner instead of the body. |
| loader_text | string | The line next to the spinner, such as "Thinking…". |
| loader_sub_text | string | A smaller line under it. |
| thinking | string | Folded reasoning under the description, in markdown. |

To swap the loader for the answer, update the message with a new card that leaves out loader . How is on [live updates](https://docs.mss.gs/en/live-updates).

## Long descriptions

A description can be up to 50,000 bytes. Past the first 1,000, members see the start and a **Show more** button that loads the rest, so a long report does not flood the channel.

## System messages

Set "type": "system_message" for a notice rather than a bot post: maintenance windows, policy changes, anything that speaks for the community itself. It takes the same fields and buttons.

```json
{
  "message_container": {
    "type": "system_message",
    "color": "orange",
    "title": "Maintenance tonight",
    "description": "The build servers are down from 22:00 to 23:00."
  }
}
```

## Colours

The edge colour is the fastest signal on a card. Use the same colour for the same kind of news, every time.

| Colour | Use it for |
| --- | --- |
| green | Success: passed, deployed, done |
| red | Failure: failed, down, rejected |
| orange | A warning that needs a look |
| yellow | Waiting on someone: approvals, questions |
| blue | Information, the default |
| purple | Code events, or something special |

## Build your embed

Edit the fields or the JSON payload. Both stay in sync. Watch the message render exactly as it will in a channel. This is the real webhook body; copy it when it looks right.

## Keep building
