Azure Servicebus Py
MicrosoftOfficialCreated by Microsoft
- Skill
- Tool integration
- Automation and integrations
- MIT
- Low risk
- Has scripts
AutoGineer summary
Written by AutoGineer from the skill’s own files, not by the author
The azure-servicebus-py skill provides the Python SDK for interacting with Azure Service Bus. It enables developers to implement enterprise messaging patterns, including message queuing, publish-subscribe topics, and session-based ordered processing. The skill emphasizes secure authentication via DefaultAzureCredential and provides patterns for message settlement, dead-letter queue management, and high-throughput batch operations.
What it does
- Connects to Azure Service Bus namespaces for messaging operations.
- Supports sending and receiving messages via queues and topics.
- Implements message settlement actions like complete, abandon, and dead-letter.
- Provides support for FIFO message ordering using sessions.
- Enables scheduling of messages for future delivery.
- Facilitates batch message processing for improved throughput.
When to use it
- When building cloud-native applications requiring reliable asynchronous communication.
- When implementing pub/sub architectures using Azure Service Bus topics.
- When needing to handle poison messages via dead-letter queues.
- When requiring ordered processing of related messages using sessions.
- When integrating Python applications with Azure messaging infrastructure.
Advantages
- Native support for Azure identity-based authentication.
- Provides both synchronous and asynchronous client implementations.
- Includes built-in support for message batching and session management.
- Offers granular control over message settlement and retry logic.
Usage examples
- “How do I send a batch of messages to an Azure Service Bus queue?”
- The agent provides a code snippet using ServiceBusClient and ServiceBusSender to create and send a batch of messages, including logic for handling batch size limits.
- “Show me how to process messages from a dead-letter queue.”
- The agent provides an example using ServiceBusSubQueue.DEAD_LETTER to initialize a receiver and iterate through messages, including accessing dead-letter reasons.
Install
Download the skill as a ZIP:
https://autogineer.com/agent-skills/microsoft/azure-servicebus-py/downloadUnzip it into the cross-client skills folder of your project:
mkdir -p .agents/skills && unzip azure-servicebus-py.zip -d .agents/skills
Documentation by Microsoft
Azure Service Bus SDK for Python
Enterprise messaging for reliable cloud communication with queues and pub/sub topics.
Installation
pip install azure-servicebus azure-identityEnvironment Variables
SERVICEBUS_FULLY_QUALIFIED_NAMESPACE=<namespace>.servicebus.windows.net # Required for all auth methods
SERVICEBUS_QUEUE_NAME=myqueue # Required for queue operations
SERVICEBUS_TOPIC_NAME=mytopic # Required for topic operations
SERVICEBUS_SUBSCRIPTION_NAME=mysubscription # Required for subscription operations
AZURE_TOKEN_CREDENTIALS=prod # Required only if DefaultAzureCredential is used in productionAuthentication & Lifecycle
🔑 Two rules apply to every code sample below:
Prefer
DefaultAzureCredential. It works locally (Azure CLI / VS Code / Developer CLI) and in Azure (managed identity, workload identity) with no code change. Avoid connection strings, account/API keys — they bypass Entra audit and rotation.
Local dev:
DefaultAzureCredentialworks as-is.Production: set
AZURE_TOKEN_CREDENTIALS=prod(orAZURE_TOKEN_CREDENTIALS=<specific_credential>) to constrain the credential chain to production-safe credentials.Wrap every client in a context manager so HTTP transports, sockets, and token caches are released deterministically:
Sync:
with <Client>(...) as client:Async:
async with <Client>(...) as client:andasync with DefaultAzureCredential() as credential:(fromazure.identity.aio)Snippets may abbreviate this setup, but production code should always follow both rules.
from azure.identity import DefaultAzureCredential, ManagedIdentityCredential
from azure.servicebus import ServiceBusClient
# Local dev: DefaultAzureCredential. Production: set AZURE_TOKEN_CREDENTIALS=prod or AZURE_TOKEN_CREDENTIALS=<specific_credential>
credential = DefaultAzureCredential(require_envvar=True)
# Or use a specific credential directly in production:
# See https://learn.microsoft.com/python/api/overview/azure/identity-readme?view=azure-python#credential-classes
# credential = ManagedIdentityCredential()
namespace = "<namespace>.servicebus.windows.net"
with ServiceBusClient(
fully_qualified_namespace=namespace,
credential=credential
) as client:
# Use client here (see following sections for operations)
...Client Types
| Client | Purpose | Get From |
|---|---|---|
ServiceBusClient | Connection management | Direct instantiation |
ServiceBusSender | Send messages | client.get_queue_sender() / get_topic_sender() |
ServiceBusReceiver | Receive messages | client.get_queue_receiver() / get_subscription_receiver() |
Send Messages (Async)
import asyncio
from azure.servicebus.aio import ServiceBusClient
from azure.servicebus import ServiceBusMessage
from azure.identity.aio import DefaultAzureCredential
async def send_messages():
credential = DefaultAzureCredential()
async with ServiceBusClient(
fully_qualified_namespace="<namespace>.servicebus.windows.net",
credential=credential
) as client:
sender = client.get_queue_sender(queue_name="myqueue")
async with sender:
# Single message
message = ServiceBusMessage("Hello, Service Bus!")
await sender.send_messages(message)
# Batch of messages
messages = [ServiceBusMessage(f"Message {i}") for i in range(10)]
await sender.send_messages(messages)
# Message batch (for size control)
batch = await sender.create_message_batch()
for i in range(100):
try:
batch.add_message(ServiceBusMessage(f"Batch message {i}"))
except ValueError: # Batch full
await sender.send_messages(batch)
batch = await sender.create_message_batch()
batch.add_message(ServiceBusMessage(f"Batch message {i}"))
await sender.send_messages(batch)
asyncio.run(send_messages())Receive Messages (Async)
async def receive_messages():
credential = DefaultAzureCredential()
async with ServiceBusClient(
fully_qualified_namespace="<namespace>.servicebus.windows.net",
credential=credential
) as client:
receiver = client.get_queue_receiver(queue_name="myqueue")
async with receiver:
# Receive batch
messages = await receiver.receive_messages(
max_message_count=10,
max_wait_time=5 # seconds
)
for msg in messages:
print(f"Received: {str(msg)}")
await receiver.complete_message(msg) # Remove from queue
asyncio.run(receive_messages())Receive Modes
| Mode | Behavior | Use Case |
|---|---|---|
PEEK_LOCK (default) | Message locked, must complete/abandon | Reliable processing |
RECEIVE_AND_DELETE | Removed immediately on receive | At-most-once delivery |
from azure.servicebus import ServiceBusReceiveMode
receiver = client.get_queue_receiver(
queue_name="myqueue",
receive_mode=ServiceBusReceiveMode.RECEIVE_AND_DELETE
)Message Settlement
async with receiver:
messages = await receiver.receive_messages(max_message_count=1)
for msg in messages:
try:
# Process message...
await receiver.complete_message(msg) # Success - remove from queue
except ProcessingError:
await receiver.abandon_message(msg) # Retry later
except PermanentError:
await receiver.dead_letter_message(
msg,
reason="ProcessingFailed",
error_description="Could not process"
)| Action | Effect |
|---|---|
complete_message() | Remove from queue (success) |
abandon_message() | Release lock, retry immediately |
dead_letter_message() | Move to dead-letter queue |
defer_message() | Set aside, receive by sequence number |
Topics and Subscriptions
# Send to topic
sender = client.get_topic_sender(topic_name="mytopic")
async with sender:
await sender.send_messages(ServiceBusMessage("Topic message"))
# Receive from subscription
receiver = client.get_subscription_receiver(
topic_name="mytopic",
subscription_name="mysubscription"
)
async with receiver:
messages = await receiver.receive_messages(max_message_count=10)Sessions (FIFO)
# Send with session
message = ServiceBusMessage("Session message")
message.session_id = "order-123"
await sender.send_messages(message)
# Receive from specific session
receiver = client.get_queue_receiver(
queue_name="session-queue",
session_id="order-123"
)
# Receive from next available session
from azure.servicebus import NEXT_AVAILABLE_SESSION
receiver = client.get_queue_receiver(
queue_name="session-queue",
session_id=NEXT_AVAILABLE_SESSION
)Scheduled Messages
from datetime import datetime, timedelta, timezone
message = ServiceBusMessage("Scheduled message")
scheduled_time = datetime.now(timezone.utc) + timedelta(minutes=10)
# Schedule message
sequence_number = await sender.schedule_messages(message, scheduled_time)
# Cancel scheduled message
await sender.cancel_scheduled_messages(sequence_number)Dead-Letter Queue
from azure.servicebus import ServiceBusSubQueue
# Receive from dead-letter queue
dlq_receiver = client.get_queue_receiver(
queue_name="myqueue",
sub_queue=ServiceBusSubQueue.DEAD_LETTER
)
async with dlq_receiver:
messages = await dlq_receiver.receive_messages(max_message_count=10)
for msg in messages:
print(f"Dead-lettered: {msg.dead_letter_reason}")
await dlq_receiver.complete_message(msg)Sync Client (for simple scripts)
from azure.servicebus import ServiceBusClient, ServiceBusMessage
from azure.identity import DefaultAzureCredential
with ServiceBusClient(
fully_qualified_namespace="<namespace>.servicebus.windows.net",
credential=DefaultAzureCredential()
) as client:
with client.get_queue_sender("myqueue") as sender:
sender.send_messages(ServiceBusMessage("Sync message"))
with client.get_queue_receiver("myqueue") as receiver:
for msg in receiver:
print(str(msg))
receiver.complete_message(msg)Best Practices
Pick sync OR async and stay consistent. Do not mix
azure.xxxsync clients withazure.xxx.aioasync clients in the same call path. Choose one mode per module.Always use context managers for clients and async credentials. Wrap every client in
with Client(...) as client:(sync) orasync with Client(...) as client:(async) for proper cleanup. For asyncDefaultAzureCredentialfromazure.identity.aio, also useasync with credential:so tokens and transports are cleaned up.Use
DefaultAzureCredentialfor portable auth across local dev and Azure (avoid connection strings / API keys when possible).Use async client for production workloads
Complete messages after successful processing
Use dead-letter queue for poison messages
Use sessions for ordered, FIFO processing
Use message batches for high-throughput scenarios
Set
max_wait_timeto avoid infinite blocking
Reference Files
| File | Contents |
|---|---|
| references/patterns.md | Competing consumers, sessions, retry patterns, request-response, transactions |
| references/dead-letter.md | DLQ handling, poison messages, reprocessing strategies |
| scripts/setup_servicebus.py | CLI for queue/topic/subscription management and DLQ monitoring |
Files
- ATTRIBUTION.mdAdded by AutoGineer968 B
- LICENSELicence file added by AutoGineer1.1 KiB
- SKILL.md10.1 KiB
- references/dead-letter.md13.8 KiB
- references/patterns.md13.6 KiB
- scripts/setup_servicebus.pyScript13.0 KiB
references/dead-letter.md
Dead-Letter Queue Reference
Handling poison messages and dead-letter queue processing in Azure Service Bus.
Dead-Letter Queue Overview
The dead-letter queue (DLQ) is a secondary sub-queue for messages that cannot be processed:
Main Queue: myqueue
└── Dead-Letter Queue: myqueue/$deadletterqueueWhy Messages Get Dead-Lettered
| Reason | Description |
|---|---|
MaxDeliveryCountExceeded | Message abandoned too many times |
HeaderSizeExceeded | Message headers too large |
TTLExpiration | Message expired before delivery |
SessionIdMismatch | Session ID doesn't match |
MessageSizeExceeded | Message body too large |
| Custom reason | Explicitly dead-lettered by application |
Receiving from Dead-Letter Queue
from azure.servicebus import ServiceBusSubQueue
from azure.servicebus.aio import ServiceBusClient
from azure.identity.aio import DefaultAzureCredential
async def receive_dead_letters(namespace: str, queue_name: str):
"""Receive messages from dead-letter queue."""
credential = DefaultAzureCredential()
async with ServiceBusClient(
fully_qualified_namespace=namespace,
credential=credential
) as client:
# Get dead-letter queue receiver
dlq_receiver = client.get_queue_receiver(
queue_name=queue_name,
sub_queue=ServiceBusSubQueue.DEAD_LETTER
)
async with dlq_receiver:
messages = await dlq_receiver.receive_messages(
max_message_count=10,
max_wait_time=5
)
for msg in messages:
print(f"Dead-letter message: {str(msg)}")
print(f" Reason: {msg.dead_letter_reason}")
print(f" Description: {msg.dead_letter_error_description}")
print(f" Enqueued: {msg.enqueued_time_utc}")
print(f" Delivery count: {msg.delivery_count}")
# Process or complete
await dlq_receiver.complete_message(msg)Explicit Dead-Lettering
Dead-letter messages programmatically for non-retryable errors:
async def process_with_dead_letter(receiver, msg):
"""Process message and dead-letter on permanent failures."""
try:
result = await process_message(msg)
await receiver.complete_message(msg)
return result
except ValidationError as e:
# Invalid message format - don't retry
await receiver.dead_letter_message(
msg,
reason="ValidationFailed",
error_description=f"Invalid format: {e}"
)
except DuplicateError as e:
# Already processed - dead-letter with context
await receiver.dead_letter_message(
msg,
reason="DuplicateDetected",
error_description=f"Message already processed: {e}"
)
except ExternalServiceUnavailable:
# Temporary - abandon for retry
await receiver.abandon_message(msg)
except Exception as e:
# Unknown error - dead-letter with full context
await receiver.dead_letter_message(
msg,
reason="UnhandledException",
error_description=f"{type(e).__name__}: {str(e)}"
)Dead-Letter Queue Processor
Automated processing of dead-lettered messages:
import json
from datetime import datetime, timezone
class DeadLetterProcessor:
"""Process and analyze dead-lettered messages."""
def __init__(self, client, queue_name: str):
self.client = client
self.queue_name = queue_name
async def process_dlq(self, handler_map: dict = None):
"""Process DLQ messages with reason-specific handlers."""
handler_map = handler_map or {}
receiver = self.client.get_queue_receiver(
queue_name=self.queue_name,
sub_queue=ServiceBusSubQueue.DEAD_LETTER
)
async with receiver:
while True:
messages = await receiver.receive_messages(
max_message_count=10,
max_wait_time=5
)
if not messages:
break
for msg in messages:
reason = msg.dead_letter_reason or "Unknown"
handler = handler_map.get(reason, self.default_handler)
try:
await handler(msg, receiver)
except Exception as e:
print(f"Error handling DLQ message: {e}")
# Leave message in DLQ for manual review
async def default_handler(self, msg, receiver):
"""Default: log and complete."""
print(f"DLQ Message: {msg.message_id}")
print(f" Reason: {msg.dead_letter_reason}")
print(f" Body: {str(msg.body)[:100]}...")
await receiver.complete_message(msg)
async def retry_handler(self, msg, receiver):
"""Retry message by sending back to main queue."""
sender = self.client.get_queue_sender(queue_name=self.queue_name)
async with sender:
# Create new message from DLQ message
retry_msg = ServiceBusMessage(
body=msg.body,
application_properties={
**(msg.application_properties or {}),
"dlq_retry": True,
"dlq_reason": msg.dead_letter_reason,
"dlq_retry_time": datetime.now(timezone.utc).isoformat()
}
)
await sender.send_messages(retry_msg)
await receiver.complete_message(msg)
print(f"Retried message: {msg.message_id}")
async def archive_handler(self, msg, receiver):
"""Archive message to storage for analysis."""
archive_data = {
"message_id": msg.message_id,
"body": str(msg.body),
"dead_letter_reason": msg.dead_letter_reason,
"dead_letter_error_description": msg.dead_letter_error_description,
"enqueued_time": str(msg.enqueued_time_utc),
"delivery_count": msg.delivery_count,
"application_properties": dict(msg.application_properties or {})
}
# Save to blob storage, database, etc.
await archive_to_storage(archive_data)
await receiver.complete_message(msg)
# Usage
processor = DeadLetterProcessor(client, "myqueue")
await processor.process_dlq({
"MaxDeliveryCountExceeded": processor.retry_handler,
"ValidationFailed": processor.archive_handler,
})Reprocessing Strategies
Selective Retry Based on Age
from datetime import datetime, timedelta, timezone
async def retry_recent_messages(client, queue_name: str, max_age_hours: int = 24):
"""Retry only recently dead-lettered messages."""
cutoff_time = datetime.now(timezone.utc) - timedelta(hours=max_age_hours)
dlq_receiver = client.get_queue_receiver(
queue_name=queue_name,
sub_queue=ServiceBusSubQueue.DEAD_LETTER
)
sender = client.get_queue_sender(queue_name=queue_name)
retried = 0
expired = 0
async with dlq_receiver, sender:
while True:
messages = await dlq_receiver.receive_messages(
max_message_count=50,
max_wait_time=5
)
if not messages:
break
for msg in messages:
if msg.enqueued_time_utc > cutoff_time:
# Recent enough to retry
retry_msg = ServiceBusMessage(body=msg.body)
await sender.send_messages(retry_msg)
retried += 1
else:
# Too old - just complete (discard)
expired += 1
await dlq_receiver.complete_message(msg)
print(f"Retried: {retried}, Expired: {expired}")Retry with Fix
async def retry_with_transform(client, queue_name: str, transformer):
"""Retry messages after applying a fix/transformation."""
dlq_receiver = client.get_queue_receiver(
queue_name=queue_name,
sub_queue=ServiceBusSubQueue.DEAD_LETTER
)
sender = client.get_queue_sender(queue_name=queue_name)
async with dlq_receiver, sender:
messages = await dlq_receiver.receive_messages(max_message_count=100)
for msg in messages:
try:
# Transform/fix the message
fixed_body = transformer(msg.body)
retry_msg = ServiceBusMessage(
body=fixed_body,
application_properties={
"retried_from_dlq": True,
"original_message_id": msg.message_id
}
)
await sender.send_messages(retry_msg)
await dlq_receiver.complete_message(msg)
except Exception as e:
print(f"Could not fix message {msg.message_id}: {e}")
# Leave in DLQ
# Example transformer: fix JSON encoding issue
def fix_json_encoding(body: bytes) -> bytes:
text = body.decode('utf-8', errors='replace')
data = json.loads(text)
return json.dumps(data).encode('utf-8')
await retry_with_transform(client, "myqueue", fix_json_encoding)Monitoring Dead-Letter Queues
Count Dead-Lettered Messages
from azure.servicebus.management import ServiceBusAdministrationClient
async def get_dlq_count(namespace: str, queue_name: str) -> int:
"""Get count of messages in dead-letter queue."""
admin_client = ServiceBusAdministrationClient(
fully_qualified_namespace=namespace,
credential=DefaultAzureCredential()
)
async with admin_client:
runtime_props = await admin_client.get_queue_runtime_properties(queue_name)
return runtime_props.dead_letter_message_count
# Alert if DLQ has messages
dlq_count = await get_dlq_count("myns.servicebus.windows.net", "myqueue")
if dlq_count > 0:
print(f"ALERT: {dlq_count} messages in DLQ!")DLQ Analysis Report
async def analyze_dlq(client, queue_name: str) -> dict:
"""Analyze DLQ contents by reason."""
analysis = {
"total": 0,
"by_reason": {},
"oldest": None,
"newest": None
}
dlq_receiver = client.get_queue_receiver(
queue_name=queue_name,
sub_queue=ServiceBusSubQueue.DEAD_LETTER,
receive_mode=ServiceBusReceiveMode.PEEK_LOCK
)
async with dlq_receiver:
# Peek without removing
messages = await dlq_receiver.peek_messages(max_message_count=1000)
for msg in messages:
analysis["total"] += 1
reason = msg.dead_letter_reason or "Unknown"
analysis["by_reason"][reason] = analysis["by_reason"].get(reason, 0) + 1
if analysis["oldest"] is None or msg.enqueued_time_utc < analysis["oldest"]:
analysis["oldest"] = msg.enqueued_time_utc
if analysis["newest"] is None or msg.enqueued_time_utc > analysis["newest"]:
analysis["newest"] = msg.enqueued_time_utc
return analysis
# Generate report
report = await analyze_dlq(client, "myqueue")
print(f"DLQ Analysis for 'myqueue':")
print(f" Total messages: {report['total']}")
print(f" Oldest: {report['oldest']}")
print(f" Newest: {report['newest']}")
print(f" By reason:")
for reason, count in report['by_reason'].items():
print(f" {reason}: {count}")Preventing Dead-Letters
Increase Max Delivery Count
from azure.servicebus.management import ServiceBusAdministrationClient
async def increase_max_delivery(namespace: str, queue_name: str, max_count: int):
"""Increase max delivery count for a queue."""
admin_client = ServiceBusAdministrationClient(
fully_qualified_namespace=namespace,
credential=DefaultAzureCredential()
)
async with admin_client:
queue = await admin_client.get_queue(queue_name)
queue.max_delivery_count = max_count
await admin_client.update_queue(queue)
print(f"Updated max delivery count to {max_count}")Implement Proper Error Handling
async def resilient_processor(receiver, msg):
"""Process with proper error categorization."""
try:
await process_message(msg)
await receiver.complete_message(msg)
except (ConnectionError, TimeoutError):
# Transient - safe to retry
await receiver.abandon_message(msg)
except ValidationError:
# Bad data - don't retry, dead-letter
await receiver.dead_letter_message(msg, reason="ValidationError")
except Exception as e:
# Unknown - check delivery count
if msg.delivery_count >= 3:
# Enough retries, dead-letter with context
await receiver.dead_letter_message(
msg,
reason="ProcessingFailed",
error_description=f"Failed after {msg.delivery_count} attempts: {e}"
)
else:
# Still have retries left
await receiver.abandon_message(msg)Best Practices
| Practice | Description |
|---|---|
| Monitor DLQ counts | Alert when messages appear in DLQ |
| Set appropriate max delivery | Balance between retries and DLQ accumulation |
| Include context | Always provide reason and description when dead-lettering |
| Regular cleanup | Process or archive old DLQ messages |
| Categorize errors | Distinguish retryable vs permanent failures |
| Implement retry handlers | Automate reprocessing where safe |
| Archive for analysis | Keep DLQ data for debugging patterns |
Requirements and compatibility
Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta
“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.
Needs: Needs an API key, Needs Python
Security
What our scanner noticed· Low risk
- Instructions that use the network
- Scripts that your agent may run
This skill includes scripts that your agent may run. Read them before you use it.
A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.
Attribution and licence
- Created by
- Microsoft
- Publisher
- Microsoft (Official)
- Source
- microsoft/skills · .github/plugins/azure-sdk-python/skills/azure-servicebus-py · commit 0696600
- Licence
- MIT License (redistribution allowed)
- LICENSE (added by AutoGineer from LICENSE)
- ATTRIBUTION.md (written by AutoGineer)
- Synced
AutoGineer is not affiliated with or endorsed by Microsoft or Microsoft.
Download ZIP
The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download azure-servicebus-py.zip