diff --git a/bin/dbus/cx.ring.Ring.ConfigurationManager.xml b/bin/dbus/cx.ring.Ring.ConfigurationManager.xml
index 2425d163c631a0f08891eca067fb4d7aaab59a1f..145ab265828cb4f2f395625bfee0f296983f5965 100644
--- a/bin/dbus/cx.ring.Ring.ConfigurationManager.xml
+++ b/bin/dbus/cx.ring.Ring.ConfigurationManager.xml
@@ -1787,6 +1787,18 @@
            <arg type="u" name="id" direction="out"/>
        </method>
 
+       <method name="countInteractions" tp:name-for-bindings="countInteractions">
+           <tp:added version="10.0.0"/>
+           <tp:docstring>
+               Get how many messages there is since an interaction ("" for initial commit)
+           </tp:docstring>
+           <arg type="s" name="accountId" direction="in"/>
+           <arg type="s" name="conversationId" direction="in"/>
+           <arg type="s" name="toId" direction="in"/>
+           <arg type="s" name="fromId" direction="in"/>
+           <arg type="u" name="count" direction="out"/>
+       </method>
+
        <signal name="mediaParametersChanged" tp:name-for-bindings="mediaParametersChanged">
            <tp:added version="2.3.0"/>
            <tp:docstring>
diff --git a/bin/dbus/dbusconfigurationmanager.cpp b/bin/dbus/dbusconfigurationmanager.cpp
index 6ff5f7315ccfc5c308337d435be70db4c89a4efe..9991a88f449026f162dcbeece77af57861123362 100644
--- a/bin/dbus/dbusconfigurationmanager.cpp
+++ b/bin/dbus/dbusconfigurationmanager.cpp
@@ -932,6 +932,15 @@ DBusConfigurationManager::loadConversationMessages(const std::string& accountId,
     return DRing::loadConversationMessages(accountId, conversationId, fromMessage, n);
 }
 
+uint32_t
+DBusConfigurationManager::countInteractions(const std::string& accountId,
+                                                 const std::string& conversationId,
+                                                 const std::string& toId,
+                                                 const std::string& fromId)
+{
+    return DRing::countInteractions(accountId, conversationId, toId, fromId);
+}
+
 bool
 DBusConfigurationManager::isAudioMeterActive(const std::string& id)
 {
diff --git a/bin/dbus/dbusconfigurationmanager.h b/bin/dbus/dbusconfigurationmanager.h
index 04a5a63ffcf29347635c4c9c2e06a761c6b645e4..ab425a6a859ee9f3291f94353eb15f37ac4c8089 100644
--- a/bin/dbus/dbusconfigurationmanager.h
+++ b/bin/dbus/dbusconfigurationmanager.h
@@ -283,6 +283,10 @@ public:
                                       const std::string& conversationId,
                                       const std::string& fromMessage,
                                       const uint32_t& n);
+    uint32_t countInteractions(const std::string& accountId,
+                                const std::string& conversationId,
+                                const std::string& toId,
+                                const std::string& fromId);
 };
 
 #endif // __RING_DBUSCONFIGURATIONMANAGER_H__
diff --git a/bin/jni/conversation.i b/bin/jni/conversation.i
index 042be6048f2af87574d25ad9fb7ec8e49ae8860f..7858045b8481522a7f354bcea27b2cfc66e11544 100644
--- a/bin/jni/conversation.i
+++ b/bin/jni/conversation.i
@@ -57,7 +57,7 @@ namespace DRing {
   // Message send/load
   void sendMessage(const std::string& accountId, const std::string& conversationId, const std::string& message, const std::string& parent);
   uint32_t loadConversationMessages(const std::string& accountId, const std::string& conversationId, const std::string& fromMessage, size_t n);
-
+  uint32_t countInteractions(const std::string& accountId, const std::string& conversationId, const std::string& toId, const std::string& fromId);
 }
 
 class ConversationCallback {
diff --git a/bin/nodejs/conversation.i b/bin/nodejs/conversation.i
index 042be6048f2af87574d25ad9fb7ec8e49ae8860f..45605ae27b36cd09ada9a5e0dd95afd7d5cbdc9d 100644
--- a/bin/nodejs/conversation.i
+++ b/bin/nodejs/conversation.i
@@ -57,6 +57,7 @@ namespace DRing {
   // Message send/load
   void sendMessage(const std::string& accountId, const std::string& conversationId, const std::string& message, const std::string& parent);
   uint32_t loadConversationMessages(const std::string& accountId, const std::string& conversationId, const std::string& fromMessage, size_t n);
+  uint32_t countInteractions(const std::string& accountId, const std::string& conversationId, const std::string& toId, const std::string& fromId);
 
 }
 
diff --git a/src/account.h b/src/account.h
index c0b6c28f8310dce17c86e6c16615dcbbb418ebe9..48a7ca128bc6ff7f4dd8d55624969c8c6f9fb1e0 100644
--- a/src/account.h
+++ b/src/account.h
@@ -319,6 +319,10 @@ public:
                                 const std::string& /*conversationId*/,
                                 const std::string& /*commitId*/) {};
 
