New to Kestra?
Use blueprints to kickstart your first workflows.
Publish incident status updates to Bluesky with Kestra. An ops webhook proposes the post, a human sign-off gate approves it, and Discord logs the outcome.
During an incident, the public status feed is the one channel where automation must not outrun judgment. A wrong or premature post erodes exactly the trust the feed exists to protect. This blueprint automates everything except the decision: incident tooling POSTs a proposed status update to a Kestra webhook, io.kestra.plugin.core.flow.If checks a boolean approval flag, and only an approved draft reaches io.kestra.plugin.bluesky.BlueskyExecution and the public feed. An unapproved draft fails the execution through io.kestra.plugin.core.execution.Fail, loudly and visibly, instead of being quietly dropped.
Bluesky increasingly serves as the community-facing status channel for developer products, where affected users actually follow along, and this flow treats it with the care a public channel deserves: posts are effectively irreversible once seen, so the default answer is always no. Each published skeet also carries its own provenance, opening with the execution state and flow id and closing with a link to the execution, so every update visibly comes from the approved status pipeline rather than a rushed manual post.
incident_webhook trigger (io.kestra.plugin.core.trigger.Webhook) receives status_message and approved from the incident tooling, typically wired to a confirm button an incident commander clicks.signoff_gate (io.kestra.plugin.core.flow.If) evaluates {{ (trigger.body.approved ?? inputs.approved) == true }}. Manual executions use the approved flow input, which defaults to false, the safe state.post_status (io.kestra.plugin.bluesky.BlueskyExecution) posts the update with the approved text as customMessage. The plugin always renders its built-in execution template: the post opens with the execution state and flow id, for example RUNNING: company.team/bluesky-incident-status-post, then the approved message, and closes with a link to the execution in the Kestra UI. The 300-grapheme limit applies to that whole rendered post.log_publication writes the outcome to the Discord incident channel, adding the public post to the incident timeline.refuse_unapproved (io.kestra.plugin.core.execution.Fail) fails the execution with an explicit message, and the errors block posts a Discord alert so the team knows the public feed was not updated.Approval gates bolted onto chat bots tend to decay into rubber stamps because nothing enforces them. In Kestra the gate is control flow: the posting task is structurally unreachable without the flag, the refusal is a failed execution in the history, and the app password never leaves the secret store. Every status update, posted or refused, is auditable after the retro.
BLUESKY_IDENTIFIER: the Bluesky handle of the status account, for example yourstatus.bsky.social.BLUESKY_APP_PASSWORD: an app password generated in Bluesky settings.DISCORD_WEBHOOK_URL: Discord incoming webhook URL for the incident channel.approved left at false and confirm it fails with the refusal message and a Discord alert.approved set to true and a test status_message, and check the skeet on the status account.phase field in the webhook body and branching the wording on it.post_status.