🚌 Azure Messaging

Azure Service Bus
Complete Guide

From beginner to architecture level β€” queues, topics, DLQ, sessions, security, Logic Apps, Function Apps, APIM integrations, and every architecture pattern you need.

Beginner β†’ Architecture21 SectionsEnterprise MessagingSDK ExamplesPolicy XML

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.

πŸ”—
Decoupling
Producer and consumer don't need to be online simultaneously
πŸ›‘οΈ
Reliability
Messages are persisted until successfully processed
πŸ“‹
Ordering
FIFO guarantees with sessions for critical workflows
⚑
Load Leveling
Absorb traffic spikes; consumers process at their own pace
☠️
Dead-Letter
Unprocessable messages stored safely for analysis
πŸ’³
Transactions
Atomic operations across multiple messaging entities

02Core Concepts & Terminology

TermDefinition
NamespaceContainer for all messaging resources. Unique DNS name: <namespace>.servicebus.windows.net
QueuePoint-to-point channel. One sender, one receiver per message.
TopicPublish-subscribe channel. One sender, multiple independent subscribers.
SubscriptionNamed view of a topic. Each subscriber gets its own copy of messages.
MessageThe data unit sent through Service Bus. Has body + system + user properties.
Dead-Letter QueueSub-queue for messages that couldn't be processed after max retries.
SessionGroups related messages for FIFO ordered processing.
LockPrevents other consumers receiving a message while one processes it.
TTLTime to Live β€” how long a message survives before auto-expiry.
Scheduled EnqueueSend 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.

PropertyDescriptionDefault
Max SizeMax queue storage1 GB (up to 80 GB Premium)
Max Message SizePer message body256 KB (100 MB Premium)
TTLMessage time-to-live14 days
Lock DurationTime a receiver holds the lock60 seconds
Max Delivery CountRetries before dead-lettering10
Duplicate DetectionWindow for deduplicationOff by default
SessionsEnable ordered sessionsOff 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.

⚠️
Tier RequirementTopics are not available on the Basic tier. You need Standard or Premium.

Subscription Filters

Filter TypeDescriptionExample
Boolean FilterAll messages (TrueFilter) or none (FalseFilter)Default: TrueFilter
SQL FilterSQL-92 expression on message propertiesOrderType = 'Premium'
Correlation FilterMatch on standard/custom properties β€” more efficientCorrelationId = '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.

FeatureBasicStandardPremium
Queuesβœ“ Yesβœ“ Yesβœ“ Yes
Topics & Subscriptionsβœ— Noβœ“ Yesβœ“ Yes
Max Message Size256 KB256 KB100 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 modelPer operationPer operationPer 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

csharp
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)

csharp
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

ActionMethodEffect
CompleteCompleteMessageAsync()Message removed from queue β€” success
AbandonAbandonMessageAsync()Message returned to queue for retry
Dead-LetterDeadLetterMessageAsync(reason, description)Moved to DLQ permanently
DeferDeferMessageAsync()Hidden until explicitly fetched by sequence number

Receive Modes

ModeDescriptionUse When
PeekLock (default)Message locked, must be completed/abandonedReliable processing required
ReceiveAndDeleteMessage removed immediately on receiptFast, at-most-once semantics

Scheduled & Batch Messages

csharp
// 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 PathFormat
Queue DLQ<queue-name>/$deadletterqueue
Topic Subscription DLQ<topic>/<subscriptions>/<sub-name>/$deadletterqueue

Reading from DLQ

csharp
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);
}
πŸ’‘
Best PracticeAlways monitor DLQ message count via Azure Monitor alerts, and implement a dedicated DLQ processor to handle, log, and replay messages automatically.

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.

csharp
// 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

sql
-- Only receive Premium EU orders over $100
OrderType = 'Premium' AND Region = 'EU' AND OrderAmount > 100

Create Filter with Action via SDK

csharp
var filter = new SqlRuleFilter("Region = 'EU'");
var action = new SqlRuleAction("SET ProcessedBy = 'EUProcessor'");

await adminClient.CreateRuleAsync(topicName, subscriptionName,
    new CreateRuleOptions("EUFilter", filter) { Action = action });
⚑
Performance TipPrefer Correlation Filters over SQL Filters for better throughput. Correlation filters are evaluated server-side with O(1) performance vs SQL's O(n).

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.

MethodTypeRecommended?
Account SAS / Connection StringShared 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

csharp
// No keys in code β€” uses DefaultAzureCredential
var credential = new DefaultAzureCredential();
var client = new ServiceBusClient(
    "<namespace>.servicebus.windows.net",
    credential);

RBAC Roles

RolePermissions
Azure Service Bus Data OwnerFull access β€” send, receive, manage
Azure Service Bus Data SenderSend messages only
Azure Service Bus Data ReceiverReceive messages only
bash
# 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.