+    virtual void onMessageDisplayed(const std::string& /*peer*/,
+                                    const std::string& /*conversationId*/,
+                                    const std::string& /*interactionId*/) {};
+
     // Invites
     virtual void onConversationRequest(const std::string& /*from*/, const Json::Value&) {};
     virtual void onNeedConversationRequest(const std::string& /*from*/,
diff --git a/src/client/conversation_interface.cpp b/src/client/conversation_interface.cpp
index fa36d42b7baf708de419cfa716115d4e88d7c2c2..0278a8950af0dd9b904a4347bd84de55f49f2d4a 100644
--- a/src/client/conversation_interface.cpp
+++ b/src/client/conversation_interface.cpp
@@ -146,4 +146,15 @@ loadConversationMessages(const std::string& accountId,
     return 0;
 }
 
+uint32_t
+countInteractions(const std::string& accountId,
+                       const std::string& conversationId,
+                       const std::string& toId,
+                       const std::string& fromId)
+{
+    if (auto acc = jami::Manager::instance().getAccount<jami::JamiAccount>(accountId))
+        return acc->countInteractions(conversationId, toId, fromId);
+    return 0;
+}
+
 } // namespace DRing
diff --git a/src/jami/conversation_interface.h b/src/jami/conversation_interface.h
index 4f577e7c9294b43b11a8ded8293622cc299aa6f1..ada5792746bd5ea59dcb4ac1e88eda94d1f9155c 100644
--- a/src/jami/conversation_interface.h
+++ b/src/jami/conversation_interface.h
@@ -69,6 +69,10 @@ DRING_PUBLIC uint32_t loadConversationMessages(const std::string& accountId,
                                                const std::string& conversationId,
                                                const std::string& fromMessage,
                                                size_t n);
+DRING_PUBLIC uint32_t countInteractions(const std::string& accountId,
+                                        const std::string& conversationId,
+                                        const std::string& toId,
+                                        const std::string& fromId);
 
 struct DRING_PUBLIC ConversationSignal
 {
diff --git a/src/jamidht/conversation.cpp b/src/jamidht/conversation.cpp
index be50f59ee8a80b75652ed7cccf15973a602ef64d..3654904236ba323555c12da7a5ceb41e96a6fe9c 100644
--- a/src/jamidht/conversation.cpp
+++ b/src/jamidht/conversation.cpp
@@ -158,8 +158,10 @@ public:
             conversationDataPath_ = fileutils::get_data_dir() + DIR_SEPARATOR_STR
                                     + shared->getAccountID() + DIR_SEPARATOR_STR
                                     + "conversation_data" + DIR_SEPARATOR_STR + repository_->id();
-            fetchedPath_ = fileutils::get_data_dir() + DIR_SEPARATOR_STR + "fetched";
+            fetchedPath_ = conversationDataPath_ + DIR_SEPARATOR_STR + "fetched";
+            lastDisplayedPath_ = conversationDataPath_ + DIR_SEPARATOR_STR + "lastDisplayed";
             loadFetched();
+            loadLastDisplayed();
         }
     }
 
@@ -270,6 +272,26 @@ public:
         msgpack::pack(file, fetchedDevices_);
     }
 
