diff --git a/bin/dbus/cx.ring.Ring.ConfigurationManager.xml b/bin/dbus/cx.ring.Ring.ConfigurationManager.xml
index 307856c836a20da372a5522d3a3cfae8b0e2513b..92872d1477c0ec23237997347c056edc51bd1ae8 100644
--- a/bin/dbus/cx.ring.Ring.ConfigurationManager.xml
+++ b/bin/dbus/cx.ring.Ring.ConfigurationManager.xml
@@ -1799,6 +1799,23 @@
            <arg type="u" name="count" direction="out"/>
        </method>
 
+       <method name="searchConversation" tp:name-for-bindings="searchConversation">
+           <tp:added version="13.4.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="author" direction="in"/>
+           <arg type="s" name="lastId" direction="in"/>
+           <arg type="s" name="regexSearch" direction="in"/>
+           <arg type="s" name="type" direction="in"/>
+           <arg type="x" name="after" direction="in"/>
+           <arg type="x" name="before" direction="in"/>
+           <arg type="u" name="maxResult" direction="in"/>
+           <arg type="u" name="searchId" direction="out"/>
+       </method>
+
        <signal name="mediaParametersChanged" tp:name-for-bindings="mediaParametersChanged">
            <tp:added version="2.3.0"/>
            <tp:docstring>
@@ -1900,6 +1917,34 @@
            </arg>
        </signal>
 
+       <signal name="messagesFound" tp:name-for-bindings="messagesFound">
+           <tp:added version="10.0.0"/>
+           <tp:docstring>
+               Notify clients when messages matching a regex are found
+           </tp:docstring>
+           <arg type="u" name="id">
+               <tp:docstring>
+                   Id of the related loadConversationMessages's request
+               </tp:docstring>
+           </arg>
+           <arg type="s" name="account_id">
+               <tp:docstring>
+                   Account id related
+               </tp:docstring>
+           </arg>
+           <arg type="s" name="conversation_id">
+               <tp:docstring>
+                   Conversation id
+               </tp:docstring>
+           </arg>
+           <annotation name="org.qtproject.QtDBus.QtTypeName.Out3" value="VectorMapStringString"/>
+           <arg type="aa{ss}" name="messages">
+               <tp:docstring>
+                    Messages of the conversation
+               </tp:docstring>
+           </arg>
+       </signal>
+
        <signal name="messageReceived" tp:name-for-bindings="messageReceived">
            <tp:added version="10.0.0"/>
            <tp:docstring>
