01What Is Azure Service Bus?
Azure Service Bus is a fully managed enterprise message broker that enables asynchronous, reliable, and decoupled communication between applications β both cloud and on-premises. Think of it as a post office for your applications: one system drops a message in a mailbox (queue), another picks it up later, regardless of whether both are running simultaneously.
02Core Concepts & Terminology
| Term | Definition |
|---|---|
| Namespace | Container for all messaging resources. Unique DNS name: <namespace>.servicebus.windows.net |
| Queue | Point-to-point channel. One sender, one receiver per message. |
| Topic | Publish-subscribe channel. One sender, multiple independent subscribers. |
| Subscription | Named view of a topic. Each subscriber gets its own copy of messages. |
| Message | The data unit sent through Service Bus. Has body + system + user properties. |
| Dead-Letter Queue | Sub-queue for messages that couldn't be processed after max retries. |
| Session | Groups related messages for FIFO ordered processing. |
| Lock | Prevents other consumers receiving a message while one processes it. |
| TTL | Time to Live β how long a message survives before auto-expiry. |
| Scheduled Enqueue | Send a message to be delivered at a future point in time. |
03Queues, Topics & Subscriptions
Queues β Point-to-Point
A queue is a FIFO channel with competitive consumers β only ONE consumer receives each message. Multiple producers can send, and multiple consumers can compete, but each message goes to exactly one worker.
| Property | Description | Default |
|---|---|---|
| Max Size | Max queue storage | 1 GB (up to 80 GB Premium) |
| Max Message Size | Per message body | 256 KB (100 MB Premium) |
| TTL | Message time-to-live | 14 days |
| Lock Duration | Time a receiver holds the lock | 60 seconds |
| Max Delivery Count | Retries before dead-lettering | 10 |
| Duplicate Detection | Window for deduplication | Off by default |
| Sessions | Enable ordered sessions | Off by default |
Topics & Subscriptions β Publish/Subscribe
A topic allows ONE message to be delivered to multiple subscriptions independently. Each subscription gets its own copy of every message that matches its filter. Topics require Standard or Premium tier.
Subscription Filters
| Filter Type | Description | Example |
|---|---|---|
| Boolean Filter | All messages (TrueFilter) or none (FalseFilter) | Default: TrueFilter |
| SQL Filter | SQL-92 expression on message properties | OrderType = 'Premium' |
| Correlation Filter | Match on standard/custom properties β more efficient | CorrelationId = 'abc123' |
04Tiers β Basic / Standard / Premium
Azure Service Bus offers three tiers to match different workload requirements and budgets. Basic is ideal for dev/test with simple queues, Standard adds topics and transactions for most production apps, and Premium provides dedicated capacity, VNet isolation, and large message support for enterprise-critical workloads. Choose your tier based on feature needs, throughput requirements, and network security constraints.
| Feature | Basic | Standard | Premium |
|---|---|---|---|
| Queues | β Yes | β Yes | β Yes |
| Topics & Subscriptions | β No | β Yes | β Yes |
| Max Message Size | 256 KB | 256 KB | 100 MB |
| Sessions | β No | β Yes | β Yes |
| Transactions | β No | β Yes | β Yes |
| Duplicate Detection | β No | β Yes | β Yes |
| VNet Integration | β No | β No | β Yes |
| Private Endpoints | β No | β No | β Yes |
| Geo-Disaster Recovery | β No | β No | β Yes |
| Availability Zones | β No | β No | β Yes |
| Dedicated capacity | β No | β No | β Yes |
| Pricing model | Per operation | Per operation | Per messaging unit/hr |
05Sending & Receiving Messages
Understanding how to send and receive messages is the foundation of working with Service Bus. The .NET SDK provides a processor-based model for reliable consumption with automatic lock renewal, and supports batch sending for high-throughput scenarios. Always use PeekLock mode in production to ensure messages are not lost if processing fails.
Send a Message β .NET SDK
var client = new ServiceBusClient("your-connection-string");
var sender = client.CreateSender("your-queue-name");
var message = new ServiceBusMessage("{\"orderId\": 123, \"amount\": 99.99}")
{
ContentType = "application/json",
Subject = "OrderCreated",
MessageId = Guid.NewGuid().ToString(),
TimeToLive = TimeSpan.FromHours(24),
ApplicationProperties =
{
["Region"] = "EU",
["Priority"] = "High"
}
};
await sender.SendMessageAsync(message);Receive with Processor (Recommended)
var processor = client.CreateProcessor("your-queue-name", new ServiceBusProcessorOptions
{
MaxConcurrentCalls = 5,
AutoCompleteMessages = false
});
processor.ProcessMessageAsync += async args =>
{
string body = args.Message.Body.ToString();
Console.WriteLine($"Received: {body}");
// Process the message...
// Complete ONLY after successful processing
await args.CompleteMessageAsync(args.Message);
};
processor.ProcessErrorAsync += args =>
{
Console.WriteLine($"Error: {args.Exception}");
return Task.CompletedTask;
};
await processor.StartProcessingAsync();Message Settlement Actions
| Action | Method | Effect |
|---|---|---|
| Complete | CompleteMessageAsync() | Message removed from queue β success |
| Abandon | AbandonMessageAsync() | Message returned to queue for retry |
| Dead-Letter | DeadLetterMessageAsync(reason, description) | Moved to DLQ permanently |
| Defer | DeferMessageAsync() | Hidden until explicitly fetched by sequence number |
Receive Modes
| Mode | Description | Use When |
|---|---|---|
| PeekLock (default) | Message locked, must be completed/abandoned | Reliable processing required |
| ReceiveAndDelete | Message removed immediately on receipt | Fast, at-most-once semantics |
Scheduled & Batch Messages
// Schedule for future delivery
var msg = new ServiceBusMessage("Reminder");
msg.ScheduledEnqueueTime = DateTimeOffset.UtcNow.AddHours(1);
long seqNo = await sender.ScheduleMessageAsync(msg, msg.ScheduledEnqueueTime);
// Cancel scheduled message
await sender.CancelScheduledMessageAsync(seqNo);
// Batch send
using ServiceBusMessageBatch batch = await sender.CreateMessageBatchAsync();
for (int i = 0; i < 100; i++)
{
if (!batch.TryAddMessage(new ServiceBusMessage($"Message {i}")))
{
await sender.SendMessagesAsync(batch);
}
}
await sender.SendMessagesAsync(batch);06Dead-Letter Queue (DLQ)
The DLQ is a special sub-queue that receives messages that exceeded max delivery count, were explicitly dead-lettered, had their TTL expire, or failed filter evaluation.
| DLQ Path | Format |
|---|---|
| Queue DLQ | <queue-name>/$deadletterqueue |
| Topic Subscription DLQ | <topic>/<subscriptions>/<sub-name>/$deadletterqueue |
Reading from DLQ
var dlqReceiver = client.CreateReceiver(
"your-queue-name",
new ServiceBusReceiverOptions { SubQueue = SubQueue.DeadLetter });
var dlqMsg = await dlqReceiver.ReceiveMessageAsync();
if (dlqMsg != null)
{
Console.WriteLine($"Reason: {dlqMsg.DeadLetterReason}");
Console.WriteLine($"Description: {dlqMsg.DeadLetterErrorDescription}");
Console.WriteLine($"Delivery Count: {dlqMsg.DeliveryCount}");
Console.WriteLine($"Body: {dlqMsg.Body}");
// Fix and re-submit, or discard
await dlqReceiver.CompleteMessageAsync(dlqMsg);
}07Sessions
Sessions provide guaranteed FIFO ordering and exclusive processing for groups of related messages. They are essential when message sequence matters β such as multi-step order workflows or user-specific event streams. Enable sessions on the queue or subscription at creation time, and use session state to track progress across related messages.
Sessions enable ordered, FIFO processing of related messages. All messages with the same SessionId are delivered in order to the same consumer β critical for order management, user workflows, and financial processing.
// Send with session
var msg1 = new ServiceBusMessage("Step 1: Order Created") { SessionId = "order-456" };
var msg2 = new ServiceBusMessage("Step 2: Payment Received") { SessionId = "order-456" };
await sender.SendMessageAsync(msg1);
await sender.SendMessageAsync(msg2);
// Receive sessions
var sessionProcessor = client.CreateSessionProcessor("session-queue");
sessionProcessor.ProcessMessageAsync += async args =>
{
Console.WriteLine($"Session: {args.Message.SessionId}");
// Store/retrieve session state
byte[] state = await args.GetSessionStateAsync();
await args.SetSessionStateAsync(BinaryData.FromString("step-2-complete"));
await args.CompleteMessageAsync(args.Message);
};08Filters & Actions
Filters control which messages reach each subscription, while actions can modify message properties in-flight. This enables intelligent routing β for example, sending only high-priority EU orders to a specific processor. Use correlation filters for best performance and SQL filters when you need complex expressions.
SQL Filter
-- Only receive Premium EU orders over $100
OrderType = 'Premium' AND Region = 'EU' AND OrderAmount > 100Create Filter with Action via SDK
var filter = new SqlRuleFilter("Region = 'EU'");
var action = new SqlRuleAction("SET ProcessedBy = 'EUProcessor'");
await adminClient.CreateRuleAsync(topicName, subscriptionName,
new CreateRuleOptions("EUFilter", filter) { Action = action });09Security & Authentication
Service Bus supports multiple authentication methods, but Managed Identity with Entra ID RBAC is the recommended approach for production. This eliminates secrets from your code and provides fine-grained access control at the namespace, queue, or topic level. Always follow least-privilege principles by assigning only Sender or Receiver roles rather than the full Owner role.
| Method | Type | Recommended? |
|---|---|---|
| Account SAS / Connection String | Shared Key | β οΈ Avoid in production |
| SAS Policy (least-privilege) | Shared Key scoped | β Acceptable |
| Managed Identity (System/User) | Entra ID token | β Recommended |
| Service Principal (Entra ID) | Entra ID token | β Recommended |
Managed Identity β Zero Secrets
// No keys in code β uses DefaultAzureCredential
var credential = new DefaultAzureCredential();
var client = new ServiceBusClient(
"<namespace>.servicebus.windows.net",
credential);RBAC Roles
| Role | Permissions |
|---|---|
Azure Service Bus Data Owner | Full access β send, receive, manage |
Azure Service Bus Data Sender | Send messages only |
Azure Service Bus Data Receiver | Receive messages only |
# Assign Sender role via Azure CLI
az role assignment create \
--assignee <principal-id> \
--role "Azure Service Bus Data Sender" \
--scope /subscriptions/<sub>/resourceGroups/<rg>/providers/\
Microsoft.ServiceBus/namespaces/<namespace>10Network Security
Network security controls restrict how clients connect to your Service Bus namespace. While IP filtering is available on all tiers, VNet integration and Private Endpoints require Premium tier. For enterprise deployments, use Private Endpoints to ensure traffic never traverses the public internet and disable public network access entirely.
| Feature | Basic | Standard | Premium |
|---|---|---|---|
| IP Filtering | β Yes | β Yes | β Yes |
| VNet Service Endpoints | β No | β No | β Yes |
| Private Endpoints | β No | β No | β Yes |
| Disable Public Access | β No | β No | β Yes |
<namespace>.servicebus.windows.net to the private IP and all public access is blocked.11Logic Apps Integration
Logic Apps provides built-in connectors for Service Bus that enable no-code/low-code message processing workflows. You can trigger a Logic App when messages arrive in a queue or topic subscription, and use actions to send, complete, abandon, or dead-letter messages. Use Managed Identity authentication to avoid storing connection strings in your Logic App connections.
| Connector | Direction | Description |
|---|---|---|
| When a message is received in a queue | Trigger | Fire Logic App when queue has messages |
| When a message is received in a topic subscription | Trigger | Fire on topic subscription messages |
| Send message to queue | Action | Put a message into a queue |
| Send message to topic | Action | Publish to a topic |
| Complete message in queue | Action | Mark message as successfully processed |
| Abandon message in queue | Action | Return message to queue for retry |
| Dead-letter message in queue | Action | Move message to DLQ explicitly |
| Get messages from dead-letter queue | Action | Read and inspect DLQ messages |
| Renew lock on message | Action | Extend the processing lock duration |
Azure Service Bus Data Receiver orAzure Service Bus Data Sender roles. This avoids connection strings entirely.12Function Apps Integration
Azure Functions offers native Service Bus bindings for event-driven message processing with automatic scaling. The trigger binding processes messages as they arrive, while output bindings let you send messages from any function. Functions scale out automatically based on queue depth, making them ideal for variable-load message processing.
Service Bus Trigger
[FunctionName("ProcessOrder")]
public static async Task Run(
[ServiceBusTrigger("orders", Connection = "ServiceBusConnection")]
ServiceBusReceivedMessage message,
ServiceBusMessageActions messageActions,
ILogger log)
{
string body = message.Body.ToString();
var order = JsonSerializer.Deserialize<Order>(body);
try
{
await ProcessOrderAsync(order);
await messageActions.CompleteMessageAsync(message);
}
catch (Exception ex)
{
await messageActions.DeadLetterMessageAsync(message,
"ProcessingFailed", ex.Message);
}
}Topic Subscription Trigger
[FunctionName("HandleBillingEvent")]
public static void Run(
[ServiceBusTrigger("order-events", "billing",
Connection = "ServiceBusConnection")]
string messageBody,
ILogger log)
{
log.LogInformation($"Billing event: {messageBody}");
}Output Binding β Send to Queue
[FunctionName("CreateOrder")]
[return: ServiceBus("orders", Connection = "ServiceBusConnection")]
public static ServiceBusMessage Run(
[HttpTrigger(AuthorizationLevel.Function, "post")] HttpRequest req)
{
string body = new StreamReader(req.Body).ReadToEnd();
return new ServiceBusMessage(body)
{
ContentType = "application/json",
Subject = "OrderCreated"
};
}DLQ Trigger
[FunctionName("ProcessDLQ")]
public static void Run(
[ServiceBusTrigger("orders/$deadletterqueue",
Connection = "ServiceBusConnection")]
ServiceBusReceivedMessage dlqMessage,
ILogger log)
{
log.LogError($"DLQ Message: {dlqMessage.MessageId}");
log.LogError($"Reason: {dlqMessage.DeadLetterReason}");
// Alert on-call, store in blob, replay to queue...
}host.json Configuration
{
"version": "2.0",
"extensions": {
"serviceBus": {
"prefetchCount": 100,
"messageHandlerOptions": {
"autoComplete": false,
"maxConcurrentCalls": 16,
"maxAutoRenewDuration": "00:05:00"
}
}
}
}13APIM Integration
Azure API Management can front Service Bus with a REST API, allowing HTTP clients to publish messages without direct Service Bus SDK dependencies. APIM handles authentication, rate limiting, and request transformation before forwarding to Service Bus via its REST API. This pattern is especially useful for exposing messaging capabilities to external partners or legacy systems.
APIM acts as an HTTP facade over Service Bus β clients POST to a REST endpoint, APIM transforms and publishes the message to a queue or topic using Managed Identity.
APIM Policy β Publish to Queue
<inbound>
<base />
<!-- Get Managed Identity token for Service Bus -->
<authentication-managed-identity
resource="https://servicebus.windows.net"
output-token-variable-name="sbToken" />
<!-- Forward to Service Bus REST API -->
<send-request mode="new"
response-variable-name="sbResult" timeout="30">
<set-url>https://{{sb-namespace}}.servicebus.windows.net/{{queue}}/messages</set-url>
<set-method>POST</set-method>
<set-header name="Authorization" exists-action="override">
<value>@("Bearer " + (string)context.Variables["sbToken"])</value>
</set-header>
<set-header name="Content-Type" exists-action="override">
<value>application/json</value>
</set-header>
<set-body>@(context.Request.Body.As<string>())</set-body>
</send-request>
<!-- Return 202 Accepted immediately -->
<return-response>
<set-status code="202" reason="Accepted" />
<set-body>{"requestId": "@(context.RequestId)"}</set-body>
</return-response>
</inbound>14Other Azure Services
Service Bus integrates with a broad ecosystem of Azure services to build complete event-driven architectures. From Event Grid for reactive notifications to KEDA for Kubernetes autoscaling, these integrations extend Service Bus beyond simple point-to-point messaging into a central nervous system for distributed applications.
15vs Storage Queue / Blob / Table
Azure offers both Storage Queues and Service Bus Queues, and choosing the right one depends on your requirements. Storage Queues are simpler and cheaper for basic fire-and-forget scenarios, while Service Bus provides enterprise features like ordering, dead-lettering, transactions, and pub/sub. Use this comparison to pick the right tool for your workload.
| Feature | Service Bus Queue | Storage Queue |
|---|---|---|
| Max Message Size | 256 KB (100 MB Premium) | 64 KB |
| Max Queue Size | 80 GB | Unlimited |
| FIFO Ordering | Sessions | β No |
| Dead-Letter Queue | β Yes | β No |
| Duplicate Detection | β Yes | β No |
| Transactions | β Yes | β No |
| Topics / Pub-Sub | β Yes | β No |
| Max Retention | 14 days | 7 days |
| Best For | Enterprise workflows | Simple cheap tasks |
16Monitoring & Alerts
Proactive monitoring is critical for maintaining healthy messaging infrastructure. Azure Monitor provides built-in metrics for queue depth, DLQ count, throttling, and errors. Set up alerts on key thresholds β especially dead-lettered messages and throttled requests β to catch issues before they impact your application.
| Metric | Description | Alert Threshold |
|---|---|---|
| ActiveMessages | Messages waiting to be processed | > 1000 (tune per app) |
| DeadletteredMessages | Messages in DLQ | > 0 β always alert |
| IncomingMessages | Messages sent per interval | Spike detection |
| ThrottledRequests | Requests being throttled | > 0 |
| ServerErrors | Server-side errors | > 0 |
| Size | Queue/topic size in bytes | > 80% of max |
KQL β DLQ Messages in Last 24h
AzureDiagnostics
| where ResourceType == "NAMESPACES"
| where OperationName contains "deadletter"
| where TimeGenerated > ago(24h)
| project TimeGenerated, EntityName, CallerIpAddress, ResultType
| order by TimeGenerated desc17Geo-DR & High Availability
High availability and disaster recovery ensure your messaging infrastructure survives regional outages. Service Bus Premium offers Availability Zones for intra-region resilience and Geo-DR for cross-region metadata failover. Plan your recovery strategy carefully β standard Geo-DR replicates only metadata, so implement message replay mechanisms for full data recovery.
Geo-Disaster Recovery (Premium only) replicates namespace metadata (queues, topics, subscriptions, policies) to a paired region. Message data is NOT replicated β plan for replay on failover.
| Feature | Description | Tier |
|---|---|---|
| Geo-Disaster Recovery | Metadata sync to secondary region; manual failover via alias | Premium |
| Availability Zones | 3 AZ copies within a region, automatic | Premium |
| Geo-Replication (Preview) | Full message replication across regions in near real-time | Premium |
18Architecture Patterns
Service Bus enables several proven distributed architecture patterns that solve common challenges in microservices and event-driven systems. These patterns address scalability, reliability, and data consistency across loosely coupled services. Choose the pattern that matches your specific integration challenge β most production systems combine multiple patterns.
19Pricing Overview
Service Bus pricing varies significantly across tiers β from pay-per-operation on Basic/Standard to fixed messaging-unit pricing on Premium. Understanding the cost model helps you optimize spend: batch operations to reduce per-op costs, use correlation filters over SQL filters for cheaper processing, and right-size your Premium messaging units based on actual throughput needs.
| Tier | Cost | Use Case |
|---|---|---|
| Basic | $0.05 per million operations | Dev/test, simple low-cost queue scenarios |
| Standard | $10/month base + $0.01 per million ops | Most production applications |
| Premium | ~$677/month per messaging unit | Enterprise, compliance, high throughput |
20Quick Reference Cheat Sheet
# Namespace endpoint
sb://<namespace>.servicebus.windows.net
# Queue DLQ
<queue-name>/$deadletterqueue
# Topic Subscription DLQ
<topic>/<subscriptions>/<sub-name>/$deadletterqueue
# Connection string format
Endpoint=sb://<ns>.servicebus.windows.net/;
SharedAccessKeyName=<policy>;SharedAccessKey=<key>// Send
await sender.SendMessageAsync(new ServiceBusMessage("body"));
// Complete
await receiver.CompleteMessageAsync(message);
// Abandon (retry)
await receiver.AbandonMessageAsync(message);
// Dead-letter
await receiver.DeadLetterMessageAsync(message, "reason", "desc");
// Defer
await receiver.DeferMessageAsync(message);
// Receive deferred
await receiver.ReceiveDeferredMessageAsync(sequenceNumber);
// Peek (no lock)
await receiver.PeekMessageAsync();
// Schedule
await sender.ScheduleMessageAsync(message, DateTimeOffset.UtcNow.AddHours(1));| Limit | Value |
|---|---|
| Max queues per namespace | 10,000 |
| Max topics per namespace | 10,000 |
| Max subscriptions per topic | 2,000 |
| Max message size (Standard) | 256 KB |
| Max message size (Premium) | 100 MB |
| Max message TTL | 14 days (unlimited on Premium) |
| Max delivery count | 1β2000 (default 10) |
| Max lock duration | 5 minutes |