+    void loadLastDisplayed()
+    {
+        try {
+            // read file
+            auto file = fileutils::loadFile(lastDisplayedPath_);
+            // load values
+            msgpack::object_handle oh = msgpack::unpack((const char*) file.data(), file.size());
+            std::lock_guard<std::mutex> lk {lastDisplayedMtx_};
+            oh.get().convert(lastDisplayed_);
+        } catch (const std::exception& e) {
+            return;
+        }
+    }
+
+    void saveLastDisplayed()
+    {
+        std::ofstream file(lastDisplayedPath_, std::ios::trunc | std::ios::binary);
+        msgpack::pack(file, lastDisplayed_);
+    }
+
     std::unique_ptr<ConversationRepository> repository_;
     std::weak_ptr<JamiAccount> account_;
     std::atomic_bool isRemoving_ {false};
@@ -289,6 +311,9 @@ public:
     std::string fetchedPath_ {};
     std::mutex fetchedDevicesMtx_ {};
     std::set<std::string> fetchedDevices_ {};
+    std::string lastDisplayedPath_ {};
+    std::mutex lastDisplayedMtx_ {};
+    std::map<std::string, std::string> lastDisplayed_ {};
 };
 
 bool
@@ -554,12 +579,20 @@ Conversation::getMembers(bool includeInvited) const
     if (!shared)
         return result;
     auto members = pimpl_->repository_->members();
+    std::lock_guard<std::mutex> lk(pimpl_->lastDisplayedMtx_);
     for (const auto& member : members) {
         if (member.role == MemberRole::BANNED)
             continue;
         if (member.role == MemberRole::INVITED && !includeInvited)
             continue;
-        result.emplace_back(member.map());
+        auto mm = member.map();
+        std::string lastDisplayed;
+        auto itDisplayed = pimpl_->lastDisplayed_.find(member.uri);
+        if (itDisplayed != pimpl_->lastDisplayed_.end()) {
+            lastDisplayed = itDisplayed->second;
+        }
+        mm["lastDisplayed"] = std::move(lastDisplayed);
+        result.emplace_back(std::move(mm));
     }
     return result;
 }
@@ -1070,4 +1103,19 @@ Conversation::hasFetched(const std::string& deviceId)
     pimpl_->saveFetched();
 }
 
+void
+Conversation::setMessageDisplayed(const std::string& uri, const std::string& interactionId)
+{
+    std::lock_guard<std::mutex> lk(pimpl_->lastDisplayedMtx_);
+    pimpl_->lastDisplayed_[uri] = interactionId;
+    pimpl_->saveLastDisplayed();
+}
+
+uint32_t
+Conversation::countInteractions(const std::string& toId, const std::string& fromId) const
+{
+    // Log but without content to avoid costly convertions.
+    return pimpl_->repository_->log(fromId, toId, false, true).size();
+}
+
 } // namespace jami
\ No newline at end of file
diff --git a/src/jamidht/conversation.h b/src/jamidht/conversation.h
index d52495f60452f51861a821debb5cddbbc131ff80..4deacf7f5cb7fccb69f4c4426cd6f98bf5fa6e6f 100644
--- a/src/jamidht/conversation.h
+++ b/src/jamidht/conversation.h
@@ -307,6 +307,21 @@ public:
      */
     void hasFetched(const std::string& deviceId);
 
+    /**
+     * Store last read commit (returned in getMembers)
+     * @param uri               Of the member
+     * @param interactionId     Last interaction displayed
+     */
+    void setMessageDisplayed(const std::string& uri, const std::string& interactionId);
+
+    /**
+     * Retrieve how many interactions there is from HEAD to interactionId
+     * @param toId     "" for getting the whole history
+     * @param fromId   "" => HEAD
+     * @return number of interactions since interactionId
+     */
+    uint32_t countInteractions(const std::string& toId, const std::string& fromId = "") const;
+
 private:
     std::shared_ptr<Conversation> shared()
     {
diff --git a/src/jamidht/conversationrepository.cpp b/src/jamidht/conversationrepository.cpp
index 585028fb309625a70ed5537dfabd91f2358e9bb6..88f8015141872b895278bb26a5c0d4cf7a087f8c 100644
--- a/src/jamidht/conversationrepository.cpp
+++ b/src/jamidht/conversationrepository.cpp
@@ -114,7 +114,8 @@ public:
     std::vector<ConversationCommit> log(const std::string& from,
                                         const std::string& to,
                                         unsigned n,
-                                        bool logIfNotFound = false) const;
+                                        bool logIfNotFound = false,
+                                        bool fastLog = false) const;
     std::optional<std::string> linearizedParent(const std::string& commitId) const;
 
     GitObject fileAtTree(const std::string& path, const GitTree& tree) const;
