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.
Before you start
Section titled “Before you start”- 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.
Send the notifications
Section titled “Send the notifications”-
When the run starts, post the root message with
POST /bots/v1/messages. Describe the run: operation, target, run number and a link. Keepmessage_idfrom the response.{"chat_id": 1234,"content": "**Deploy** · production · [run 42](https://example.com/runs/42)","content_type": "MD"} -
Create the status reply with
POST /bots/v1/thread-replies. Use the root’smessage_idasparent_message_id, and send"content_type": "MD"and an explicit"status": "GN". Keepreply_idfrom the response.{"chat_id": 1234,"parent_message_id": 5678,"content": "Deploying…","content_type": "MD","status": "GN"} -
When the stage changes, send the full new text to
POST /bots/v1/thread-replies/streamwith"status": "GN". Replace the text rather than appending a line for every command. -
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:
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.
What DonutChat does
Section titled “What DonutChat does”The iPhone, Android and web apps show a status line under the reply, and every app marks the root message’s thread link:
| Reply status | Status line | Thread link |
|---|---|---|
GN | Working… or the stage you report, then Taking longer than usual… after 30 seconds without an update | 👀 |
OK | Finished · time | 👍 until read |
FL | Response 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 highersequence, 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
GNor when it ends asFL. Finishing withOKnotifies only people who follow the thread. - A
GNupdate afterOKorFLis ignored. SendingOKorFLagain 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
GNas interrupted. Your nextOKorFLupdate still replaces it.
Keep the reply accurate
Section titled “Keep the reply accurate”- 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
FLand 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.
FLsends 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
|| trueor 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.
Localized status text
Section titled “Localized status text”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.
en | es | fr | ja | ko |
|---|---|---|---|---|
| Queued | En espera | En attente | 待機中 | 대기 중 |
| Building… | Compilando… | Compilation en cours… | ビルド中… | 빌드 중… |
| Build complete | Compilación completada | Compilation terminée | ビルド完了 | 빌드 완료 |
| Build failed | Error en la compilación | Échec de la compilation | ビルド失敗 | 빌드 실패 |
| Deploying… | Desplegando… | Déploiement en cours… | デプロイ中… | 배포 중… |
| Deployment complete | Despliegue completado | Déploiement terminé | デプロイ完了 | 배포 완료 |
| Deployment failed | Error en el despliegue | Échec du déploiement | デプロイ失敗 | 배포 실패 |
| Canceled | Cancelado | Annulé | キャンセル済み | 취소됨 |
| Skipped | Omitido | Ignoré | スキップ | 건너뜀 |
Translate failure and cancellation explanations too. Keep IDs, status values and keys the same in every language.