azuremqttazure-iot-hub

Azure IoT Hub C2D feedback reports Success for some period when the device is offline


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

Reproduction steps

  1. 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);
    
  2. Listen for feedback:

    var receiver = serviceClient.GetFeedbackReceiver();
    var batch = await receiver.ReceiveAsync();
    foreach (var record in batch.Records)
        Console.WriteLine($"{record.StatusCode} | {record.Description}");
    
  3. 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


What I’ve researched

Questions

  1. Why does IoT Hub emit Success when the device never actually consumed the message?
  2. Is there any built‑in mechanism (other than adding my own ACK telemetry or switching to direct methods) that returns Expired/Rejected for messages sent while the device is offline?
  3. If not, what is the recommended pattern for guaranteeing command delivery or detecting offline devices?

Solution

  • 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.).

    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 or Rejected

    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:

    1. Set a short TTL, like 30 seconds.

    2. Ensure the device is offline and remains offline beyond the TTL.

    3. Ensure your feedback receiver keeps listening (for at least an hour).

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