@@ -1486,7 +1487,8 @@ std::vector<ConversationCommit>
 ConversationRepository::Impl::log(const std::string& from,
                                   const std::string& to,
                                   unsigned n,
-                                  bool logIfNotFound) const
+                                  bool logIfNotFound,
+                                  bool fastLog) const
 {
     std::vector<ConversationCommit> commits {};
 
@@ -1533,6 +1535,12 @@ ConversationRepository::Impl::log(const std::string& from,
             break;
         }
 
+        if (fastLog) {
+            // Used to only count commit
+            commits.emplace(commits.end(), ConversationCommit {});
+            continue;
+        }
+
         const git_signature* sig = git_commit_author(commit.get());
         GitAuthor author;
         author.name = sig->name;
@@ -2372,9 +2380,12 @@ ConversationRepository::logN(const std::string& last, unsigned n, bool logIfNotF
 }
 
 std::vector<ConversationCommit>
-ConversationRepository::log(const std::string& from, const std::string& to, bool logIfNotFound) const
+ConversationRepository::log(const std::string& from,
+                            const std::string& to,
+                            bool logIfNotFound,
+                            bool fastLog) const
 {
-    return pimpl_->log(from, to, 0, logIfNotFound);
+    return pimpl_->log(from, to, 0, logIfNotFound, fastLog);
 }
 
 std::optional<ConversationCommit>
diff --git a/src/jamidht/conversationrepository.h b/src/jamidht/conversationrepository.h
index 692b9526e0402b68b546ed3f89ce297cda5e922f..63e747d957e22e0b615c3829b47247454b62ccb9 100644
--- a/src/jamidht/conversationrepository.h
+++ b/src/jamidht/conversationrepository.h
@@ -185,6 +185,7 @@ public:
      * @param last              last commit (default empty)
      * @param n                 Max commits number to get (default: 0)
      * @param logIfNotFound     Log if commit is found (false for detect commit before pulling)
+     * @param fastLog           Used to count commits. This will just generate empty ConversationCommit
      * @return a list of commits
      */
     std::vector<ConversationCommit> logN(const std::string& last = "",
@@ -192,7 +193,8 @@ public:
                                          bool logIfNotFound = true) const;
     std::vector<ConversationCommit> log(const std::string& from = "",
                                         const std::string& to = "",
-                                        bool logIfNotFound = true) const;
+                                        bool logIfNotFound = true,
+                                        bool fastLog = false) const;
     std::optional<ConversationCommit> getCommit(const std::string& commitId,
                                                 bool logIfNotFound = true) const;
 
diff --git a/src/jamidht/jamiaccount.cpp b/src/jamidht/jamiaccount.cpp
index 25eb0ae4f9a8ece3e2f3c589550a8b2959ddeb65..53176ef2ab87b04f46e5093cfc0b9cb58dbc2f46 100644
--- a/src/jamidht/jamiaccount.cpp
+++ b/src/jamidht/jamiaccount.cpp
@@ -4176,6 +4176,19 @@ JamiAccount::loadConversationMessages(const std::string& conversationId,
     return id;
 }
 
