I’m sending C2D messages from an Azure IoT Hub to an MQTT device from a .NET application using Azure IoT Device SDK.
Even when the device is powered off or has lost its network connection, my feedback receiver gets a record whose
StatusCode == Success
The device twin also keeps showing connectionState = Connected
for several minutes. (I'm aware that this property should be used only for development purposes)
I need feedback that tells me “message NOT delivered because the device was offline” (Expired
, Rejected
, or similar).
Send a C2D message with the Service SDK:
var msg = new Message(Encoding.UTF8.GetBytes("hello"))
{
MessageId = Guid.NewGuid().ToString(),
Ack = DeliveryAcknowledgement.Full,
ExpiryTimeUtc = DateTime.UtcNow.AddMinutes(1)
};
await serviceClient.SendAsync(deviceId, msg);
Listen for feedback:
var receiver = serviceClient.GetFeedbackReceiver();
var batch = await receiver.ReceiveAsync();
foreach (var record in batch.Records)
Console.WriteLine($"{record.StatusCode} | {record.Description}");
Physically power off or unplug the device before it receives the message.
Expected feedback: Expired
(or at least no Success
)
Actual feedback (within a few seconds): Success
deviceDisconnected
event, but I’d like to understand why IoT Hub labels this “Success” and whether there’s a native way to get “not delivered” status.Success
when the device never actually consumed the message?Expired
/Rejected
for messages sent while the device is offline?Sending a C2D message with Ack = Full
and a short TTL (1 min). The device is offline (e.g., powered off).
You receive feedback from IoT Hub with StatusCode = Success
, even though the device never connected to receive the message.
You expect Expired
or Rejected
, because the message was not delivered to the actual device application.
Even though the device is powered off, IoT Hub still believes it’s connected for a while due to how MQTT handles disconnects (delayed TCP timeout, keep-alive intervals, etc.).
Your message gets enqueued in the device-bound queue.
IoT Hub returns Success
because it successfully enqueued the message, not because it was read by the device.
But MQTT clients do not explicitly acknowledge messages the way AMQP clients do, so the feedback system interprets certain message lifecycle events loosely.
Why You Don’t Get
Expired
orRejected
Expired: You might get this if the TTL expires and the device has not reconnected. But depending on how the feedback receiver is implemented and whether you're listening long enough, you might miss it.
Reject: Only possible if the device explicitly rejects the message not supported in MQTT.
Abandon: Also not supported in MQTT.
You can verify message expiration by:
Set a short TTL, like 30 seconds.
Ensure the device is offline and remains offline beyond the TTL.
Ensure your feedback receiver keeps listening (for at least an hour).
Eventually you should get:
{
"statusCode": "Expired",
"description": "Message expired",
...
}
But again: MQTT + C2D doesn’t guarantee exact delivery feedback unless the device explicitly consumes and acknowledges the message, which MQTT lacks.
Don’t rely solely on feedback = Success
. Use short TTL + D2C acknowledgments. Use Event Grid to monitor device status. Consider switching to AMQP if full lifecycle control is critical. Use direct methods for time-critical commands.
Refer to this link for https://learn.microsoft.com/en-us/azure/iot-hub/iot-hub-devguide-messages-c2d