diff --git a/src/api/conversationmodel.h b/src/api/conversationmodel.h
index 642f014a11871056cff73324039fcbe9f10ff4f1..90e04543c6ce96cf21d6e6aaf5a7c27d6510b840 100644
--- a/src/api/conversationmodel.h
+++ b/src/api/conversationmodel.h
@@ -88,6 +88,15 @@ public:
                       const api::BehaviorController& behaviorController);
     ~ConversationModel();
 
+    /**
+     * Get unfiltered underlying conversation data. This is intended to
+     * serve as the underlying data for QAbstractListModel based objects.
+     * The corresponding data mutation signals will need to be responded
+     * to with appropriate QAbstractListModel signal forwarding.
+     * @return raw conversation queue
+     */
+    const ConversationQueue& getConversations() const;
+
     /**
      * Get conversations which should be shown client side
      * @return conversations filtered with the current filter
@@ -237,8 +246,7 @@ public:
      * @param convId
      * @param interactionId
      */
-    void clearInteractionFromConversation(const QString& convId,
-                                          const uint64_t& interactionId);
+    void clearInteractionFromConversation(const QString& convId, const uint64_t& interactionId);
     /**
      * Retry to send a message. In fact, will delete the previous interaction and resend a new one.
      * @param convId
@@ -264,9 +272,7 @@ public:
 
     void acceptTransfer(const QString& convUid, uint64_t interactionId);
 
-    void acceptTransfer(const QString& convUid,
-                        uint64_t interactionId,
-                        const QString& path);
+    void acceptTransfer(const QString& convUid, uint64_t interactionId, const QString& path);
 
     void cancelTransfer(const QString& convUid, uint64_t interactionId);
 
@@ -378,6 +384,36 @@ Q_SIGNALS:
      */
     void searchResultUpdated() const;
 
+    /**
+     * The following signals are intended for QAbtractListModel compatibility
+     */
+
+    /*!
+     * Emitted before conversations are inserted into the underlying queue
+     * @param position The starting row of the insertion
+     * @param rows The number of items inserted
+     */
+    void beginInsertRows(int position, int rows = 1) const;
+
+    //! Emitted once insertion is complete
+    void endInsertRows() const;
+
+    /*!
+     * Emitted before conversations are removed from the underlying queue
+     * @param position The starting row of the removal
+     * @param rows The number of items removed
+     */
+    void beginRemoveRows(int position, int rows = 1) const;
+
+    //! Emitted once removal is complete
+    void endRemoveRows() const;
+
+    /**
+     * Emitted once a conversation has been updated
+     * @param position
+     */
+    void dataChanged(int position) const;
+
 private:
     std::unique_ptr<ConversationModelPimpl> pimpl_;
 };
diff --git a/src/conversationmodel.cpp b/src/conversationmodel.cpp
index b6801ba6ae6d30ce2b429b72289860593d4001bd..be31a18f0de155befd6e95b4e739a2f958069645 100644
--- a/src/conversationmodel.cpp
+++ b/src/conversationmodel.cpp
@@ -331,6 +331,12 @@ ConversationModel::ConversationModel(const account::Info& owner,
 
 ConversationModel::~ConversationModel() {}
 
+const ConversationModel::ConversationQueue&
+ConversationModel::getConversations() const
+{
+    return pimpl_->conversations;
+}
+
 const ConversationModel::ConversationQueueProxy&
 ConversationModel::allFilteredConversations() const
 {
@@ -898,6 +904,7 @@ ConversationModel::sendMessage(const QString& uid, const QString& body)
             // The order has changed, informs the client to redraw the list
             pimpl_->invalidateModel();
             emit modelChanged();
+            Q_EMIT dataChanged(pimpl_->indexOf(convId));
         });
 
         if (isTemporary) {
@@ -1013,10 +1020,11 @@ ConversationModel::clearHistory(const QString& uid)
         std::lock_guard<std::mutex> lk(pimpl_->interactionsLocks[uid]);
         conversation.interactions.clear();
     }
-    storage::getHistory(pimpl_->db, conversation); // will contains "Conversation started"
+    storage::getHistory(pimpl_->db, conversation); // will contain "Conversation started"
 
     emit modelChanged();
     emit conversationCleared(uid);
+    Q_EMIT dataChanged(conversationIdx);
 }
 
 void
@@ -1079,8 +1087,9 @@ ConversationModel::clearInteractionFromConversation(const QString& convId,
         emit interactionRemoved(convId, interactionId);
     }
     if (lastInteractionUpdated) {
-        // last interaction as changed, so the order can changes.
+        // last interaction as changed, so the order can change.
         emit modelChanged();
+        Q_EMIT dataChanged(conversationIdx);
     }
 }
 
