Skip to content
donutchat.com

Build and deploy notifications

Report each build or deployment run with one bot message and one thread reply per attempt, updated from start to the verified result, instead of posting a message for every stage.

This needs an API bot. Incoming webhooks post plain top-level messages and can’t update them, so use a webhook only if a single message at the end of a run is enough.

  • An API bot, with its token stored as a secret in your CI system.
  • The bot added to the chat that gets the notifications.
  • The chat’s ID. In the iPhone, Android or web app, open the chat’s info, select Manage chat, then select Chat ID to copy it. Your bot can also list its chats with GET /bots/v1/chats.
  1. When the run starts, post the root message with POST /bots/v1/messages. Describe the run: operation, target, run number and a link. Keep message_id from the response.

    {
    "chat_id": 1234,
    "content": "**Deploy** · production · [run 42](https://example.com/runs/42)",
    "content_type": "MD"
    }
  2. Create the status reply with POST /bots/v1/thread-replies. Use the root’s message_id as parent_message_id, and send "content_type": "MD" and an explicit "status": "GN". Keep reply_id from the response.

    {
    "chat_id": 1234,
    "parent_message_id": 5678,
    "content": "Deploying…",
    "content_type": "MD",
    "status": "GN"
    }
  3. When the stage changes, send the full new text to POST /bots/v1/thread-replies/stream with "status": "GN". Replace the text rather than appending a line for every command.

  4. When the run ends, send the final text to the same endpoint with "status": "OK" after a verified success, or "FL" after a failure or cancellation.

    {
    "chat_id": 1234,
    "parent_message_id": 5678,
    "reply_id": 9876,
    "content": "Deployment complete · production",
    "status": "OK"
    }

Every request needs the header Authorization: Bearer followed by the token. Always send status to the stream endpoint: an update without it finishes the reply as OK.

Read IDs from responses with a tool that keeps them exact. Chat IDs, and some reply IDs, are too large for tools that store JSON numbers as floating point. From a shell step:

Terminal window
reply_id=$(curl -sS --max-time 10 https://api.donutchat.com/bots/v1/thread-replies \
-H "Authorization: Bearer $DONUT_BOT_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"chat_id\": $CHAT_ID, \"parent_message_id\": $ROOT_ID, \"content\": \"Deploying…\", \"content_type\": \"MD\", \"status\": \"GN\"}" \
| python3 -c 'import json, sys; print(json.load(sys.stdin).get("reply_id", ""))') || reply_id=""

Read the root’s message_id the same way. An empty ID means the request failed, so skip the updates that need it.

Thread replies and streaming lists every field and error.

The iPhone, Android and web apps show a status line under the reply, and every app marks the root message’s thread link:

Reply statusStatus lineThread link
GNWorking… or the stage you report, then Taking longer than usual… after 30 seconds without an update👀
OKFinished · time👍 until read
FLResponse interrupted⚠️ until read
  • Taking longer than usual… doesn’t mean the run failed. New text or a progress report starts the 30 seconds again; sending the same text and status doesn’t.
  • To show the current stage, report it with activity, for example "action": "tests" with "status": "active" to show Running tests…. During a long step, report it again about every 20 seconds with a higher sequence, so the line doesn’t go stale.
  • The desktop app shows the reply text without the status line, so always state the outcome in the text.
  • The root message notifies chat members like any bot message. The thread reply sends no push notification while it’s GN or when it ends as FL. Finishing with OK notifies only people who follow the thread.
  • A GN update after OK or FL is ignored. Sending OK or FL again replaces the final text, and readers see Updated · time.
  • Updates aren’t checked for order: the last accepted update wins. Overlapping updates to the same reply can fail with stream_failed.
  • Requests have no idempotency key. A create request that timed out may still have posted.
  • During maintenance, DonutChat can mark a reply that’s still GN as interrupted. Your next OK or FL update still replaces it.
  • One writer per reply. Parallel jobs report to one job that owns the reply, and its updates never overlap.
  • One reply per attempt. Store the root and reply IDs with the run and attempt, for example as job outputs. A rerun gets its own reply. Never update another attempt’s reply, and never guess an ID.
  • Finish on every path. Send the final update after success, failure and cancellation, for example from a step that runs even when the job fails. If the runner dies, a separate cleanup job with the stored IDs can finish the reply from the CI system’s real result. Until then, the result is unknown: say so rather than guessing.
  • Report only what you verified. A passing build isn’t a deployment, and a deployment isn’t a store release. Mark a platform that didn’t run as skipped.
  • Report cancellation as a failure. There’s no canceled status: finish with FL and say “Canceled” in the text. Showing a status doesn’t cancel anything, so don’t offer a Stop control unless something actually cancels the run.
  • Alert people separately if needed. FL sends no push notification. If a failure must reach people right away, also post a short top-level message.
  • Keep notifications best effort. Use short timeouts, and run notification commands so a failure can’t fail the job, for example with || true or your CI system’s continue-on-error setting. Never let a DonutChat error rerun the build.
  • Don’t retry creation blindly. Retrying a create after a timeout can post twice. Retrying an update with the full text is safe.
  • Keep secrets out. Never put the token or raw logs in the text; link to the run instead.

Automation has no request to take a language from. Set a language for each chat or integration (en, es, fr, ja or ko), use English when none is set, and don’t guess from commit authors or runner settings. DonutChat’s own labels, such as Working…, follow each reader’s app language; the desktop app is in English.

enesfrjako
QueuedEn esperaEn attente待機中대기 중
Building…Compilando…Compilation en cours…ビルド中…빌드 중…
Build completeCompilación completadaCompilation terminéeビルド完了빌드 완료
Build failedError en la compilaciónÉchec de la compilationビルド失敗빌드 실패
Deploying…Desplegando…Déploiement en cours…デプロイ中…배포 중…
Deployment completeDespliegue completadoDéploiement terminéデプロイ完了배포 완료
Deployment failedError en el despliegueÉchec du déploiementデプロイ失敗배포 실패
CanceledCanceladoAnnuléキャンセル済み취소됨
SkippedOmitidoIgnoréスキップ건너뜀

Translate failure and cancellation explanations too. Keep IDs, status values and keys the same in every language.