Azure Service Bus Message Operations: Complete, Abandon, Defer, and Deadletter

Azure Service Bus is a fully managed message broker that supports a range of messaging operations. Understanding how to handle messages efficiently is key to building reliable and scalable applications. Here we will discuss the core message operations: complete, abandon, defer, and deadletter, explaining how and when to use each.
Message Processing Overview
When a message is received from a queue or subscription in Azure Service Bus, the receiver has several options for handling that message. The choice of operation depends on the outcome of processing the message:
Complete: The message is processed successfully.
Abandon: The processing failed, and the message should be retried.
Defer: The message needs to be processed later.
Deadletter: The message is not processed due to an error and needs to be stored for later analysis.
Complete Operation
What is it?
The complete operation is used when a message has been successfully processed. Completing a message removes it from the queue or subscription.
How to use it?
import { ServiceBusClient } from '@azure/service-bus';
async function processMessage(message) {
try {
// Process the message
console.log("Processing message: ", message.body);
// If processing succeeds, complete the message
await message.complete();
console.log("Message completed successfully.");
} catch (error) {
console.error("Error processing message: ", error);
}
}
can be done, like this as well
import { ServiceBusClient } from '@azure/service-bus';
async function completeMessageExample() {
const connectionString = "YOUR_SERVICE_BUS_CONNECTION_STRING";
const queueName = "YOUR_QUEUE_NAME";
const sbClient = new ServiceBusClient(connectionString);
const receiver = sbClient.createReceiver(queueName);
const messages = await receiver.receiveMessages(1);
for (const message of messages) {
// Process the message
console.log(`Received message: ${message.body}`);
await receiver.completeMessage(message);
console.log("Message completed.");
}
await sbClient.close();
}
completeMessageExample();
When to use?
After the message has been processed without errors.
Ensures that the message is removed from the queue, preventing it from being processed again.
Abandon Operation
What is it?
The abandon operation is used when the processing of a message fails temporarily, and you want the message to be retried. Abandoning a message returns it to the queue for another attempt.
How to use it?
async function processMessage(message) {
try {
// Simulate a failure in processing
throw new Error("Processing failed");
} catch (error) {
// If processing fails, abandon the message
await message.abandon();
console.log("Message abandoned for retry.");
}
}
can be done like this as well
async function abandonMessageExample() {
const sbClient = new ServiceBusClient("YOUR_SERVICE_BUS_CONNECTION_STRING");
const receiver = sbClient.createReceiver("YOUR_QUEUE_NAME");
const messages = await receiver.receiveMessages(1);
for (const message of messages) {
try {
// Simulate processing logic
if (message.body.shouldAbandon) throw new Error("Processing error");
await receiver.completeMessage(message);
} catch {
await receiver.abandonMessage(message, { propertiesToModify: { retryCount: (message.properties.retryCount || 0) + 1 } });
console.log("Message abandoned.");
}
}
await sbClient.close();
}
abandonMessageExample();
When to use?
When a transient error occurs (e.g., network issues).
Allows the message to be retried based on the retry policy.
Defer Operation
What is it?
The defer operation is used when the message cannot be processed immediately, but you want to process it later. Deferred messages stay in the queue but are not immediately visible for retrieval.
How to use it?
async function processMessage(message) {
// Check some condition
if (!canProcessNow()) {
// Defer the message
await message.defer();
console.log("Message deferred for later processing.");
}
}
async function canProcessNow() {
// Logic to determine if processing can happen now
return false;
}
can be done like this as well
async function deferMessageExample() {
const sbClient = new ServiceBusClient("YOUR_SERVICE_BUS_CONNECTION_STRING");
const receiver = sbClient.createReceiver("YOUR_QUEUE_NAME");
const messages = await receiver.receiveMessages(1);
for (const message of messages) {
if (message.body.deferProcessing) {
await receiver.deferMessage(message);
console.log("Message deferred.");
} else {
await receiver.completeMessage(message);
}
}
await sbClient.close();
}
deferMessageExample();
When to use?
When the message depends on an external event or resource.
Useful for scheduling the processing of messages at a later time.
Deadletter Operation
What is it?
The deadletter operation is used when the message cannot be processed due to a fatal error. Deadlettering a message moves it to the Dead Letter Queue (DLQ) for further inspection and troubleshooting.
How to use it?
async function processMessage(message) {
try {
// Simulate a fatal error
throw new Error("Fatal processing error");
} catch (error) {
// If a fatal error occurs, deadletter the message
await message.deadLetter({
reason: "ProcessingError",
description: "A fatal error occurred while processing the message."
});
console.log("Message sent to dead letter queue.");
}
}
can be done like this as well
async function deadLetterMessageExample() {
const sbClient = new ServiceBusClient("YOUR_SERVICE_BUS_CONNECTION_STRING");
const receiver = sbClient.createReceiver("YOUR_QUEUE_NAME");
const messages = await receiver.receiveMessages(1);
for (const message of messages) {
if (!isValidMessage(message)) {
await receiver.deadLetterMessage(message, {
deadLetterReason: "InvalidData",
deadLetterErrorDescription: "The message format is incorrect"
});
console.log("Message dead-lettered.");
} else {
await receiver.completeMessage(message);
}
}
await sbClient.close();
}
function isValidMessage(message) {
// Add your validation logic here
return true;
}
deadLetterMessageExample();
When to use?
When a message cannot be processed due to an unresolvable issue (e.g., invalid data format).
Allows for the message to be analyzed later without impacting the main queue processing.
Practical Example
Here's a full example demonstrating these operations:
import { ServiceBusClient } from '@azure/service-bus';
const connectionString = "YOUR_SERVICE_BUS_CONNECTION_STRING";
const queueName = "YOUR_QUEUE_NAME";
async function main() {
const sbClient = new ServiceBusClient(connectionString);
const receiver = sbClient.createReceiver(queueName);
const messages = await receiver.receiveMessages(10);
for (const message of messages) {
try {
console.log(`Received message: ${message.body}`);
// Simulate message processing
if (message.body.shouldComplete) {
await message.complete();
console.log("Message completed.");
} else if (message.body.shouldAbandon) {
await message.abandon();
console.log("Message abandoned.");
} else if (message.body.shouldDefer) {
await message.defer();
console.log("Message deferred.");
} else {
await message.deadLetter();
console.log("Message deadlettered.");
}
} catch (error) {
console.error("Error processing message: ", error);
}
}
await sbClient.close();
}
main().catch((err) => {
console.log("Error occurred: ", err);
});
Dead-Letter Subqueue
What is a Dead-Letter Subqueue?
The dead-letter subqueue is a special subqueue associated with each queue or subscription. It is used to store messages that cannot be processed successfully. Each queue or subscription in Azure Service Bus has its own dead-letter subqueue, which can be accessed using the $DeadLetterQueue
suffix.
When is a Message Moved to the Dead-Letter Subqueue?
Max Delivery Attempts: The message exceeds the maximum delivery attempts set for the queue or subscription.
Explicit Deadlettering: The message is explicitly dead-lettered by the application.
Validation Errors: The message fails validation checks (e.g., format issues).
TTL (Time to Live) Expiry: The message's TTL expires before it is processed.
Retrieving Messages from the Dead-Letter Subqueue
You can access messages in the dead-letter subqueue by using the $DeadLetterQueue
suffix in the queue or subscription name.
async function retrieveDeadLetteredMessages() {
const sbClient = new ServiceBusClient("YOUR_SERVICE_BUS_CONNECTION_STRING");
const receiver = sbClient.createReceiver("YOUR_QUEUE_NAME/$DeadLetterQueue");
const messages = await receiver.receiveMessages(10);
for (const message of messages) {
console.log(`Dead-lettered message: ${message.body}`);
await receiver.completeMessage(message); // Optionally complete the message after reviewing
}
await sbClient.close();
}
retrieveDeadLetteredMessages();
Subscribe to my newsletter
Read articles from Muhammad Sufiyan directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Muhammad Sufiyan
Muhammad Sufiyan
As a former 3D Animator with more than 12 years of experience, I have always been fascinated by the intersection of technology and creativity. That's why I recently shifted my career towards MERN stack development and software engineering, where I have been serving since 2021. With my background in 3D animation, I bring a unique perspective to software development, combining creativity and technical expertise to build innovative and visually engaging applications. I have a passion for learning and staying up-to-date with the latest technologies and best practices, and I enjoy collaborating with cross-functional teams to solve complex problems and create seamless user experiences. In my current role as a MERN stack developer, I have been responsible for developing and implementing web applications using MongoDB, Express, React, and Node.js. I have also gained experience in Agile development methodologies, version control with Git, and cloud-based deployment using platforms like Heroku and AWS. I am committed to delivering high-quality work that meets the needs of both clients and end-users, and I am always seeking new challenges and opportunities to grow both personally and professionally.