FeatureBasicStandardPremium
IP Filteringβœ“ Yesβœ“ Yesβœ“ Yes
VNet Service Endpointsβœ— Noβœ— Noβœ“ Yes
Private Endpointsβœ— Noβœ— Noβœ“ Yes
Disable Public Accessβœ— Noβœ— Noβœ“ Yes
πŸ”’
Private EndpointsPremium tier supports Private Endpoints β€” Service Bus accessible only within your VNet via a private IP. DNS resolves <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.

ConnectorDirectionDescription
When a message is received in a queueTriggerFire Logic App when queue has messages
When a message is received in a topic subscriptionTriggerFire on topic subscription messages
Send message to queueActionPut a message into a queue
Send message to topicActionPublish to a topic
Complete message in queueActionMark message as successfully processed
Abandon message in queueActionReturn message to queue for retry
Dead-letter message in queueActionMove message to DLQ explicitly
Get messages from dead-letter queueActionRead and inspect DLQ messages
Renew lock on messageActionExtend the processing lock duration
πŸ”‘
Managed Identity (Recommended)For Standard Logic Apps, enable System Assigned Identity and assignAzure 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

csharp
[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

csharp
[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

csharp
[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

csharp
[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

json
{
  "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

xml
<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.

πŸ•ΈοΈ
Event Grid
Service Bus emits events for DLQ threshold, no-listeners alerts β€” trigger Functions or Logic Apps reactively.
πŸ“¦
Blob Storage
Claim-Check pattern: store large payloads in Blob, send only the URL reference via Service Bus.
☸️
AKS + KEDA
KEDA scales Kubernetes pods automatically based on Service Bus queue depth.
πŸ”„
Azure Relay
Bridge on-premises systems with Service Bus using outbound connections β€” no inbound firewall ports needed.
🏭
Data Factory
Use Service Bus as a trigger source or send completion events after pipeline runs.
🌐
API Center
Register and catalog your messaging APIs alongside REST APIs for discoverability.

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.

FeatureService Bus QueueStorage Queue
Max Message Size256 KB (100 MB Premium)64 KB
Max Queue Size80 GBUnlimited
FIFO OrderingSessionsβœ— No
Dead-Letter Queueβœ“ Yesβœ— No
Duplicate Detectionβœ“ Yesβœ— No
Transactionsβœ“ Yesβœ— No
Topics / Pub-Subβœ“ Yesβœ— No
Max Retention14 days7 days
Best ForEnterprise workflowsSimple cheap tasks
πŸ’‘
Blob + Service Bus = Claim-Check PatternFor payloads over 256KB β€” store the data in Blob Storage, send only the URL via Service Bus. The consumer fetches the payload from Blob using the URL in the message.

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.

MetricDescriptionAlert Threshold
ActiveMessagesMessages waiting to be processed> 1000 (tune per app)
DeadletteredMessagesMessages in DLQ> 0 β€” always alert
IncomingMessagesMessages sent per intervalSpike detection
ThrottledRequestsRequests being throttled> 0
ServerErrorsServer-side errors> 0
SizeQueue/topic size in bytes> 80% of max

KQL β€” DLQ Messages in Last 24h

kql
AzureDiagnostics
| where ResourceType == "NAMESPACES"
| where OperationName contains "deadletter"
| where TimeGenerated > ago(24h)
| project TimeGenerated, EntityName, CallerIpAddress, ResultType
| order by TimeGenerated desc

17Geo-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.

FeatureDescriptionTier
Geo-Disaster RecoveryMetadata sync to secondary region; manual failover via aliasPremium
Availability Zones3 AZ copies within a region, automaticPremium
Geo-Replication (Preview)Full message replication across regions in near real-timePremium

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.

πŸ‘₯
Competing Consumers
Multiple workers compete for queue messages β€” scales horizontally by adding more consumers.
πŸ“‘
Publisher-Subscriber
One event fan-out to many subscribers via Topics β€” email, billing, analytics all receive independently.
πŸ”„
Saga Pattern
Coordinate distributed transactions across microservices with compensating events on failure.
πŸ“Ž
Claim-Check
Store large payloads in Blob Storage, pass only the URL via Service Bus to avoid size limits.
↩️
Request-Reply
Use ReplyTo + CorrelationId to implement async RPC over queues between services.
πŸ“¬
Outbox Pattern
Write messages to a local outbox table in the same DB transaction, then relay to Service Bus.

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.

TierCostUse Case
Basic$0.05 per million operationsDev/test, simple low-cost queue scenarios
Standard$10/month base + $0.01 per million opsMost production applications
Premium~$677/month per messaging unitEnterprise, compliance, high throughput
πŸ’°
Cost TipsUse Correlation Filters over SQL filters (cheaper server processing). Set appropriate TTL to avoid storing messages longer than needed. Monitor and drain DLQs regularly.

20Quick Reference Cheat Sheet

Connection & Paths
text
# 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>
Common SDK Calls
csharp
// 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));
LimitValue
Max queues per namespace10,000
Max topics per namespace10,000
Max subscriptions per topic2,000
Max message size (Standard)256 KB
Max message size (Premium)100 MB
Max message TTL14 days (unlimited on Premium)
Max delivery count1–2000 (default 10)
Max lock duration5 minutes