diff --git a/bin/dbus/dbusclient.cpp b/bin/dbus/dbusclient.cpp
index 164bd6155d32d8371c8b2cc487eace0e58d8d009..5eac7e20946579f3e39016beab05487263c0d112 100644
--- a/bin/dbus/dbusclient.cpp
+++ b/bin/dbus/dbusclient.cpp
@@ -299,6 +299,8 @@ DBusClient::initLibrary(int flags)
     const std::map<std::string, SharedCallback> convEvHandlers = {
         exportable_callback<ConversationSignal::ConversationLoaded>(
             bind(&DBusConfigurationManager::conversationLoaded, confM, _1, _2, _3, _4)),
+        exportable_callback<ConversationSignal::MessagesFound>(
+            bind(&DBusConfigurationManager::messagesFound, confM, _1, _2, _3, _4)),
         exportable_callback<ConversationSignal::MessageReceived>(
             bind(&DBusConfigurationManager::messageReceived, confM, _1, _2, _3)),
         exportable_callback<ConversationSignal::ConversationProfileUpdated>(
diff --git a/bin/dbus/dbusconfigurationmanager.cpp b/bin/dbus/dbusconfigurationmanager.cpp
index 3fffa78e705174cb03cd4a282d56cf5d729c4e1e..3ebf647c7a6047f5e3f546312030348305168254 100644
--- a/bin/dbus/dbusconfigurationmanager.cpp
+++ b/bin/dbus/dbusconfigurationmanager.cpp
@@ -939,6 +939,28 @@ DBusConfigurationManager::countInteractions(const std::string& accountId,
     return DRing::countInteractions(accountId, conversationId, toId, fromId, authorUri);
 }
 
+uint32_t
+DBusConfigurationManager::searchConversation(const std::string& accountId,
+                                             const std::string& conversationId,
+                                             const std::string& author,
+                                             const std::string& lastId,
+                                             const std::string& regexSearch,
+                                             const std::string& type,
+                                             const int64_t& after,
+                                             const int64_t& before,
+                                             const uint32_t& maxResult)
+{
+    return DRing::searchConversation(accountId,
+                                     conversationId,
+                                     author,
+                                     lastId,
+                                     regexSearch,
+                                     type,
+                                     after,
+                                     before,
+                                     maxResult);
+}
+
 bool
 DBusConfigurationManager::isAudioMeterActive(const std::string& id)
 {
diff --git a/bin/dbus/dbusconfigurationmanager.h b/bin/dbus/dbusconfigurationmanager.h
index 86357d49c25972fc5c23b02286d6f8d38a3893b8..1316b9a7236285c5499a91bdc102923d68a3d50e 100644
--- a/bin/dbus/dbusconfigurationmanager.h
+++ b/bin/dbus/dbusconfigurationmanager.h
@@ -290,6 +290,15 @@ public:
                                const std::string& toId,
                                const std::string& fromId,
                                const std::string& authorUri);
+    uint32_t searchConversation(const std::string& accountId,
+                                const std::string& conversationId,
+                                const std::string& author,
+                                const std::string& lastId,
+                                const std::string& regexSearch,
+                                const std::string& type,
+                                const int64_t& after,
+                                const int64_t& before,
+                                const uint32_t& maxResult);
 };
 
 #endif // __RING_DBUSCONFIGURATIONMANAGER_H__
diff --git a/bin/jni/conversation.i b/bin/jni/conversation.i
index d076d8206ad12bf593ffc9bc28ab62b3d622c71c..cfd130f80e3a306f0f88da201cfc78c4358fc854 100644
--- a/bin/jni/conversation.i
+++ b/bin/jni/conversation.i
@@ -26,6 +26,7 @@ class ConversationCallback {
 public:
     virtual ~ConversationCallback(){}
     virtual void conversationLoaded(uint32_t /* id */, const std::string& /*accountId*/, const std::string& /* conversationId */, std::vector<std::map<std::string, std::string>> /*messages*/){}
+    virtual void messagesFound(uint32_t /* id */, const std::string& /*accountId*/, const std::string& /* conversationId */, std::vector<std::map<std::string, std::string>> /*messages*/){}
     virtual void messageReceived(const std::string& /*accountId*/, const std::string& /* conversationId */, std::map<std::string, std::string> /*message*/){}
     virtual void conversationProfileUpdated(const std::string& /*accountId*/, const std::string& /* conversationId */, std::map<std::string, std::string> /*profile*/){}
     virtual void conversationRequestReceived(const std::string& /*accountId*/, const std::string& /* conversationId */, std::map<std::string, std::string> /*metadatas*/){}
@@ -61,12 +62,22 @@ namespace DRing {
   uint32_t loadConversationMessages(const std::string& accountId, const std::string& conversationId, const std::string& fromMessage, size_t n);
   uint32_t loadConversationUntil(const std::string& accountId, const std::string& conversationId, const std::string& fromMessage, const std::string& toMessage);
   uint32_t countInteractions(const std::string& accountId, const std::string& conversationId, const std::string& toId, const std::string& fromId, const std::string& authorUri);
+  uint32_t searchConversation(const std::string& accountId,
+           const std::string& conversationId,
+           const std::string& author,
+           const std::string& lastId,
+           const std::string& regexSearch,
+           const std::string& type,
+           const int64_t& after,
+           const int64_t& before,
+           const uint32_t& maxResult);
 }
 
 class ConversationCallback {
 public:
     virtual ~ConversationCallback(){}
     virtual void conversationLoaded(uint32_t /* id */, const std::string& /*accountId*/, const std::string& /* conversationId */, std::vector<std::map<std::string, std::string>> /*messages*/){}
+    virtual void messagesFound(uint32_t /* id */, const std::string& /*accountId*/, const std::string& /* conversationId */, std::vector<std::map<std::string, std::string>> /*messages*/){}
     virtual void messageReceived(const std::string& /*accountId*/, const std::string& /* conversationId */, std::map<std::string, std::string> /*message*/){}
     virtual void conversationProfileUpdated(const std::string& /*accountId*/, const std::string& /* conversationId */, std::map<std::string, std::string> /*profile*/){}
     virtual void conversationRequestReceived(const std::string& /*accountId*/, const std::string& /* conversationId */, std::map<std::string, std::string> /*metadatas*/){}
diff --git a/bin/jni/jni_interface.i b/bin/jni/jni_interface.i
index 80645527a225a48f3f524a017f2e6846d9c83bac..b0dcb92fcb94438576bf9c0dd6aa51e6fab9f336 100644
--- a/bin/jni/jni_interface.i
+++ b/bin/jni/jni_interface.i
@@ -320,6 +320,7 @@ void init(ConfigurationCallback* confM, Callback* callM, PresenceCallback* presM
 
     const std::map<std::string, SharedCallback> conversationHandlers = {
         exportable_callback<ConversationSignal::ConversationLoaded>(bind(&ConversationCallback::conversationLoaded, convM, _1, _2, _3, _4)),
+        exportable_callback<ConversationSignal::MessagesFound>(bind(&ConversationCallback::messagesFound, convM, _1, _2, _3, _4)),
         exportable_callback<ConversationSignal::MessageReceived>(bind(&ConversationCallback::messageReceived, convM, _1, _2, _3)),
         exportable_callback<ConversationSignal::ConversationProfileUpdated>(bind(&ConversationCallback::conversationProfileUpdated, convM, _1, _2, _3)),
         exportable_callback<ConversationSignal::ConversationRequestReceived>(bind(&ConversationCallback::conversationRequestReceived, convM, _1, _2, _3)),
diff --git a/bin/nodejs/conversation.i b/bin/nodejs/conversation.i
index c63ed3bf7aba6102cc2e6295ed609b63d1892e64..8ae3b712f7b61e33d53a93068b642f10f970334f 100644
--- a/bin/nodejs/conversation.i
+++ b/bin/nodejs/conversation.i
@@ -26,6 +26,7 @@ class ConversationCallback {
 public:
     virtual ~ConversationCallback(){}
     virtual void conversationLoaded(uint32_t /* id */, const std::string& /*accountId*/, const std::string& /* conversationId */, std::vector<std::map<std::string, std::string>> /*messages*/){}
+    virtual void messagesFound(uint32_t /* id */, const std::string& /*accountId*/, const std::string& /* conversationId */, std::vector<std::map<std::string, std::string>> /*messages*/){}
     virtual void messageReceived(const std::string& /*accountId*/, const std::string& /* conversationId */, std::map<std::string, std::string> /*message*/){}
     virtual void conversationProfileUpdated(const std::string& /*accountId*/, const std::string& /* conversationId */, std::map<std::string, std::string> /*profile*/){}
     virtual void conversationRequestReceived(const std::string& /*accountId*/, const std::string& /* conversationId */, std::map<std::string, std::string> /*metadatas*/){}
@@ -61,6 +62,15 @@ namespace DRing {
   uint32_t loadConversationMessages(const std::string& accountId, const std::string& conversationId, const std::string& fromMessage, size_t n);
   uint32_t loadConversationUntil(const std::string& accountId, const std::string& conversationId, const std::string& fromMessage, const std::string& toMessage);
   uint32_t countInteractions(const std::string& accountId, const std::string& conversationId, const std::string& toId, const std::string& fromId, const std::string& authorUri);
+  uint32_t searchConversation(const std::string& accountId,
+           const std::string& conversationId,
+           const std::string& author,
+           const std::string& lastId,
+           const std::string& regexSearch,
+           const std::string& type,
+           const int64_t& after,
+           const int64_t& before,
+           const uint32_t& maxResult);
 
 }
 
@@ -68,6 +78,7 @@ class ConversationCallback {
 public:
     virtual ~ConversationCallback(){}
     virtual void conversationLoaded(uint32_t /* id */, const std::string& /*accountId*/, const std::string& /* conversationId */, std::vector<std::map<std::string, std::string>> /*messages*/){}
+    virtual void messagesFound(uint32_t /* id */, const std::string& /*accountId*/, const std::string& /* conversationId */, std::vector<std::map<std::string, std::string>> /*messages*/){}
     virtual void messageReceived(const std::string& /*accountId*/, const std::string& /* conversationId */, std::map<std::string, std::string> /*message*/){}
     virtual void conversationProfileUpdated(const std::string& /*accountId*/, const std::string& /* conversationId */, std::map<std::string, std::string> /*profile*/){}
     virtual void conversationRequestReceived(const std::string& /*accountId*/, const std::string& /* conversationId */, std::map<std::string, std::string> /*metadatas*/){}
diff --git a/bin/nodejs/nodejs_interface.i b/bin/nodejs/nodejs_interface.i
index 7084f08444be4770d65935cadbdfa2dfac8b902b..6c2021bf3a14c1033633956a68caf4e86aa1b623 100644
--- a/bin/nodejs/nodejs_interface.i
+++ b/bin/nodejs/nodejs_interface.i
@@ -146,6 +146,7 @@ void init(const SWIGV8_VALUE& funcMap){
 
     const std::map<std::string, SharedCallback> conversationHandlers = {
         exportable_callback<ConversationSignal::ConversationLoaded>(bind(&conversationLoaded, _1, _2, _3, _4)),
+        exportable_callback<ConversationSignal::MessagesFound>(bind(&messagesFound, _1, _2, _3, _4)),
         exportable_callback<ConversationSignal::MessageReceived>(bind(&messageReceived, _1, _2, _3)),
         exportable_callback<ConversationSignal::ConversationProfileUpdated>(bind(&conversationProfileUpdated, _1, _2, _3)),
         exportable_callback<ConversationSignal::ConversationRequestReceived>(bind(&conversationRequestReceived, _1, _2, _3)),
diff --git a/src/client/conversation_interface.cpp b/src/client/conversation_interface.cpp
index 9ce74993ebe52551c07330070cd90accc7883bdf..67a4a6404c39855688fb0a60404f61e6557c89a2 100644
--- a/src/client/conversation_interface.cpp
+++ b/src/client/conversation_interface.cpp
@@ -185,4 +185,30 @@ countInteractions(const std::string& accountId,
     return 0;
 }
 
+uint32_t
+searchConversation(const std::string& accountId,
+                   const std::string& conversationId,
+                   const std::string& author,
+                   const std::string& lastId,
+                   const std::string& regexSearch,
+                   const std::string& type,
+                   const int64_t& after,
+                   const int64_t& before,
+                   const uint32_t& maxResult)
+{
+    uint32_t res = 0;
+    jami::Filter filter {author, lastId, regexSearch, type, after, before, maxResult};
+    for (const auto& accId : jami::Manager::instance().getAccountList()) {
+        if (!accountId.empty() && accId != accountId)
+            continue;
+        if (auto acc = jami::Manager::instance().getAccount<jami::JamiAccount>(accId)) {
+            res = std::uniform_int_distribution<uint32_t>()(acc->rand);
+            if (auto convModule = acc->convModule()) {
+                convModule->search(res, conversationId, filter);
+            }
+        }
+    }
+    return res;
+}
+
 } // namespace DRing
diff --git a/src/client/ring_signal.cpp b/src/client/ring_signal.cpp
index b31cc746f432fa2d3bd07be7fc76b762ec6e4e17..53fd6858e48a3a9237dc1a56a6350c3c2910cc29 100644
--- a/src/client/ring_signal.cpp
+++ b/src/client/ring_signal.cpp
@@ -127,6 +127,7 @@ getSignalHandlers()
 
         /* Conversation */
         exported_callback<DRing::ConversationSignal::ConversationLoaded>(),
+        exported_callback<DRing::ConversationSignal::MessagesFound>(),
         exported_callback<DRing::ConversationSignal::MessageReceived>(),
         exported_callback<DRing::ConversationSignal::ConversationProfileUpdated>(),
         exported_callback<DRing::ConversationSignal::ConversationRequestReceived>(),
diff --git a/src/jami/conversation_interface.h b/src/jami/conversation_interface.h
index 931b1fc6336ae632f4ad010307e4afd40910c9f6..bc146e8bab47898f27ad4dab6ca922093d1bca4f 100644
--- a/src/jami/conversation_interface.h
+++ b/src/jami/conversation_interface.h
@@ -78,6 +78,15 @@ DRING_PUBLIC uint32_t countInteractions(const std::string& accountId,
                                         const std::string& toId,
                                         const std::string& fromId,
                                         const std::string& authorUri);
+DRING_PUBLIC uint32_t searchConversation(const std::string& accountId,
+                                         const std::string& conversationId,
+                                         const std::string& author,
+                                         const std::string& lastId,
+                                         const std::string& regexSearch,
+                                         const std::string& type,
+                                         const int64_t& after,
+                                         const int64_t& before,
+                                         const uint32_t& maxResult);
 
 struct DRING_PUBLIC ConversationSignal
 {
@@ -89,6 +98,14 @@ struct DRING_PUBLIC ConversationSignal
                              const std::string& /* conversationId */,
                              std::vector<std::map<std::string, std::string>> /*messages*/);
     };
+    struct DRING_PUBLIC MessagesFound
+    {
+        constexpr static const char* name = "MessagesFound";
+        using cb_type = void(uint32_t /* id */,
+                             const std::string& /*accountId*/,
+                             const std::string& /* conversationId */,
+                             std::vector<std::map<std::string, std::string>> /*messages*/);
+    };
     struct DRING_PUBLIC MessageReceived
     {
         constexpr static const char* name = "MessageReceived";
diff --git a/src/jamidht/conversation.cpp b/src/jamidht/conversation.cpp
index c9e11216af554271e856439b5ed41602987b9cfd..e8ad1b94465c73b15ae1e1c70df11dc8e9747a8a 100644
--- a/src/jamidht/conversation.cpp
+++ b/src/jamidht/conversation.cpp
@@ -193,7 +193,7 @@ public:
                 convcommits.emplace_back(*commit);
             }
         }
-        announce(convCommitToMap(convcommits));
+        announce(repository_->convCommitToMap(convcommits));
     }
 
     void announce(const std::vector<std::map<std::string, std::string>>& commits) const
@@ -347,10 +347,6 @@ public:
     std::unique_ptr<ConversationRepository> repository_;
     std::weak_ptr<JamiAccount> account_;
     std::atomic_bool isRemoving_ {false};
-    std::vector<std::map<std::string, std::string>> convCommitToMap(
-        const std::vector<ConversationCommit>& commits) const;
-    std::optional<std::map<std::string, std::string>> convCommitToMap(
-        const ConversationCommit& commit) const;
     std::vector<std::map<std::string, std::string>> loadMessages(const std::string& fromMessage = "",
                                                                  const std::string& toMessage = "",
                                                                  size_t n = 0);
@@ -397,81 +393,6 @@ Conversation::Impl::repoPath() const
            + DIR_SEPARATOR_STR + "conversations" + DIR_SEPARATOR_STR + repository_->id();
 }
 
-std::optional<std::map<std::string, std::string>>
-Conversation::Impl::convCommitToMap(const ConversationCommit& commit) const
-{
-    if (!repository_)
-        return std::nullopt;
-    auto authorDevice = commit.author.email;
-    auto authorId = repository_->uriFromDevice(authorDevice);
-    if (authorId == "")
-        return std::nullopt;
-
-    std::string parents;
-    auto parentsSize = commit.parents.size();
-    for (std::size_t i = 0; i < parentsSize; ++i) {
-        parents += commit.parents[i];
-        if (i != parentsSize - 1)
-            parents += ",";
-    }
-    std::string type;
-    if (parentsSize > 1)
-        type = "merge";
-    std::string body {};
-    std::map<std::string, std::string> message;
-    if (type.empty()) {
-        std::string err;
-        Json::Value cm;
-        Json::CharReaderBuilder rbuilder;
-        auto reader = std::unique_ptr<Json::CharReader>(rbuilder.newCharReader());
-        if (reader->parse(commit.commit_msg.data(),
-                          commit.commit_msg.data() + commit.commit_msg.size(),
-                          &cm,
-                          &err)) {
-            for (auto const& id : cm.getMemberNames()) {
-                if (id == "type") {
-                    type = cm[id].asString();
-                    continue;
-                }
-                message.insert({id, cm[id].asString()});
-            }
-        } else {
-            JAMI_WARN("%s", err.c_str());
-        }
-    }
-    if (type == "application/data-transfer+json") {
-        // Avoid the client to do the concatenation
-        message["fileId"] = commit.id + "_" + message["tid"];
-        auto extension = fileutils::getFileExtension(message["displayName"]);
-        if (!extension.empty())
-            message["fileId"] += "." + extension;
-    }
-    message[ConversationMapKeys::ID] = commit.id;
-    message["parents"] = parents;
-    message["linearizedParent"] = commit.linearized_parent;
-    message["author"] = authorId;
-    if (type.empty())
-        return std::nullopt;
-    message["type"] = type;
-    message["timestamp"] = std::to_string(commit.timestamp);
-
-    return message;
-}
-
-std::vector<std::map<std::string, std::string>>
-Conversation::Impl::convCommitToMap(const std::vector<ConversationCommit>& commits) const
-{
-    std::vector<std::map<std::string, std::string>> result = {};
-    result.reserve(commits.size());
-    for (const auto& commit : commits) {
-        auto message = convCommitToMap(commit);
-        if (message == std::nullopt)
-            break;
-        result.emplace_back(*message);
-    }
-    return result;
-}
-
 std::vector<std::map<std::string, std::string>>
 Conversation::Impl::loadMessages(const std::string& fromMessage,
                                  const std::string& toMessage,
@@ -484,7 +405,7 @@ Conversation::Impl::loadMessages(const std::string& fromMessage,
         convCommits = repository_->logN(fromMessage, n);
     else
         convCommits = repository_->log(fromMessage, toMessage);
-    return convCommitToMap(convCommits);
+    return repository_->convCommitToMap(convCommits);
 }
 
 Conversation::Conversation(const std::weak_ptr<JamiAccount>& account,
@@ -848,7 +769,7 @@ Conversation::getCommit(const std::string& commitId) const
     auto commit = pimpl_->repository_->getCommit(commitId);
     if (commit == std::nullopt)
         return std::nullopt;
-    return pimpl_->convCommitToMap(*commit);
+    return pimpl_->repository_->convCommitToMap(*commit);
 }
 
 void
@@ -917,7 +838,7 @@ Conversation::Impl::mergeHistory(const std::string& uri)
     }
 
     JAMI_DBG("Successfully merge history with %s", uri.c_str());
-    auto result = convCommitToMap(newCommits);
+    auto result = repository_->convCommitToMap(newCommits);
     for (const auto& commit : result) {
         auto it = commit.find("type");
         if (it != commit.end() && it->second == "member") {
@@ -1349,4 +1270,34 @@ Conversation::countInteractions(const std::string& toId,
     return pimpl_->repository_->log(fromId, toId, false, true, authorUri).size();
 }
 
+void
+Conversation::search(uint32_t req,
+                     const Filter& filter,
+                     const std::shared_ptr<std::atomic_int>& flag) const
+{
+    // Because logging a conversation can take quite some time,
+    // do it asynchronously
+    dht::ThreadPool::io().run([w = weak(), req, filter, flag] {
+        if (auto sthis = w.lock()) {
+            auto acc = sthis->pimpl_->account_.lock();
+            if (!acc)
+                return;
+            auto commits = sthis->pimpl_->repository_->search(filter);
+            if (commits.size() > 0)
+                emitSignal<DRing::ConversationSignal::MessagesFound>(req,
+                                                                     acc->getAccountID(),
+                                                                     sthis->id(),
+                                                                     std::move(commits));
+            // If we're the latest thread, inform client that the search is finished
+            if ((*flag)-- == 1 /* decrement return the old value */) {
+                emitSignal<DRing::ConversationSignal::MessagesFound>(
+                    req,
+                    acc->getAccountID(),
+                    std::string {},
+                    std::vector<std::map<std::string, std::string>> {});
+            }
+        }
+    });
+}
+
 } // namespace jami
diff --git a/src/jamidht/conversation.h b/src/jamidht/conversation.h
index 592b96ff8df31dd4313cb5c71919cc84c29214ad..e9a914e2f8d14c49e535bec01d4a21854004449d 100644
--- a/src/jamidht/conversation.h
+++ b/src/jamidht/conversation.h
@@ -27,6 +27,7 @@
 #include <json/json.h>
 #include <msgpack.hpp>
 
+#include "jamidht/conversationrepository.h"
 #include "jami/datatransfer_interface.h"
 #include "conversationrepository.h"
 
@@ -371,6 +372,17 @@ public:
                                const std::string& fromId = "",
                                const std::string& authorUri = "") const;
 
+    /**
+     * Search in the conversation via a filter
+     * @param req       Id of the request
+     * @param filter    Parameters for the search
+     * @param flag      To check when search is finished
+     * @note triggers messagesFound
+     */
+    void search(uint32_t req,
+                const Filter& filter,
+                const std::shared_ptr<std::atomic_int>& flag) const;
+
 private:
     std::shared_ptr<Conversation> shared()
     {
diff --git a/src/jamidht/conversation_module.cpp b/src/jamidht/conversation_module.cpp
index ded480f57b4843d02ecf0d0f5b2c8bac10d63132..4451b60fd0c23b63f72d2fa223dfba14d47637c4 100644
--- a/src/jamidht/conversation_module.cpp
+++ b/src/jamidht/conversation_module.cpp
@@ -1620,6 +1620,26 @@ ConversationModule::countInteractions(const std::string& convId,
     return 0;
 }
 
+void
+ConversationModule::search(uint32_t req, const std::string& convId, const Filter& filter) const
+{
+    std::unique_lock<std::mutex> lk(pimpl_->conversationsMtx_);
+    auto finishedFlag = std::make_shared<std::atomic_int>(pimpl_->conversations_.size());
+    for (const auto& [cid, conversation] : pimpl_->conversations_) {
+        if (!conversation || (!convId.empty() && convId != cid)) {
+            if ((*finishedFlag)-- == 1) {
+                emitSignal<DRing::ConversationSignal::MessagesFound>(
+                    req,
+                    pimpl_->accountId_,
+                    std::string {},
+                    std::vector<std::map<std::string, std::string>> {});
+            }
+            continue;
+        }
+        conversation->search(req, filter, finishedFlag);
+    }
+}
+
 void
 ConversationModule::updateConversationInfos(const std::string& conversationId,
                                             const std::map<std::string, std::string>& infos,
diff --git a/src/jamidht/conversation_module.h b/src/jamidht/conversation_module.h
index f7e3c2e8868123e216ba89f739ec07b68caa0d78..4895641dfddd3d40ae864d2a4cc65a73d4b22f53 100644
--- a/src/jamidht/conversation_module.h
+++ b/src/jamidht/conversation_module.h
@@ -295,6 +295,15 @@ public:
                                const std::string& fromId,
                                const std::string& authorUri) const;
 
+    /**
+     * Search in conversations via a filter
+     * @param req       Id of the request
+     * @param convId    Leave empty to search in all conversation, else add the conversation's id
+     * @param filter    Parameters for the search
+     * @note triggers messagesFound
+     */
+    void search(uint32_t req, const std::string& convId, const Filter& filter) const;
+
     // Conversation's infos management
     /**
      * Update metadata from conversations (like title, avatar, etc)
diff --git a/src/jamidht/conversationrepository.cpp b/src/jamidht/conversationrepository.cpp
index 129420ddd2d954ce9fc3b80a135125324df65567..87f2c8f2828421a952383cb08848a596e9560f38 100644
--- a/src/jamidht/conversationrepository.cpp
+++ b/src/jamidht/conversationrepository.cpp
@@ -54,6 +54,13 @@ as_view(const GitObject& blob)
     return as_view(reinterpret_cast<git_blob*>(blob.get()));
 }
 
+enum class CallbackResult { Skip, Break, Ok };
+
+using PreConditionCb
+    = std::function<CallbackResult(const std::string&, const GitAuthor&, const GitCommit&)>;
+using PostConditionCb
+    = std::function<bool(const std::string&, const GitAuthor&, ConversationCommit&)>;
+
 class ConversationRepository::Impl
 {
 public:
@@ -133,12 +140,18 @@ public:
     std::string diffStats(const GitDiff& diff) const;
 
     std::vector<ConversationCommit> behind(const std::string& from) const;
+    void forEachCommit(PreConditionCb&& preCondition,
+                       std::function<void(ConversationCommit&&)>&& emplaceCb,
+                       PostConditionCb&& postCondition,
+                       const std::string& from = "",
+                       bool logIfNotFound = true) const;
     std::vector<ConversationCommit> log(const std::string& from,
                                         const std::string& to,
                                         unsigned n,
                                         bool logIfNotFound = false,
                                         bool fastLog = false,
                                         const std::string& authorUri = "") const;
+    std::vector<std::map<std::string, std::string>> search(const Filter& filter) const;
 
     GitObject fileAtTree(const std::string& path, const GitTree& tree) const;
     GitObject memberCertificate(std::string_view memberUri, const GitTree& tree) const;
@@ -183,6 +196,9 @@ public:
 
     void initMembers();
 
+    std::optional<std::map<std::string, std::string>> convCommitToMap(
+        const ConversationCommit& commit) const;
+
     // Permissions
     MemberRole updateProfilePermLvl_ {MemberRole::ADMIN};
 
@@ -1867,22 +1883,20 @@ ConversationRepository::Impl::behind(const std::string& from) const
     return log(from, to, 0);
 }
 
-std::vector<ConversationCommit>
-ConversationRepository::Impl::log(const std::string& from,
-                                  const std::string& to,
-                                  unsigned n,
-                                  bool logIfNotFound,
-                                  bool fastLog,
-                                  const std::string& authorUri) const
+void
+ConversationRepository::Impl::forEachCommit(PreConditionCb&& preCondition,
+                                            std::function<void(ConversationCommit&&)>&& emplaceCb,
+                                            PostConditionCb&& postCondition,
+                                            const std::string& from,
+                                            bool logIfNotFound) const
 {
-    std::vector<ConversationCommit> commits {};
     git_oid oid, oidFrom, oidMerge;
 
     // Note: Start from head to get all merge possibilities and correct linearized parent.
     auto repo = repository();
     if (!repo or git_reference_name_to_id(&oid, repo.get(), "HEAD") < 0) {
         JAMI_ERR("Cannot get reference for HEAD");
-        return commits;
+        return;
     }
 
     if (from != "" && git_oid_fromstr(&oidFrom, from.c_str()) == 0) {
@@ -1901,51 +1915,26 @@ ConversationRepository::Impl::log(const std::string& from,
         // there). only log if the fail is unwanted.
         if (logIfNotFound)
             JAMI_DBG("Couldn't init revwalker for conversation %s", id_.c_str());
-        return commits;
+        return;
     }
 
     GitRevWalker walker {walker_ptr, git_revwalk_free};
     git_revwalk_sorting(walker.get(), GIT_SORT_TOPOLOGICAL | GIT_SORT_TIME);
 
-    auto startLogging = from == "";
     for (auto idx = 0u; !git_revwalk_next(&oid, walker.get()); ++idx) {
         git_commit* commit_ptr = nullptr;
         std::string id = git_oid_tostr_s(&oid);
-        if (!commits.empty()) {
-            // Set linearized parent
-            commits.rbegin()->linearized_parent = id;
-        }
         if (git_commit_lookup(&commit_ptr, repo.get(), &oid) < 0) {
             JAMI_WARN("Failed to look up commit %s", id.c_str());
             break;
         }
         GitCommit commit {commit_ptr, git_commit_free};
-        if ((n != 0 && commits.size() == n) || (id == to))
-            break;
-
-        if (!startLogging && from != "" && from == id)
-            startLogging = true;
-        if (!startLogging)
-            continue;
 
         const git_signature* sig = git_commit_author(commit.get());
         GitAuthor author;
         author.name = sig->name;
         author.email = sig->email;
 
-        if (fastLog) {
-            if (authorUri != "") {
-                auto cert = tls::CertificateStore::instance().getCertificate(author.email);
-                if (cert && cert->issuer) {
-                    if (authorUri == cert->issuer->getId().toString())
-                        break;
-                }
-            }
-            // Used to only count commit
-            commits.emplace(commits.end(), ConversationCommit {});
-            continue;
-        }
-
         std::vector<std::string> parents;
         auto parentsCount = git_commit_parentcount(commit.get());
         for (unsigned int p = 0; p < parentsCount; ++p) {
@@ -1957,25 +1946,145 @@ ConversationRepository::Impl::log(const std::string& from,
             }
         }
 
-        auto cc = commits.emplace(commits.end(), ConversationCommit {});
-        cc->id = std::move(id);
-        cc->commit_msg = git_commit_message(commit.get());
-        cc->author = std::move(author);
-        cc->parents = std::move(parents);
+        auto result = preCondition(id, author, commit);
+        if (result == CallbackResult::Skip)
+            continue;
+        else if (result == CallbackResult::Break)
+            break;
+
+        ConversationCommit cc;
+        cc.id = id;
+        cc.commit_msg = git_commit_message(commit.get());
+        cc.author = std::move(author);
+        cc.parents = std::move(parents);
         git_buf signature = {}, signed_data = {};
         if (git_commit_extract_signature(&signature, &signed_data, repo.get(), &oid, "signature")
             < 0) {
             JAMI_WARN("Could not extract signature for commit %s", id.c_str());
         } else {
-            cc->signature = base64::decode(
+            cc.signature = base64::decode(
                 std::string(signature.ptr, signature.ptr + signature.size));
-            cc->signed_content = std::vector<uint8_t>(signed_data.ptr,
-                                                      signed_data.ptr + signed_data.size);
+            cc.signed_content = std::vector<uint8_t>(signed_data.ptr,
+                                                     signed_data.ptr + signed_data.size);
         }
         git_buf_dispose(&signature);
         git_buf_dispose(&signed_data);
-        cc->timestamp = git_commit_time(commit.get());
+        cc.timestamp = git_commit_time(commit.get());
+
+        auto post = postCondition(id, author, cc);
+        emplaceCb(std::move(cc));
+
+        if (post)
+            break;
     }
+}
+
+std::vector<ConversationCommit>
+ConversationRepository::Impl::log(const std::string& from,
+                                  const std::string& to,
+                                  unsigned n,
+                                  bool logIfNotFound,
+                                  bool fastLog,
+                                  const std::string& authorUri) const
+{
+    std::vector<ConversationCommit> commits {};
+    auto startLogging = from == "";
+    forEachCommit(
+        [&](const auto& id, const auto& author, const auto&) {
+            if (!commits.empty()) {
+                // Set linearized parent
+                commits.rbegin()->linearized_parent = id;
+            }
+
+            if ((n != 0 && commits.size() == n) || (id == to))
+                return CallbackResult::Break; // Stop logging
+
+            if (!startLogging && from != "" && from == id)
+                startLogging = true;
+            if (!startLogging)
+                return CallbackResult::Skip; // Start logging after this one
+
+            if (fastLog) {
+                if (authorUri != "") {
+                    if (authorUri == uriFromDevice(author.email)) {
+                        return CallbackResult::Break; // Found author, stop
+                    }
+                }
+                // Used to only count commit
+                commits.emplace(commits.end(), ConversationCommit {});
+                return CallbackResult::Skip;
+            }
+
+            return CallbackResult::Ok; // Continue
+        },
+        [&](auto&& cc) { commits.emplace(commits.end(), std::forward<decltype(cc)>(cc)); },
+        [](auto, auto, auto) { return false; },
+        from,
+        logIfNotFound);
+    return commits;
+}
+
+std::vector<std::map<std::string, std::string>>
+ConversationRepository::Impl::search(const Filter& filter) const
+{
+    std::vector<std::map<std::string, std::string>> commits {};
+    forEachCommit(
+        [&](const auto& id, const auto& author, auto& commit) {
+            if (!commits.empty()) {
+                // Set linearized parent
+                commits.rbegin()->at("linearizedParent") = id;
+            }
+
+            if (!filter.author.empty() && filter.author != uriFromDevice(author.email)) {
+                // Filter author
+                return CallbackResult::Skip;
+            }
+            auto commitTime = git_commit_time(commit.get());
+            if (filter.before && filter.before < commitTime) {
+                // Only get commits before this date
+                return CallbackResult::Skip;
+            }
+            if (filter.after && filter.after > commitTime) {
+                // Only get commits before this date
+                if (git_commit_parentcount(commit.get()) <= 1)
+                    return CallbackResult::Break;
+                else
+                    return CallbackResult::Skip; // Because we are sorting it with
+                                                 // GIT_SORT_TOPOLOGICAL | GIT_SORT_TIME
+            }
+
+            return CallbackResult::Ok; // Continue
+        },
+        [&](auto&& cc) {
+            auto content = convCommitToMap(cc);
+            auto contentType = content ? content->at("type") : "";
+            auto isSearchable = contentType == "text/plain"
+                                || contentType == "application/data-transfer+json";
+            if (filter.type.empty() && !isSearchable) {
+                // Not searchable, at least for now
+                return;
+            } else if (contentType == filter.type) {
+                if (isSearchable) {
+                    // If it's a text match the body, else the display name
+                    auto body = contentType == "text/plain" ? content->at("body")
+                                                            : content->at("displayName");
+                    std::smatch body_match;
+                    if (std::regex_search(body, body_match, std::regex(filter.regexSearch))) {
+                        commits.emplace(commits.end(), std::move(*content));
+                    }
+                } else {
+                    // Matching type, just add it to the results
+                    commits.emplace(commits.end(), std::move(*content));
+                }
+            }
+        },
+        [&](auto id, auto, auto) {
+            if (filter.maxResult != 0 && commits.size() == filter.maxResult)
+                return true;
+            if (id == filter.lastId)
+                return true;
+            return false;
+        });
     return commits;
 }
 
@@ -2182,6 +2291,63 @@ ConversationRepository::Impl::initMembers()
     }
 }
 
+std::optional<std::map<std::string, std::string>>
+ConversationRepository::Impl::convCommitToMap(const ConversationCommit& commit) const
+{
+    auto authorId = uriFromDevice(commit.author.email);
+    if (authorId.empty())
+        return std::nullopt;
+    std::string parents;
+    auto parentsSize = commit.parents.size();
+    for (std::size_t i = 0; i < parentsSize; ++i) {
+        parents += commit.parents[i];
+        if (i != parentsSize - 1)
+            parents += ",";
+    }
+    std::string type {};
+    if (parentsSize > 1)
+        type = "merge";
+    std::string body {};
+    std::map<std::string, std::string> message;
+    if (type.empty()) {
+        std::string err;
+        Json::Value cm;
+        Json::CharReaderBuilder rbuilder;
+        auto reader = std::unique_ptr<Json::CharReader>(rbuilder.newCharReader());
+        if (reader->parse(commit.commit_msg.data(),
+                          commit.commit_msg.data() + commit.commit_msg.size(),
+                          &cm,
+                          &err)) {
+            for (auto const& id : cm.getMemberNames()) {
+                if (id == "type") {
+                    type = cm[id].asString();
+                    continue;
+                }
+                message.insert({id, cm[id].asString()});
+            }
+        } else {
+            JAMI_WARN("%s", err.c_str());
+        }
+    }
+    if (type.empty()) {
+        return std::nullopt;
+    } else if (type == "application/data-transfer+json") {
+        // Avoid the client to do the concatenation
+        message["fileId"] = commit.id + "_" + message["tid"];
+        auto extension = fileutils::getFileExtension(message["displayName"]);
+        if (!extension.empty())
+            message["fileId"] += "." + extension;
+    }
+    message["id"] = commit.id;
+    message["parents"] = parents;
+    message["linearizedParent"] = commit.linearized_parent;
+    message["author"] = authorId;
+    message["type"] = type;
+    message["timestamp"] = std::to_string(commit.timestamp);
+
+    return message;
+}
+
 std::string
 ConversationRepository::Impl::diffStats(const GitDiff& diff) const
 {
@@ -2795,6 +2961,12 @@ ConversationRepository::log(const std::string& from,
     return pimpl_->log(from, to, 0, logIfNotFound, fastLog, authorUri);
 }
 
+std::vector<std::map<std::string, std::string>>
+ConversationRepository::search(const Filter& filter) const
+{
+    return pimpl_->search(filter);
+}
+
 std::optional<ConversationCommit>
 ConversationRepository::getCommit(const std::string& commitId, bool logIfNotFound) const
 {
@@ -3594,4 +3766,24 @@ ConversationRepository::getHead() const
     return {};
 }
 
+std::optional<std::map<std::string, std::string>>
+ConversationRepository::convCommitToMap(const ConversationCommit& commit) const
+{
+    return pimpl_->convCommitToMap(commit);
+}
+
+std::vector<std::map<std::string, std::string>>
+ConversationRepository::convCommitToMap(const std::vector<ConversationCommit>& commits) const
+{
+    std::vector<std::map<std::string, std::string>> result = {};
+    result.reserve(commits.size());
+    for (const auto& commit : commits) {
+        auto message = pimpl_->convCommitToMap(commit);
+        if (message == std::nullopt)
+            continue;
+        result.emplace_back(*message);
+    }
+    return result;
+}
+
 } // namespace jami
diff --git a/src/jamidht/conversationrepository.h b/src/jamidht/conversationrepository.h
index 7d66e9b290a8f17f4f822a81374839a6e1a27545..90e7403c03b05613cc01444a3b49fc96173b4b20 100644
--- a/src/jamidht/conversationrepository.h
+++ b/src/jamidht/conversationrepository.h
@@ -53,6 +53,17 @@ constexpr auto EUNAUTHORIZED = 4;
 class JamiAccount;
 class ChannelSocket;
 
+struct Filter
+{
+    std::string author;
+    std::string lastId;
+    std::string regexSearch;
+    std::string type;
+    int64_t after {0};
+    int64_t before {0};
+    uint32_t maxResult {0};
+};
+
 struct GitAuthor
 {
     std::string name {};
@@ -205,6 +216,13 @@ public:
     std::optional<ConversationCommit> getCommit(const std::string& commitId,
                                                 bool logIfNotFound = true) const;
 
+    /**
+     * Search in the conversation via a filter
+     * @param filter    Parameters for the search
+     * @return matching commits
+     */
+    std::vector<std::map<std::string, std::string>> search(const Filter& filter) const;
+
     /**
      * Get parent via topological + date sort in branch main of a commit
      * @param commitId      id to choice
@@ -366,6 +384,13 @@ public:
      * @return account's URI
      */
     std::string uriFromDevice(const std::string& deviceId) const;
+    /**
+     * Convert ConversationCommit to MapStringString for the client
+     */
+    std::vector<std::map<std::string, std::string>> convCommitToMap(
+        const std::vector<ConversationCommit>& commits) const;
+    std::optional<std::map<std::string, std::string>> convCommitToMap(
+        const ConversationCommit& commit) const;
 
     /**
      * Get current HEAD hash
diff --git a/test/agent/src/bindings/signal.cpp b/test/agent/src/bindings/signal.cpp
index 99479959e44d871a76bce0b4bd30e8df7923ffb7..50557fb0c56dc2ce9680562efd1f76a4fbc2497c 100644
--- a/test/agent/src/bindings/signal.cpp
+++ b/test/agent/src/bindings/signal.cpp
@@ -209,20 +209,19 @@ install_signal_primitives(void*)
     add_handler<DRing::CallSignal::RecordPlaybackFilepath, const std::string&, const std::string&>(
         handlers, "record-playback-filepath");
 
-    add_handler<DRing::CallSignal::ConferenceCreated,
-                const std::string&, const std::string&>(handlers,
-                                                        "conference-created");
+    add_handler<DRing::CallSignal::ConferenceCreated, const std::string&, const std::string&>(
+        handlers, "conference-created");
 
     add_handler<DRing::CallSignal::ConferenceChanged,
-                const std::string&, const std::string&, const std::string&>(
-                    handlers, "conference-changed");
+                const std::string&,
+                const std::string&,
+                const std::string&>(handlers, "conference-changed");
 
     add_handler<DRing::CallSignal::UpdatePlaybackScale, const std::string&, unsigned, unsigned>(
         handlers, "update-playback-scale");
 
-    add_handler<DRing::CallSignal::ConferenceRemoved,
-                const std::string&, const std::string&>(handlers,
-                                                        "conference-removed");
+    add_handler<DRing::CallSignal::ConferenceRemoved, const std::string&, const std::string&>(
+        handlers, "conference-removed");
 
     add_handler<DRing::CallSignal::RecordingStateChanged, const std::string&, int>(
         handlers, "recording-state-changed");
@@ -500,6 +499,12 @@ install_signal_primitives(void*)
                 const std::string&,
                 std::vector<std::map<std::string, std::string>>>(handlers, "conversation-loaded");
 
+    add_handler<DRing::ConversationSignal::MessagesFound,
+                uint32_t,
+                const std::string&,
+                const std::string&,
+                std::vector<std::map<std::string, std::string>>>(handlers, "messages-found");
+
     add_handler<DRing::ConversationSignal::MessageReceived,
                 const std::string&,
                 const std::string&,
diff --git a/test/unitTest/conversation/conversation.cpp b/test/unitTest/conversation/conversation.cpp
index 40ad6d0bffaa86b7cf56946a4d848b41826ad0f6..6bb07fe232021e6a8e2293e2fb2ed646f99da062 100644
--- a/test/unitTest/conversation/conversation.cpp
+++ b/test/unitTest/conversation/conversation.cpp
@@ -110,6 +110,7 @@ private:
     void testImportMalformedContacts();
     void testRemoveReaddMultipleDevice();
     void testSendReply();
+    void testSearchInConv();
 
     CPPUNIT_TEST_SUITE(ConversationTest);
     CPPUNIT_TEST(testCreateConversation);
@@ -150,6 +151,7 @@ private:
     CPPUNIT_TEST(testImportMalformedContacts);
     CPPUNIT_TEST(testRemoveReaddMultipleDevice);
     CPPUNIT_TEST(testSendReply);
+    CPPUNIT_TEST(testSearchInConv);
     CPPUNIT_TEST_SUITE_END();
 };
 
@@ -3006,6 +3008,94 @@ ConversationTest::testSendReply()
     CPPUNIT_ASSERT(!cv.wait_for(lk, 10s, [&]() { return messageBobReceived.size() == 3; }));
 }
 
+void
+ConversationTest::testSearchInConv()
+{
+    auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
+    auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
+    auto bobUri = bobAccount->getUsername();
+    auto aliceUri = aliceAccount->getUsername();
+    std::mutex mtx;
+    std::unique_lock<std::mutex> lk {mtx};
+    std::condition_variable cv;
+    std::map<std::string, std::shared_ptr<DRing::CallbackWrapperBase>> confHandlers;
+    bool conversationReady = false, requestReceived = false, memberMessageGenerated = false,
+         messageReceived = false;
+    std::vector<std::string> bobMessages;
+    std::string convId = "";
+    confHandlers.insert(DRing::exportable_callback<DRing::ConfigurationSignal::IncomingTrustRequest>(
+        [&](const std::string& account_id,
+            const std::string& /*from*/,
+            const std::string& /*conversationId*/,
+            const std::vector<uint8_t>& /*payload*/,
+            time_t /*received*/) {
+            if (account_id == bobId)
+                requestReceived = true;
+            cv.notify_one();
+        }));
+    confHandlers.insert(DRing::exportable_callback<DRing::ConversationSignal::ConversationReady>(
+        [&](const std::string& accountId, const std::string& conversationId) {
+            if (accountId == aliceId) {
+                convId = conversationId;
+            } else if (accountId == bobId) {
+                conversationReady = true;
+            }
+            cv.notify_one();
+        }));
+    confHandlers.insert(DRing::exportable_callback<DRing::ConversationSignal::MessageReceived>(
+        [&](const std::string& accountId,
+            const std::string& conversationId,
+            std::map<std::string, std::string> message) {
+            if (accountId == aliceId && conversationId == convId) {
+                if (message["type"] == "member")
+                    memberMessageGenerated = true;
+            } else if (accountId == bobId) {
+                messageReceived = true;
+            }
+            cv.notify_one();
+        }));
+    std::vector<std::map<std::string, std::string>> messages;
+    bool finished = false;
+    confHandlers.insert(DRing::exportable_callback<DRing::ConversationSignal::MessagesFound>(
+        [&](uint32_t,
+            const std::string&,
+            const std::string& conversationId,
+            std::vector<std::map<std::string, std::string>> msg) {
+            if (conversationId == convId)
+                messages = msg;
+            finished = conversationId.empty();
+            cv.notify_one();
+        }));
+    DRing::registerSignalHandlers(confHandlers);
+    requestReceived = false;
+    aliceAccount->addContact(bobUri);
+    aliceAccount->sendTrustRequest(bobUri, {});
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return requestReceived; }));
+    CPPUNIT_ASSERT(bobAccount->acceptTrustRequest(aliceUri));
+    CPPUNIT_ASSERT(
+        cv.wait_for(lk, 30s, [&]() { return conversationReady && memberMessageGenerated; }));
+    // Add some messages
+    messageReceived = false;
+    DRing::sendMessage(aliceId, convId, "message 1"s, "");
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return messageReceived; }));
+    messageReceived = false;
+    DRing::sendMessage(aliceId, convId, "message 2"s, "");
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return messageReceived; }));
+    messageReceived = false;
+    DRing::sendMessage(aliceId, convId, "message 3"s, "");
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return messageReceived; }));
+    DRing::searchConversation(aliceId, convId, "", "", "message", 0, 0, 0);
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return messages.size() == 3 && finished; }));
+    messages.clear();
+    finished = false;
+    DRing::searchConversation(aliceId, convId, "", "", "message 2", 0, 0, 0);
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return messages.size() == 1 && finished; }));
+    messages.clear();
+    finished = false;
+    DRing::searchConversation(aliceId, convId, "", "", "foo", 0, 0, 0);
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return messages.size() == 0 && finished; }));
+}
+
 } // namespace test
 } // namespace jami