+uint32_t
+JamiAccount::countInteractions(const std::string& convId,
+                               const std::string& toId,
+                               const std::string& fromId) const
+{
+    std::lock_guard<std::mutex> lk(conversationsMtx_);
+    auto conversation = conversations_.find(convId);
+    if (conversation != conversations_.end() && conversation->second) {
+        return conversation->second->countInteractions(toId, fromId);
+    }
+    return 0;
+}
+
 void
 JamiAccount::onNewGitCommit(const std::string& peer,
                             const std::string& deviceId,
@@ -4196,6 +4209,18 @@ JamiAccount::onNewGitCommit(const std::string& peer,
     fetchNewCommits(peer, deviceId, conversationId, commitId);
 }
 
+void
+JamiAccount::onMessageDisplayed(const std::string& peer,
+                                const std::string& conversationId,
+                                const std::string& interactionId)
+{
+    std::unique_lock<std::mutex> lk(conversationsMtx_);
+    auto conversation = conversations_.find(conversationId);
+    if (conversation != conversations_.end() && conversation->second) {
+        conversation->second->setMessageDisplayed(peer, interactionId);
+    }
+}
+
 void
 JamiAccount::fetchNewCommits(const std::string& peer,
                              const std::string& deviceId,
diff --git a/src/jamidht/jamiaccount.h b/src/jamidht/jamiaccount.h
index 88a528769e9a01daa28471697f391d0d53e6e6aa..735b9b4a3ac5fe6870870c08cbe8006f2130346d 100644
--- a/src/jamidht/jamiaccount.h
+++ b/src/jamidht/jamiaccount.h
@@ -554,11 +554,25 @@ public:
                                       const std::string& fromMessage = "",
                                       size_t n = 0);
 
+    /**
+     * Retrieve how many interactions there is from HEAD to interactionId
+     * @param convId
+     * @param interactionId     "" for getting the whole history
+     * @return number of interactions since interactionId
+     */
+    uint32_t countInteractions(const std::string& convId,
+                                const std::string& toId,
+                                const std::string& fromId) const;
+
     // Received a new commit notification
     void onNewGitCommit(const std::string& peer,
                         const std::string& deviceId,
                         const std::string& conversationId,
                         const std::string& commitId) override;
+    // Received that a peer displayed a message
+    void onMessageDisplayed(const std::string& peer,
+                            const std::string& conversationId,
+                            const std::string& interactionId) override;
     /**
      * Pull remote device (do not do it if commitId is already in the current repo)
      * @param peer              Contact URI
diff --git a/src/sip/sipaccountbase.cpp b/src/sip/sipaccountbase.cpp
index 38bf1ef38fc852321819adbc37afa0ab06ebc995..c563cbb38dea68790520801037d4ff97c8e9e006 100644
--- a/src/sip/sipaccountbase.cpp
+++ b/src/sip/sipaccountbase.cpp
@@ -613,6 +613,7 @@ SIPAccountBase::onTextMessage(const std::string& id,
                 if (conversationId.empty()) // Old method
                     messageEngine_.onMessageDisplayed(from, from_hex_string(messageId), isDisplayed);
                 else if (isDisplayed) {
+                    onMessageDisplayed(from, conversationId, messageId);
                     JAMI_DBG() << "[message " << messageId << "] Displayed by peer";
                     emitSignal<DRing::ConfigurationSignal::AccountMessageStatusChanged>(
                         accountID_,
@@ -704,6 +705,8 @@ SIPAccountBase::setMessageDisplayed(const std::string& conversationUri,
     std::string conversationId = {};
     if (uri.scheme() == Uri::Scheme::SWARM)
         conversationId = uri.authority();
+    if (!conversationId.empty())
+        onMessageDisplayed(getUsername(), conversationId, messageId);
     if (status == (int) DRing::Account::MessageStates::DISPLAYED)
         sendInstantMessage(uri.authority(),
                            {{MIME_TYPE_IMDN, getDisplayed(conversationId, messageId)}});
diff --git a/test/unitTest/conversation/conversation.cpp b/test/unitTest/conversation/conversation.cpp
index 988963c543be22a1c50f269298f758ae1f10c105..4ca231c915e54cdf4632b3066af65b006f97131f 100644
--- a/test/unitTest/conversation/conversation.cpp
+++ b/test/unitTest/conversation/conversation.cpp
@@ -155,6 +155,7 @@ private:
     void testDoNotLoadIncorrectConversation();
     void testSyncingWhileAccepting();
     void testGetConversationsMembersWhileSyncing();
+    void testCountInteractions();
 
     CPPUNIT_TEST_SUITE(ConversationTest);
     CPPUNIT_TEST(testCreateConversation);
@@ -212,6 +213,7 @@ private:
     CPPUNIT_TEST(testDoNotLoadIncorrectConversation);
     CPPUNIT_TEST(testSyncingWhileAccepting);
     CPPUNIT_TEST(testGetConversationsMembersWhileSyncing);
+    CPPUNIT_TEST(testCountInteractions);
     CPPUNIT_TEST_SUITE_END();
 };
 
@@ -1250,8 +1252,43 @@ ConversationTest::testSetMessageDisplayed()
     CPPUNIT_ASSERT(
         cv.wait_for(lk, std::chrono::seconds(30), [&]() { return memberMessageGenerated; }));
 
+    // Last displayed messages should not be set yet
+    auto membersInfos = bobAccount->getConversationMembers(convId);
+    CPPUNIT_ASSERT(std::find_if(membersInfos.begin(),
+                                membersInfos.end(),
+                                [&](auto infos) {
+                                    return infos["uri"] == aliceUri && infos["lastDisplayed"] == "";
+                                })
+                   != membersInfos.end());
+    membersInfos = aliceAccount->getConversationMembers(convId);
+    CPPUNIT_ASSERT(std::find_if(membersInfos.begin(),
+                                membersInfos.end(),
+                                [&](auto infos) {
+                                    return infos["uri"] == aliceUri && infos["lastDisplayed"] == "";
+                                })
+                   != membersInfos.end());
+
     aliceAccount->setMessageDisplayed("swarm:" + convId, convId, 3);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return msgDisplayed; }));
+
+    // Now, the last displayed message should be updated in member's infos (both sides)
+    membersInfos = bobAccount->getConversationMembers(convId);
+    CPPUNIT_ASSERT(std::find_if(membersInfos.begin(),
+                                membersInfos.end(),
+                                [&](auto infos) {
+                                    return infos["uri"] == aliceUri
+                                           && infos["lastDisplayed"] == convId;
+                                })
+                   != membersInfos.end());
+    membersInfos = aliceAccount->getConversationMembers(convId);
+    CPPUNIT_ASSERT(std::find_if(membersInfos.begin(),
+                                membersInfos.end(),
+                                [&](auto infos) {
+                                    return infos["uri"] == aliceUri
+                                           && infos["lastDisplayed"] == convId;
+                                })
+                   != membersInfos.end());
+
     DRing::unregisterSignalHandlers();
 }
 
@@ -4453,6 +4490,37 @@ ConversationTest::testGetConversationsMembersWhileSyncing()
                    != members.end());
 }
 
+void
+ConversationTest::testCountInteractions()
+{
+    auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
+    auto convId = aliceAccount->startConversation();
+    std::mutex mtx;
+    std::unique_lock<std::mutex> lk {mtx};
+    std::condition_variable cv;
+
+    std::string msgId1 = "", msgId2 = "", msgId3 = "";
+    aliceAccount->sendMessage(convId, "1"s, "", "text/plain", true, [&](bool, std::string commitId) {
+        msgId1 = commitId;
+        cv.notify_one();
+    });
+    CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&] { return !msgId1.empty(); }));
+    aliceAccount->sendMessage(convId, "2"s, "", "text/plain", true, [&](bool, std::string commitId) {
+        msgId2 = commitId;
+        cv.notify_one();
+    });
+    CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&] { return !msgId2.empty(); }));
+    aliceAccount->sendMessage(convId, "3"s, "", "text/plain", true, [&](bool, std::string commitId) {
+        msgId3 = commitId;
+        cv.notify_one();
+    });
+    CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&] { return !msgId3.empty(); }));
+
+    CPPUNIT_ASSERT(DRing::countInteractions(aliceId, convId, "", "") == 4 /* 3 + initial */);
+    CPPUNIT_ASSERT(DRing::countInteractions(aliceId, convId, msgId3, "") == 0);
+    CPPUNIT_ASSERT(DRing::countInteractions(aliceId, convId, msgId2, "") == 1);
+}
+
 } // namespace test
 } // namespace jami