New to Kestra?
Use blueprints to kickstart your first workflows.
Audit an Azure Service Bus dead-letter sub-queue on a schedule and alert Slack with the real message count found.
A message that exhausts MaxDeliveryCount or expires past DefaultMessageTimeToLive
does not vanish, Service Bus moves it to the queue's own dead-letter sub-queue and leaves
it there, invisible to anything reading the main queue, until someone happens to look. A
growing dead-letter queue means messages a downstream consumer could not process, exactly
the kind of silent failure that only shows up in a postmortem. This blueprint peeks the
dead-letter sub-queue on a schedule, counts what is actually there, and alerts Slack with
the real number, without draining or altering a single message.
route_connection_mode (io.kestra.plugin.core.flow.If) picks the connection based on
use_emulator. Both branches run io.kestra.plugin.azure.servicebus.Consume against
queue_name with subQueue: DEAD_LETTER_QUEUE, with allowFailure: true so a bad
credential or a renamed queue does not stop the flow before it can be classified as
PROBE_FAILED below. Consume defaults to PEEK_LOCK receive mode and never calls
complete() on a message, so every message it reads returns to the dead-letter
sub-queue once its lock expires, this probe never drains the backlog it is reporting
on. Both branches also set tenantId to a placeholder GUID: Consume inherits this
field from AbstractAzureIdentityConnection (in the separate plugin-azure-lib
repository), which marks it @NotNull unconditionally, even though applyAuth() only
reads tenantId when connectionString is absent, so it is required by schema but
never actually used here.classify_dlq_status (io.kestra.plugin.core.debug.Return) reads whichever branch's
count exists (guarded with is defined/is not defined, since exactly one branch
ran) and returns PROBE_FAILED when neither branch returned at all,
violation_severity (CRITICAL or WARNING) when the count meets
violation_count_critical_threshold, HEALTHY otherwise.route_by_dlq_status (io.kestra.plugin.core.flow.Switch) branches on that status:
HEALTHY logs it, WARNING, CRITICAL, and PROBE_FAILED each alert Slack with the
real count that triggered it, never an invented number.log_audit_summary always runs last and logs the final classification.errors block posts to Slack if the flow itself fails outside the gate logic above.PEEK_LOCK with no complete() call means the
dead-letter queue is exactly as full after this flow runs as before.Consume against
the DEAD_LETTER_QUEUE sub-queue, with both the emulator connection string and a real
Service Bus connection string.Service Bus itself has no built-in alert for "the dead-letter queue is not empty", that
requires wiring a separate Azure Monitor alert rule on a metric. Kestra adds the schedule,
the threshold and severity policy, and a Slack alert built from the real count Consume
itself returns, all as one declarative YAML file you can version and review like code,
runnable against the official emulator before it ever touches a real namespace.
For local testing, the official Azure Service Bus emulator, no Azure account needed.
Save this as config.json:
{
"UserConfig": {
"Namespaces": [
{
"Name": "sbemulatorns",
"Queues": [
{
"Name": "queue.1",
"Properties": {
"DeadLetteringOnMessageExpiration": false,
"DefaultMessageTimeToLive": "PT1H",
"LockDuration": "PT1M",
"MaxDeliveryCount": 3,
"RequiresDuplicateDetection": false,
"RequiresSession": false
}
}
]
}
],
"Logging": { "Type": "File" }
}
}
Then run, with CONFIG_PATH pointed at that file and ACCEPT_EULA=Y after reading the
emulator's own EULA:
docker run -d --name servicebus-emulator -p 5672:5672 -p 5300:5300 \\
-v "$CONFIG_PATH:/ServiceBus_Emulator/ConfigFiles/Config.json" \\
-e ACCEPT_EULA=Y -e MSSQL_SA_PASSWORD="<a-strong-password>" -e SQL_SERVER=mssql \\
mcr.microsoft.com/azure-messaging/servicebus-emulator:latest
The emulator also needs its SQL Server Linux dependency reachable as mssql, the
official docs' docker-compose.yaml wires this correctly, a bare docker run of the
emulator alone is not enough, see the Links section. Port 5300 does not collide with
Kestra's own default UI port 8080.
Messages only reach the dead-letter sub-queue when something actually dead-letters
them, exceeding MaxDeliveryCount on a locked, never-completed message is the simplest
way to produce one for testing.
For real Service Bus, a connection string with Listen claim on the queue (a shared
access policy's primary or secondary key from the Azure portal or az servicebus queue authorization-rule keys list). Consume also exposes tenantId/clientId/
clientSecret/pemCertificate for Azure AD service-principal auth (inherited from
AbstractAzureIdentityConnection), unused by this blueprint since both branches
authenticate with connectionString instead.
A Slack incoming webhook for alerts.
SERVICEBUS_CONNECTION_STRING: used by probe_dlq_live only, not needed when
use_emulator is true.
SLACK_WEBHOOK: Slack incoming webhook URL used by the alert tasks and the errors
block.
In Kestra OSS, set these as base64-encoded environment variables, for example
SECRET_SERVICEBUS_CONNECTION_STRING, SECRET_SLACK_WEBHOOK.
| Input | Type | Default | Allowed values |
|---|---|---|---|
queue_name |
STRING | queue.1 |
any Service Bus queue name |
max_messages |
INT | 50 |
any positive integer |
max_receive_duration |
STRING | PT10S |
any ISO-8601 duration |
violation_count_critical_threshold |
INT | 1 |
any non-negative integer |
violation_severity |
SELECT | critical |
critical, warning |
use_emulator |
BOOL | true |
true, false |
emulator_connection_string |
STRING | see description | the emulator's own fixed connection string |
config.json above.queue.1 that are guaranteed to dead-letter, for example by
receiving and abandoning the same message more than MaxDeliveryCount times.SLACK_WEBHOOK secret.CRITICAL.use_emulator: false and add the SERVICEBUS_CONNECTION_STRING secret to point
this at a real queue.every_fifteen_minutes trigger.Consume with topicName/subscriptionName instead of queueName to
audit a topic subscription's dead-letter sub-queue the same way, queues and
subscriptions both have one.io.kestra.plugin.core.flow.Loop to
audit every queue in a namespace from one flow.DeadLetterReason and DeadLetterErrorDescription application
properties, included in the stored message body, to group the Slack alert by why
messages are dead-lettering, not only how many.count reflects messages this probe actually pulled within max_messages and
max_receive_duration, not necessarily the sub-queue's total depth if it holds more
than max_messages, raise both for a more complete count on a very backed-up queue.PEEK_LOCK still locks each peeked message for its LockDuration, other consumers
cannot process it during that window, a very short LockDuration combined with a slow
max_receive_duration can briefly starve a legitimate dead-letter reprocessing job
running at the same time.DeadLetteringOnMessageExpiration: false in the sample above does
not disable dead-lettering entirely, MaxDeliveryCount: 3 still dead-letters a message
that exhausts its delivery attempts.tenantId is set to a placeholder GUID on both Consume tasks and is not a real Azure
AD tenant. AbstractAzureIdentityConnection marks it @NotNull unconditionally, so a
flow using connectionString auth still fails schema validation without it, even though
applyAuth() never reads it in that case, verified directly from the plugin's source.