Skip to content

Notifications ​

Turbine can send human-readable notifications to services like Slack, Discord, Telegram, and email when workflows complete. Powered by Shoutrrr, configured via the pt_alert_channels collection, manageable from the dashboard under Settings > Notifications.

For programmatic integrations with structured JSON payloads, see Webhooks.

Schema ​

Channels live in the pt_alert_channels collection:

FieldTypeDescription
namestringFriendly label, e.g. "Slack #alerts" (required)
urlstringShoutrrr URL (required)
eventsjsonArray of event names to subscribe to (required)
enabledboolWhether the channel is active

Supported Services ​

ServiceURL Format
Slackslack://xoxb:token@channel
Discorddiscord://token@webhookid
Telegramtelegram://token@telegram?chats=@channel
Email (SMTP)smtp://user:pass@host:port/?to=recipient
Teamsteams://token
Gotifygotify://host/token
Ntfyntfy://topic
Pushoverpushover://token:apitoken@user
Matrixmatrix://user:pass@host
Generic webhookgeneric+https://example.com/endpoint

See the Shoutrrr docs for the full list of supported services and their URL formats.

Events ​

Same events as Webhooks:

EventFires when
workflow.SUCCESSWorkflow completed successfully
workflow.ERRORWorkflow failed with an error
workflow.CANCELLEDWorkflow was cancelled
workflow.WAITING_FOR_APPROVALWorkflow is paused at an approval gate
workflow.MAX_RECOVERY_ATTEMPTS_EXCEEDEDWorkflow exceeded max recovery attempts (dead letter)
workflow.*Any of the above events

Message Format ​

Notifications are plain-text messages:

[Turbine] Workflow "orderProcessing" (abc123) completed successfully
[Turbine] Workflow "orderProcessing" (abc123) failed: connection refused
[Turbine] Workflow "orderProcessing" (abc123) cancelled
[Turbine] Workflow "orderProcessing" (abc123) is waiting for approval
[Turbine] Workflow "orderProcessing" (abc123) exceeded max recovery attempts

Sending Custom Messages ​

You can send an ad-hoc message to a configured channel from Go code, in addition to the automatic event-driven dispatch.

From a step (or any code that has the step's context.Context):

go
turbine.Register(rt, "nightlyImport", func(ctx turbine.Context) (any, error) {
    rows, err := turbine.Do(ctx, func(ctx context.Context) (int, error) {
        rows := importRows(ctx)
        if rows < 100 {
            if err := turbine.SendNotification(ctx, "ops-alerts",
                fmt.Sprintf("Unusual row count in nightly import: %d", rows)); err != nil {
                turbine.LoggerFrom(ctx).Warn("notification failed", "error", err)
            }
        }
        return rows, nil
    })
    return rows, err
})

Or directly on the runtime, from host code (HTTP handlers, cron jobs, etc.):

go
if err := rt.SendNotification("ops-alerts", "Backfill finished"); err != nil {
    log.Printf("notification failed: %v", err)
}

Channels are looked up by their name field. Disabled channels are a silent no-op, calling SendNotification against a disabled channel returns nil without sending, matching the event-driven dispatch behavior. Toggling a channel off mutes both manual and automatic sends. If multiple channels share the same name, the first match is used, keep names unique. Unlike event-driven dispatch, this call is synchronous and returns the underlying delivery error, so the caller can decide whether to log, retry, or surface it.

Testing ​

Each channel has a Test button in the dashboard that sends a test notification to verify the URL is configured correctly.

Delivery ​

WARNING

Notifications are fire-and-forget. Failed deliveries are logged but not retried. For critical alerting, consider using a service with built-in retry like a webhook endpoint.

Notifications are dispatched asynchronously and don't block workflow completion.

Dashboard ​

Notifications page

Security ​

Notification URLs contain embedded credentials (tokens, passwords). URLs are masked when read through the API, only the service scheme is visible (e.g. slack://***). The full URL is stored in the database and used only for dispatch.