@@ -1242,6 +1251,7 @@ ConversationModel::clearUnreadInteractions(const QString& convId)
         pimpl_->conversations[conversationIdx].unreadMessages = 0;
         pimpl_->invalidateModel();
         emit conversationUpdated(convId);
+        Q_EMIT dataChanged(conversationIdx);
     }
 }
 
@@ -1686,7 +1696,10 @@ ConversationModelPimpl::slotContactAdded(const QString& contactUri)
 
     // delete temporary conversation if it exists and it has the uri of the added contact as uid
     if (indexOf(profileInfo.uri) >= 0) {
-        conversations.erase(conversations.begin() + indexOf(profileInfo.uri));
+        auto position = indexOf(profileInfo.uri);
+        Q_EMIT linked.beginRemoveRows(position);
+        conversations.erase(conversations.begin() + position);
+        Q_EMIT linked.endRemoveRows();
     }
     for (unsigned int i = 0; i < searchResults.size(); ++i) {
         if (searchResults.at(i).uid == profileInfo.uri)
@@ -1730,6 +1743,7 @@ ConversationModelPimpl::slotPendingContactAccepted(const QString& uri)
             }
             filteredConversations.invalidate();
             emit linked.newInteraction(convs[0], msgId, interaction);
+            Q_EMIT linked.dataChanged(convIdx);
         } catch (std::out_of_range& e) {
             qDebug() << "ConversationModelPimpl::slotContactAdded can't find contact";
         }
@@ -1745,7 +1759,10 @@ ConversationModelPimpl::slotContactRemoved(const QString& uri)
         return; // Not a contact
     }
     auto& conversationUid = conversations[conversationIdx].uid;
+
+    Q_EMIT linked.beginRemoveRows(conversationIdx);
     conversations.erase(conversations.begin() + conversationIdx);
+    Q_EMIT linked.endRemoveRows();
 
     invalidateModel();
     emit linked.modelChanged();
@@ -1761,6 +1778,7 @@ ConversationModelPimpl::slotContactModelUpdated(const QString& uri, bool needsSo
             auto& conversation = getConversationForPeerUri(uri, true).get();
             invalidateModel();
             emit linked.conversationUpdated(conversation.uid);
+            Q_EMIT linked.dataChanged(indexOf(conversation.uid));
         } catch (std::out_of_range&) {
             qDebug() << "contact updated for not existing conversation";
         }
@@ -1834,7 +1852,11 @@ ConversationModelPimpl::addConversationWith(const QString& convId, const QString
     }
 
     conversation.unreadMessages = getNumberOfUnreadMessagesFor(convId);
+
+    Q_EMIT linked.beginInsertRows(conversations.size());
     conversations.emplace_back(std::move(conversation));
+    Q_EMIT linked.endInsertRows();
+
     invalidateModel();
 }
 
@@ -1907,7 +1929,6 @@ ConversationModelPimpl::slotIncomingCall(const QString& fromId, const QString& c
     conversation.callId = callId;
 
     addOrUpdateCallMessage(callId, fromId);
-    invalidateModel();
     emit behaviorController.showIncomingCallView(linked.owner.id, conversation.uid);
 }
 
@@ -1934,6 +1955,7 @@ ConversationModelPimpl::slotCallStatusChanged(const QString& callId, int code)
                     conversation.callId = callId;
                     invalidateModel();
                     emit linked.conversationUpdated(conversation.uid);
+                    Q_EMIT linked.dataChanged(indexOf(conversation.uid));
                 }
             }
         } else if (call.status == call::Status::PEER_BUSY) {
@@ -1976,6 +1998,7 @@ ConversationModelPimpl::slotCallEnded(const QString& callId)
                 conversation.confId = ""; // The participant is detached
                 invalidateModel();
                 emit linked.conversationUpdated(conversation.uid);
+                Q_EMIT linked.dataChanged(indexOf(conversation.uid));
             }
     } catch (std::out_of_range& e) {
         qDebug() << "ConversationModelPimpl::slotCallEnded can't end inexistant call";
@@ -2026,6 +2049,7 @@ ConversationModelPimpl::addOrUpdateCallMessage(const QString& callId,
 
     invalidateModel();
     emit linked.modelChanged();
+    Q_EMIT linked.dataChanged(static_cast<int>(std::distance(conversations.begin(), conv_it)));
 }
 
 void
@@ -2109,6 +2133,7 @@ ConversationModelPimpl::addIncomingMessage(const QString& from,
 
     invalidateModel();
     emit linked.modelChanged();
+    Q_EMIT linked.dataChanged(conversationIdx);
 
     return msgId;
 }
@@ -2447,6 +2472,7 @@ ConversationModelPimpl::slotTransferStatusCreated(long long dringId, datatransfe
 
     invalidateModel();
     emit linked.modelChanged();
+    Q_EMIT linked.dataChanged(conversationIdx);
 }
 
 void