diff --git a/src/account.cpp b/src/account.cpp
index 7f69976d3142b8b1bb5e9ec2553fcf69679bdfa4..429346e152357c874d8f6a96eb1e8f8f2506a119 100644
--- a/src/account.cpp
+++ b/src/account.cpp
@@ -376,15 +376,6 @@ Account::getVolatileAccountDetails() const
             {DRing::Account::VolatileProperties::ACTIVE, active_ ? TRUE_STR : FALSE_STR}};
 }
 
-void
-Account::onIsComposing(const std::string& conversationId, const std::string& peer, bool isComposing)
-{
-    emitSignal<DRing::ConfigurationSignal::ComposingStatusChanged>(accountID_,
-                                                                   conversationId,
-                                                                   peer,
-                                                                   isComposing ? 1 : 0);
-}
-
 bool
 Account::hasActiveCodec(MediaType mediaType) const
 {
diff --git a/src/account.h b/src/account.h
index 6b596c93348ed3b3a67d6ba5c2c669ec2ab474a4..f7b2e5be477fec42cf2d7c034a13e556269dbb79 100644
--- a/src/account.h
+++ b/src/account.h
@@ -60,7 +60,7 @@ class Value;
 }
 
 namespace jami {
-static constexpr uint64_t DRING_ID_MAX_VAL = 9007199254740992;
+static constexpr uint64_t JAMI_ID_MAX_VAL = 9007199254740992;
 
 namespace upnp {
 class Controller;
@@ -175,16 +175,8 @@ public:
         return 0;
     }
 
-    virtual void sendInstantMessage(const std::string& /*convId*/,
-                                    const std::map<std::string, std::string>& /*msg*/)
-    {}
-
     virtual void setIsComposing(const std::string& /*conversationUri*/, bool /*isWriting*/) {};
 
-    virtual void onIsComposing(const std::string& /*conversationId*/,
-                               const std::string& /*peer*/,
-                               bool /*isWriting*/);
-
     virtual bool setMessageDisplayed(const std::string& /*conversationUri*/,
                                      const std::string& /*messageId*/,
                                      int /*status*/)
@@ -323,20 +315,11 @@ public:
      */
     virtual void connectivityChanged() {};
 
-    virtual void onNewGitCommit(const std::string& /*peer*/,
-                                const std::string& /*deviceId*/,
-                                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*/,
-                                           const std::string& /*conversationId*/) {};
-    virtual void checkIfRemoveForCompat(const std::string& /*peerUri*/) {};
+    virtual bool handleMessage(const std::string& /*from*/,
+                               const std::pair<std::string, std::string>& /*message*/)
+    {
+        return false;
+    };
 
     /**
      * Helper function used to load the default codec order from the codec factory
diff --git a/src/call.cpp b/src/call.cpp
index a151c0bff9c567922795add5a10992b65c442736..8d8ef5e175d9b72938a1568e13b63326fff00d6a 100644
--- a/src/call.cpp
+++ b/src/call.cpp
@@ -125,8 +125,8 @@ Call::Call(const std::shared_ptr<Account>& account,
                 duration_start_ = clock::now();
             else if (cnx_state == ConnectionState::DISCONNECTED && call_state == CallState::OVER) {
                 if (auto jamiAccount = std::dynamic_pointer_cast<JamiAccount>(getAccount().lock())) {
-                    jamiAccount->addCallHistoryMessage(getPeerNumber(), getCallDuration().count());
-
+                    jamiAccount->convModule()->addCallHistoryMessage(getPeerNumber(),
+                                                                     getCallDuration().count());
                     monitor();
                 }
             }
diff --git a/src/call_factory.cpp b/src/call_factory.cpp
index a846e42336ef787dc159711fcae4df3a46e6f8fd..f2e532823f7dfe51a9319e768268739a025d1979 100644
--- a/src/call_factory.cpp
+++ b/src/call_factory.cpp
@@ -34,7 +34,7 @@ CallFactory::getNewCallID() const
     std::string random_id;
     do {
         random_id = std::to_string(
-            std::uniform_int_distribution<uint64_t>(1, DRING_ID_MAX_VAL)(rand_));
+            std::uniform_int_distribution<uint64_t>(1, JAMI_ID_MAX_VAL)(rand_));
     } while (hasCall(random_id));
     return random_id;
 }
diff --git a/src/client/conversation_interface.cpp b/src/client/conversation_interface.cpp
index b420d703fe6ed94402e34df893481fb0d0179747..a686d74a438fb52d1daa463831fae1f72ef4c83d 100644
--- a/src/client/conversation_interface.cpp
+++ b/src/client/conversation_interface.cpp
@@ -31,6 +31,7 @@
 #include "logger.h"
 #include "manager.h"
 #include "jamidht/jamiaccount.h"
+#include "jamidht/conversation_module.h"
 
 namespace DRing {
 
@@ -38,7 +39,8 @@ std::string
 startConversation(const std::string& accountId)
 {
     if (auto acc = jami::Manager::instance().getAccount<jami::JamiAccount>(accountId))
-        return acc->startConversation();
+        if (auto convModule = acc->convModule())
+            return convModule->startConversation();
     return {};
 }
 
@@ -46,21 +48,24 @@ void
 acceptConversationRequest(const std::string& accountId, const std::string& conversationId)
 {
     if (auto acc = jami::Manager::instance().getAccount<jami::JamiAccount>(accountId))
-        acc->acceptConversationRequest(conversationId);
+        if (auto convModule = acc->convModule())
+            convModule->acceptConversationRequest(conversationId);
 }
 
 void
 declineConversationRequest(const std::string& accountId, const std::string& conversationId)
 {
     if (auto acc = jami::Manager::instance().getAccount<jami::JamiAccount>(accountId))
-        acc->declineConversationRequest(conversationId);
+        if (auto convModule = acc->convModule())
+            convModule->declineConversationRequest(conversationId);
 }
 
 bool
 removeConversation(const std::string& accountId, const std::string& conversationId)
 {
     if (auto acc = jami::Manager::instance().getAccount<jami::JamiAccount>(accountId))
-        return acc->removeConversation(conversationId);
+        if (auto convModule = acc->convModule())
+            return convModule->removeConversation(conversationId);
     return false;
 }
 
@@ -68,7 +73,8 @@ std::vector<std::string>
 getConversations(const std::string& accountId)
 {
     if (auto acc = jami::Manager::instance().getAccount<jami::JamiAccount>(accountId))
-        return acc->getConversations();
+        if (auto convModule = acc->convModule())
+            return convModule->getConversations();
     return {};
 }
 
@@ -76,7 +82,8 @@ std::vector<std::map<std::string, std::string>>
 getConversationRequests(const std::string& accountId)
 {
     if (auto acc = jami::Manager::instance().getAccount<jami::JamiAccount>(accountId))
-        return acc->getConversationRequests();
+        if (auto convModule = acc->convModule())
+            return convModule->getConversationRequests();
     return {};
 }
 
@@ -86,14 +93,16 @@ updateConversationInfos(const std::string& accountId,
                         const std::map<std::string, std::string>& infos)
 {
     if (auto acc = jami::Manager::instance().getAccount<jami::JamiAccount>(accountId))
-        acc->updateConversationInfos(conversationId, infos);
+        if (auto convModule = acc->convModule())
+            convModule->updateConversationInfos(conversationId, infos);
 }
 
 std::map<std::string, std::string>
 conversationInfos(const std::string& accountId, const std::string& conversationId)
 {
     if (auto acc = jami::Manager::instance().getAccount<jami::JamiAccount>(accountId))
-        return acc->conversationInfos(conversationId);
+        if (auto convModule = acc->convModule())
+            return convModule->conversationInfos(conversationId);
     return {};
 }
 
@@ -104,7 +113,8 @@ addConversationMember(const std::string& accountId,
                       const std::string& contactUri)
 {
     if (auto acc = jami::Manager::instance().getAccount<jami::JamiAccount>(accountId))
-        acc->addConversationMember(conversationId, contactUri);
+        if (auto convModule = acc->convModule())
+            convModule->addConversationMember(conversationId, contactUri);
 }
 
 void
@@ -113,14 +123,16 @@ removeConversationMember(const std::string& accountId,
                          const std::string& contactUri)
 {
     if (auto acc = jami::Manager::instance().getAccount<jami::JamiAccount>(accountId))
-        acc->removeConversationMember(conversationId, contactUri);
+        if (auto convModule = acc->convModule())
+            convModule->removeConversationMember(conversationId, contactUri);
 }
 
 std::vector<std::map<std::string, std::string>>
 getConversationMembers(const std::string& accountId, const std::string& conversationId)
 {
     if (auto acc = jami::Manager::instance().getAccount<jami::JamiAccount>(accountId))
-        return acc->getConversationMembers(conversationId);
+        if (auto convModule = acc->convModule())
+            return convModule->getConversationMembers(conversationId);
     return {};
 }
 
@@ -132,7 +144,8 @@ sendMessage(const std::string& accountId,
             const std::string& parent)
 {
     if (auto acc = jami::Manager::instance().getAccount<jami::JamiAccount>(accountId))
-        acc->sendMessage(conversationId, message, parent);
+        if (auto convModule = acc->convModule())
+            convModule->sendMessage(conversationId, message, parent);
 }
 
 uint32_t
@@ -142,7 +155,8 @@ loadConversationMessages(const std::string& accountId,
                          size_t n)
 {
     if (auto acc = jami::Manager::instance().getAccount<jami::JamiAccount>(accountId))
-        return acc->loadConversationMessages(conversationId, fromMessage, n);
+        if (auto convModule = acc->convModule())
+            return convModule->loadConversationMessages(conversationId, fromMessage, n);
     return 0;
 }
 
@@ -154,7 +168,8 @@ countInteractions(const std::string& accountId,
                   const std::string& authorUri)
 {
     if (auto acc = jami::Manager::instance().getAccount<jami::JamiAccount>(accountId))
-        return acc->countInteractions(conversationId, toId, fromId, authorUri);
+        if (auto convModule = acc->convModule())
+            return convModule->countInteractions(conversationId, toId, fromId, authorUri);
     return 0;
 }
 
diff --git a/src/client/datatransfer.cpp b/src/client/datatransfer.cpp
index 7383d6e718d75eb77af52fc445c0ba79df6ee3a1..a8e6b7934b690c67ffcb82a15de8b032f0345213 100644
--- a/src/client/datatransfer.cpp
+++ b/src/client/datatransfer.cpp
@@ -83,7 +83,8 @@ downloadFile(const std::string& accountId,
              const std::string& path) noexcept
 {
     if (auto acc = jami::Manager::instance().getAccount<jami::JamiAccount>(accountId))
-        return acc->downloadFile(conversationId, interactionId, fileId, path);
+        if (auto convModule = acc->convModule())
+            return convModule->downloadFile(conversationId, interactionId, fileId, path);
     return {};
 }
 
diff --git a/src/data_transfer.cpp b/src/data_transfer.cpp
index d41eabfc59374686791d43cade18396cb1695db5..f0fa11f8b65c5d4dcfc806d621ff934f3e9d18ae 100644
--- a/src/data_transfer.cpp
+++ b/src/data_transfer.cpp
@@ -52,7 +52,7 @@ DRing::DataTransferId
 generateUID()
 {
     thread_local dht::crypto::random_device rd;
-    return std::uniform_int_distribution<DRing::DataTransferId> {1, DRING_ID_MAX_VAL}(rd);
+    return std::uniform_int_distribution<DRing::DataTransferId> {1, JAMI_ID_MAX_VAL}(rd);
 }
 
 constexpr const uint32_t MAX_BUFFER_SIZE {65534}; /* Channeled max packet size */
diff --git a/src/im/message_engine.cpp b/src/im/message_engine.cpp
index 086797a34d01c918bb9d68aa79ecf2a846952234..42585f3e4887694828e84adba7b588e37b7c9fec 100644
--- a/src/im/message_engine.cpp
+++ b/src/im/message_engine.cpp
@@ -52,7 +52,7 @@ MessageEngine::sendMessage(const std::string& to, const std::map<std::string, st
         std::lock_guard<std::mutex> lock(messagesMutex_);
         auto& peerMessages = messages_[to];
         do {
-            token = std::uniform_int_distribution<MessageToken> {1, DRING_ID_MAX_VAL}(account_.rand);
+            token = std::uniform_int_distribution<MessageToken> {1, JAMI_ID_MAX_VAL}(account_.rand);
         } while (peerMessages.find(token) != peerMessages.end());
         auto m = peerMessages.emplace(token, Message {});
         m.first->second.to = to;
diff --git a/src/jamidht/CMakeLists.txt b/src/jamidht/CMakeLists.txt
index 67e9b4c3eabd77e587fc676ba02bdb7d0659fc5a..47209bd934c0badb2b9adc5ec3e5597ab8084b7e 100644
--- a/src/jamidht/CMakeLists.txt
+++ b/src/jamidht/CMakeLists.txt
@@ -32,6 +32,11 @@ list (APPEND Source_Files__jamidht
       "${CMAKE_CURRENT_SOURCE_DIR}/jamiaccount.h"
       "${CMAKE_CURRENT_SOURCE_DIR}/multiplexed_socket.cpp"
       "${CMAKE_CURRENT_SOURCE_DIR}/multiplexed_socket.h"
+      "${CMAKE_CURRENT_SOURCE_DIR}/channel_handler.h"
+      "${CMAKE_CURRENT_SOURCE_DIR}/conversation_channel_handler.h"
+      "${CMAKE_CURRENT_SOURCE_DIR}/conversation_channel_handler.cpp"
+      "${CMAKE_CURRENT_SOURCE_DIR}/conversation_module.h"
+      "${CMAKE_CURRENT_SOURCE_DIR}/conversation_module.cpp"
       "${CMAKE_CURRENT_SOURCE_DIR}/namedirectory.cpp"
       "${CMAKE_CURRENT_SOURCE_DIR}/namedirectory.h"
       "${CMAKE_CURRENT_SOURCE_DIR}/p2p.cpp"
diff --git a/src/jamidht/Makefile.am b/src/jamidht/Makefile.am
index 48c636170347db17a9f911568ff1e6dde1aa9001..b0ee274bd173f18f35ce8498ff503aeb8f5d9ac9 100644
--- a/src/jamidht/Makefile.am
+++ b/src/jamidht/Makefile.am
@@ -25,6 +25,11 @@ libringacc_la_SOURCES = \
         conversationrepository.cpp \
         gitserver.h \
         gitserver.cpp \
+        channel_handler.h \
+        conversation_channel_handler.h \
+        conversation_channel_handler.cpp \
+        conversation_module.h \
+        conversation_module.cpp \
         multiplexed_socket.h \
         multiplexed_socket.cpp \
         accountarchive.cpp \
diff --git a/src/jamidht/account_manager.cpp b/src/jamidht/account_manager.cpp
index 8796c1f3c4ab3a0fdaf202516e764b11669bed8f..cf3c1178a0160f2d1c45f5e245aca8a1ee2acf48 100644
--- a/src/jamidht/account_manager.cpp
+++ b/src/jamidht/account_manager.cpp
@@ -461,107 +461,6 @@ AccountManager::getContacts() const
     return ret;
 }
 
-void
-AccountManager::setConversations(const std::map<std::string, ConvInfo>& newConv)
-{
-    if (info_) {
-        info_->conversations = newConv;
-        saveConvInfos();
-    }
-}
-
-void
-AccountManager::setConversationMembers(const std::string& convId,
-                                       const std::vector<std::string>& members)
-{
-    if (info_) {
-        auto convIt = info_->conversations.find(convId);
-        if (convIt != info_->conversations.end()) {
-            convIt->second.members = members;
-            saveConvInfos();
-        }
-    }
-}
-
-void
-AccountManager::saveConvInfos() const
-{
-    if (!info_)
-        return;
-    std::ofstream file(info_->contacts->path() + DIR_SEPARATOR_STR "convInfo",
-                       std::ios::trunc | std::ios::binary);
-    msgpack::pack(file, info_->conversations);
-}
-
-void
-AccountManager::addConversation(const ConvInfo& info)
-{
-    if (info_) {
-        info_->conversations[info.id] = info;
-        saveConvInfos();
-    }
-}
-
-void
-AccountManager::setConversationsRequests(const std::map<std::string, ConversationRequest>& newConvReq)
-{
-    if (info_) {
-        std::lock_guard<std::mutex> lk(conversationsRequestsMtx);
-        info_->conversationsRequests = newConvReq;
-        saveConvRequests();
-    }
-}
-
-void
-AccountManager::saveConvRequests() const
-{
-    if (!info_)
-        return;
-    std::ofstream file(info_->contacts->path() + DIR_SEPARATOR_STR "convRequests",
-                       std::ios::trunc | std::ios::binary);
-    msgpack::pack(file, info_->conversationsRequests);
-}
-
-std::optional<ConversationRequest>
-AccountManager::getRequest(const std::string& id) const
-{
-    if (info_) {
-        std::lock_guard<std::mutex> lk(conversationsRequestsMtx);
-        auto it = info_->conversationsRequests.find(id);
-        if (it != info_->conversationsRequests.end())
-            return it->second;
-    }
-    return std::nullopt;
-}
-
-bool
-AccountManager::addConversationRequest(const std::string& id, const ConversationRequest& req)
-{
-    if (info_) {
-        std::lock_guard<std::mutex> lk(conversationsRequestsMtx);
-        auto it = info_->conversationsRequests.find(id);
-        if (it != info_->conversationsRequests.end()) {
-            // Check if updated
-            if (req == it->second)
-                return false;
-        }
-        info_->conversationsRequests[id] = req;
-        saveConvRequests();
-        return true;
-    }
-    return false;
-}
-
-void
-AccountManager::rmConversationRequest(const std::string& id)
-{
-    if (info_) {
-        std::lock_guard<std::mutex> lk(conversationsRequestsMtx);
-        info_->conversationsRequests.erase(id);
-        saveConvRequests();
-    }
-}
-
 /** Obtain details about one account contact in serializable form. */
 std::map<std::string, std::string>
 AccountManager::getContactDetails(const std::string& uri) const
@@ -583,12 +482,14 @@ AccountManager::findCertificate(
         if (cb)
             cb(cert);
     } else {
-        dht_->findCertificate(h, [cb = std::move(cb)](const std::shared_ptr<dht::crypto::Certificate>& crt) {
-            if (crt)
-                tls::CertificateStore::instance().pinCertificate(crt);
-            if (cb)
-                cb(crt);
-        });
+        dht_->findCertificate(h,
+                              [cb = std::move(cb)](
+                                  const std::shared_ptr<dht::crypto::Certificate>& crt) {
+                                  if (crt)
+                                      tls::CertificateStore::instance().pinCertificate(crt);
+                                  if (cb)
+                                      cb(crt);
+                              });
     }
     return true;
 }
diff --git a/src/jamidht/account_manager.h b/src/jamidht/account_manager.h
index 1fe1fc379f606f6167dd849fa7a0e9affebc607e..a74847ab547e7302080c937d3ed109e4a5389525 100644
--- a/src/jamidht/account_manager.h
+++ b/src/jamidht/account_manager.h
@@ -47,8 +47,6 @@ struct AccountInfo
 {
     dht::crypto::Identity identity;
     std::unique_ptr<ContactList> contacts;
-    std::map<std::string, ConvInfo> conversations;
-    std::map<std::string, ConversationRequest> conversationsRequests;
     std::string accountId;
     std::string deviceId;
     std::shared_ptr<dht::crypto::PublicKey> devicePk;
@@ -223,18 +221,6 @@ public:
     void removeContactConversation(const std::string& uri); // for non swarm contacts
     std::vector<std::map<std::string, std::string>> getContacts() const;
 
-    // Conversations
-    void saveConvInfos() const;
-    void saveConvRequests() const;
-    void setConversations(const std::map<std::string, ConvInfo>& newConv);
-    void setConversationMembers(const std::string& convId, const std::vector<std::string>& members);
-    void addConversation(const ConvInfo& info);
-    void setConversationsRequests(const std::map<std::string, ConversationRequest>& newConvReq);
-    std::optional<ConversationRequest> getRequest(const std::string& id) const;
-    bool addConversationRequest(const std::string& id, const ConversationRequest& req);
-    void rmConversationRequest(const std::string& id);
-    mutable std::mutex conversationsRequestsMtx;
-
     /** Obtain details about one account contact in serializable form. */
     std::map<std::string, std::string> getContactDetails(const std::string& uri) const;
 
diff --git a/src/jamidht/archive_account_manager.cpp b/src/jamidht/archive_account_manager.cpp
index 27cb4b8cae9f61ddb4a43240df6240aabe2280c4..6f44f57c9a86b1a440c0467bf857932f41d7f1ca 100644
--- a/src/jamidht/archive_account_manager.cpp
+++ b/src/jamidht/archive_account_manager.cpp
@@ -23,6 +23,7 @@
 #include "base64.h"
 #include "jami/account_const.h"
 #include "account_schema.h"
+#include "jamidht/conversation_module.h"
 
 #include <opendht/dhtrunner.h>
 #include <opendht/thread_pool.h>
@@ -349,13 +350,11 @@ ArchiveAccountManager::onArchiveLoaded(AuthContext& ctx, AccountArchive&& a)
     info->contacts = std::make_unique<ContactList>(a.id.second, path_, onChange_);
     info->contacts->setContacts(a.contacts);
     info->contacts->foundAccountDevice(deviceCertificate, ctx.deviceName, clock::now());
-    info->conversations = a.conversations;
-    info->conversationsRequests = a.conversationsRequests;
     info->ethAccount = ethAccount;
     info->announce = std::move(receipt.second);
+    ConversationModule::saveConvInfosToPath(path_, a.conversations);
+    ConversationModule::saveConvRequestsToPath(path_, a.conversationsRequests);
     info_ = std::move(info);
-    saveConvInfos();
-    saveConvRequests();
 
     JAMI_WARN("[Auth] created new device: %s", info_->deviceId.c_str());
     ctx.onSuccess(*info_,
@@ -584,8 +583,9 @@ ArchiveAccountManager::updateArchive(AccountArchive& archive) const
             archive.config[it.first] = it.second;
     }
     archive.contacts = info_->contacts->getContacts();
-    archive.conversations = info_->conversations;
-    archive.conversationsRequests = info_->conversationsRequests;
+    // Note we do not know accountID_ here, use path
+    archive.conversations = ConversationModule::convInfosFromPath(path_);
+    archive.conversationsRequests = ConversationModule::convRequestsFromPath(path_);
 }
 
 void
diff --git a/src/jamidht/channel_handler.h b/src/jamidht/channel_handler.h
new file mode 100644
index 0000000000000000000000000000000000000000..a0c448f059298e42bdfeb30fb6830fddb4b6da3f
--- /dev/null
+++ b/src/jamidht/channel_handler.h
@@ -0,0 +1,67 @@
+/*
+ *  Copyright (C) 2021 Savoir-faire Linux Inc.
+ *
+ *  Author: Sébastien Blin <sebastien.blin@savoirfairelinux.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
+ */
+
+#pragma once
+
+#include "jamidht/multiplexed_socket.h"
+
+namespace jami {
+
+using ConnectCb = std::function<void(std::shared_ptr<ChannelSocket>, const DeviceId&)>;
+
+/**
+ * A Channel handler is used to make the link between JamiAccount and ConnectionManager
+ * Its role is to manage channels for a protocol (git/sip/etc)
+ */
+class ChannelHandlerInterface
+{
+public:
+    virtual ~ChannelHandlerInterface() {};
+
+    /**
+     * Ask for a new channel
+     * @param deviceId      The device to connect
+     * @param name          The name of the channel
+     * @param cb            The callback to call when connected (can be immediate if already connected)
+     */
+    virtual void connect(const DeviceId& deviceId, const std::string& name, ConnectCb&& cb)
+        = 0;
+
+    /**
+     * Determine if we accept or not the request. Called when ConnectionManager receives a request
+     * @param deviceId      Device who asked
+     * @param name          The name of the channel
+     * @return if we accept or not
+     */
+    virtual bool onRequest(const DeviceId& deviceId, const std::string& name) = 0;
+
+    /**
+     * Called when ConnectionManager has a new channel ready
+     * @param deviceId      Related device
+     * @param name          The name of the channel
+     * @param channel       Channel to handle
+     */
+    virtual void onReady(const DeviceId& deviceId,
+                         const std::string& name,
+                         std::shared_ptr<ChannelSocket> channel)
+        = 0;
+};
+
+} // namespace jami
\ No newline at end of file
diff --git a/src/jamidht/connectionmanager.cpp b/src/jamidht/connectionmanager.cpp
index 24a10502c0904b7845aa298052306a5492683d7d..51b64b4aed2aff7a8bb4971b2f18ba1f9af87997 100644
--- a/src/jamidht/connectionmanager.cpp
+++ b/src/jamidht/connectionmanager.cpp
@@ -440,7 +440,7 @@ ConnectionManager::Impl::connectDevice(const std::shared_ptr<dht::crypto::Certif
         dht::Value::Id vid;
         auto tentatives = 0;
         do {
-            vid = ValueIdDist(1, DRING_ID_MAX_VAL)(sthis->account.rand);
+            vid = ValueIdDist(1, JAMI_ID_MAX_VAL)(sthis->account.rand);
             --tentatives;
         } while (sthis->getPendingCallbacks(deviceId, vid).size() != 0
                  && tentatives != MAX_TENTATIVES);
diff --git a/src/jamidht/conversation_channel_handler.cpp b/src/jamidht/conversation_channel_handler.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..4d7edbd081ed61fcbfc2eab6aff4ccfb9bc3cd17
--- /dev/null
+++ b/src/jamidht/conversation_channel_handler.cpp
@@ -0,0 +1,71 @@
+/*
+ *  Copyright (C) 2021 Savoir-faire Linux Inc.
+ *
+ *  Author: Sébastien Blin <sebastien.blin@savoirfairelinux.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
+ */
+
+#include "jamidht/conversation_channel_handler.h"
+
+namespace jami {
+
+ConversationChannelHandler::ConversationChannelHandler(std::weak_ptr<JamiAccount>&& acc,
+                                                       ConnectionManager& cm)
+    : ChannelHandlerInterface()
+    , account_(acc)
+    , connectionManager_(cm)
+{}
+
+ConversationChannelHandler::~ConversationChannelHandler() {}
+
+void
+ConversationChannelHandler::connect(const DeviceId& deviceId,
+                                    const std::string& channelName,
+                                    ConnectCb&& cb)
+{
+    connectionManager_.connectDevice(deviceId,
+                                     "git://" + deviceId.toString() + "/" + channelName,
+                                     [cb = std::move(cb)](std::shared_ptr<ChannelSocket> socket,
+                                                          const DeviceId& dev) {
+                                         if (cb)
+                                             cb(socket, dev);
+                                     });
+}
+
+bool
+ConversationChannelHandler::onRequest(const DeviceId&, const std::string& name)
+{
+    // Pre-check before acceptance. Sometimes, another device can start a conversation
+    // which is still not synced. So, here we decline channel's request in this case
+    // to avoid the other device to want to sync with us if we are not ready.
+    auto sep = name.find_last_of('/');
+    auto conversationId = name.substr(sep + 1);
+    auto remoteDevice = name.substr(6, sep - 6);
+    if (auto acc = account_.lock())
+        if (auto convModule = acc->convModule()) {
+            auto res = !convModule->isBannedDevice(conversationId, remoteDevice);
+            return res;
+        }
+    return false;
+}
+
+void
+ConversationChannelHandler::onReady(const DeviceId&,
+                                    const std::string&,
+                                    std::shared_ptr<ChannelSocket>)
+{}
+
+} // namespace jami
\ No newline at end of file
diff --git a/src/jamidht/conversation_channel_handler.h b/src/jamidht/conversation_channel_handler.h
new file mode 100644
index 0000000000000000000000000000000000000000..455bc0790a8d760b8786ec00fe0f6dbca5459104
--- /dev/null
+++ b/src/jamidht/conversation_channel_handler.h
@@ -0,0 +1,66 @@
+/*
+ *  Copyright (C) 2021 Savoir-faire Linux Inc.
+ *
+ *  Author: Sébastien Blin <sebastien.blin@savoirfairelinux.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
+ */
+
+#pragma once
+
+#include "jamidht/channel_handler.h"
+#include "jamidht/connectionmanager.h"
+#include "jamidht/jamiaccount.h"
+
+namespace jami {
+
+/**
+ * Manages Conversation's channels
+ */
+class ConversationChannelHandler : public ChannelHandlerInterface
+{
+public:
+    ConversationChannelHandler(std::weak_ptr<JamiAccount>&& acc, ConnectionManager& cm);
+    ~ConversationChannelHandler();
+
+    /**
+     * Ask for a new git channel
+     * @param deviceId      The device to connect
+     * @param name          The name of the channel
+     * @param cb            The callback to call when connected (can be immediate if already connected)
+     */
+    void connect(const DeviceId& deviceId, const std::string& name, ConnectCb&& cb) override;
+
+    /**
+     * Determine if we accept or not the git request
+     * @param deviceId      device who asked
+     * @param name          name asked
+     * @return if the channel is for a valid conversation and device not banned
+     */
+    bool onRequest(const DeviceId& deviceId, const std::string& name) override;
+
+    /**
+     * TODO, this needs to extract gitservers from JamiAccount
+     */
+    void onReady(const DeviceId& deviceId,
+                 const std::string& name,
+                 std::shared_ptr<ChannelSocket> channel) override;
+
+private:
+    std::weak_ptr<JamiAccount> account_;
+    ConnectionManager& connectionManager_;
+};
+
+} // namespace jami
\ No newline at end of file
diff --git a/src/jamidht/conversation_module.cpp b/src/jamidht/conversation_module.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..a863e3076f05297815bfb54dfc30cf48722eceae
--- /dev/null
+++ b/src/jamidht/conversation_module.cpp
@@ -0,0 +1,1479 @@
+/*
+ *  Copyright (C) 2021 Savoir-faire Linux Inc.
+ *
+ *  Author: Sébastien Blin <sebastien.blin@savoirfairelinux.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
+ */
+
+#include "conversation_module.h"
+
+#include <fstream>
+
+#include <opendht/thread_pool.h>
+
+#include "account_const.h"
+#include "client/ring_signal.h"
+#include "fileutils.h"
+#include "jamidht/account_manager.h"
+#include "jamidht/jamiaccount.h"
+#include "manager.h"
+#include "vcard.h"
+
+namespace jami {
+
+struct PendingConversationFetch
+{
+    bool ready {false};
+    bool cloning {false};
+    std::string deviceId {};
+    std::set<std::string> connectingTo {};
+};
+
+class ConversationModule::Impl : public std::enable_shared_from_this<Impl>
+{
+public:
+    Impl(std::weak_ptr<JamiAccount>&& account,
+         NeedsSyncingCb&& needsSyncingCb,
+         SengMsgCb&& sendMsgCb,
+         NeedSocketCb&& onNeedSocket);
+
+    // Retrieving recent commits
+    /**
+     * Clone a conversation (initial) from device
+     * @param deviceId
+     * @param convId
+     */
+    void cloneConversation(const std::string& deviceId,
+                           const std::string& peer,
+                           const std::string& convId);
+
+    /**
+     * Pull remote device
+     * @param peer              Contact URI
+     * @param deviceId          Contact's device
+     * @param conversationId
+     * @param commitId (optional)
+     */
+    void fetchNewCommits(const std::string& peer,
+                         const std::string& deviceId,
+                         const std::string& conversationId,
+                         const std::string& commitId = "");
+    /**
+     * Handle events to receive new commits
+     */
+    void checkConversationsEvents();
+    bool handlePendingConversations();
+
+    // Requests
+    std::optional<ConversationRequest> getRequest(const std::string& id) const;
+
+    // Conversations
+    /**
+     * Remove a repository and all files
+     * @param convId
+     * @param sync      If we send an update to other account's devices
+     * @param force     True if ignore the removing flag
+     */
+    void removeRepository(const std::string& convId, bool sync, bool force = false);
+
+    /**
+     * Send a message notification to all members
+     * @param conversation
+     * @param commit
+     * @param sync      If we send an update to other account's devices
+     */
+    void sendMessageNotification(const std::string& conversationId,
+                                 const std::string& commitId,
+                                 bool sync);
+    void sendMessageNotification(const Conversation& conversation,
+                                 const std::string& commitId,
+                                 bool sync);
+
+    /**
+     * @return if a convId is a valid conversation (repository cloned & usable)
+     */
+    bool isConversation(const std::string& convId) const
+    {
+        std::lock_guard<std::mutex> lk(conversationsMtx_);
+        return conversations_.find(convId) != conversations_.end();
+    }
+
+    std::weak_ptr<JamiAccount> account_;
+    NeedsSyncingCb needsSyncingCb_;
+    SengMsgCb sendMsgCb_;
+    NeedSocketCb onNeedSocket_;
+
+    std::string accountId_ {};
+    std::string deviceId_ {};
+    std::string username_ {};
+
+    // Requests
+    mutable std::mutex conversationsRequestsMtx_;
+    std::map<std::string, ConversationRequest> conversationsRequests_;
+
+    // Conversations
+    mutable std::mutex conversationsMtx_ {};
+    std::map<std::string, std::shared_ptr<Conversation>> conversations_;
+    std::mutex pendingConversationsFetchMtx_ {};
+    std::map<std::string, PendingConversationFetch> pendingConversationsFetch_;
+
+    bool startFetch(const std::string& convId, const std::string& deviceId)
+    {
+        std::lock_guard<std::mutex> lk(pendingConversationsFetchMtx_);
+        auto it = pendingConversationsFetch_.find(convId);
+        if (it == pendingConversationsFetch_.end()) {
+            PendingConversationFetch pf;
+            pf.connectingTo.insert(deviceId);
+            pendingConversationsFetch_[convId] = std::move(pf);
+            return true;
+        }
+        auto& pf = it->second;
+        if (pf.ready)
+            return false; // Already doing stuff
+        if (pf.connectingTo.find(deviceId) != pf.connectingTo.end())
+            return false; // Already connecting to this device
+        pf.connectingTo.insert(deviceId);
+        return true;
+    }
+
+    // The following informations are stored on the disk
+    mutable std::mutex convInfosMtx_; // Note, should be locked after conversationsMtx_ if needed
+    std::map<std::string, ConvInfo> convInfos_;
+    // The following methods modify what is stored on the disk
+    /**
+     * @note convInfosMtx_ should be locked
+     */
+    void saveConvInfos() const { ConversationModule::saveConvInfos(accountId_, convInfos_); }
+    /**
+     * @note conversationsRequestsMtx_ should be locked
+     */
+    void saveConvRequests() const
+    {
+        ConversationModule::saveConvRequests(accountId_, conversationsRequests_);
+    }
+    bool addConversationRequest(const std::string& id, const ConversationRequest& req)
+    {
+        std::lock_guard<std::mutex> lk(conversationsRequestsMtx_);
+        auto it = conversationsRequests_.find(id);
+        if (it != conversationsRequests_.end()) {
+            // Check if updated
+            if (req == it->second)
+                return false;
+        }
+        conversationsRequests_[id] = req;
+        saveConvRequests();
+        return true;
+    }
+    void rmConversationRequest(const std::string& id)
+    {
+        std::lock_guard<std::mutex> lk(conversationsRequestsMtx_);
+        conversationsRequests_.erase(id);
+        saveConvRequests();
+    }
+
+    // Receiving new commits
+    std::shared_ptr<RepeatedTask> conversationsEventHandler {};
+
+    std::weak_ptr<Impl> weak() { return std::static_pointer_cast<Impl>(shared_from_this()); }
+};
+
+ConversationModule::Impl::Impl(std::weak_ptr<JamiAccount>&& account,
+                               NeedsSyncingCb&& needsSyncingCb,
+                               SengMsgCb&& sendMsgCb,
+                               NeedSocketCb&& onNeedSocket)
+    : account_(account)
+    , needsSyncingCb_(needsSyncingCb)
+    , sendMsgCb_(sendMsgCb)
+    , onNeedSocket_(onNeedSocket)
+{
+    if (auto shared = account.lock()) {
+        accountId_ = shared->getAccountID();
+        deviceId_ = shared->currentDeviceId();
+        if (const auto* accm = shared->accountManager())
+            if (const auto* info = accm->getInfo())
+                username_ = info->accountId;
+    }
+    conversationsRequests_ = convRequests(accountId_);
+}
+
+void
+ConversationModule::Impl::cloneConversation(const std::string& deviceId,
+                                            const std::string&,
+                                            const std::string& convId)
+{
+    JAMI_DBG("[Account %s] Clone conversation on device %s", accountId_.c_str(), deviceId.c_str());
+
+    if (!isConversation(convId)) {
+        // Note: here we don't return and connect to all members
+        // the first that will successfully connect will be used for
+        // cloning.
+        // This avoid the case when we try to clone from convInfos + sync message
+        // at the same time.
+        if (!startFetch(convId, deviceId)) {
+            JAMI_WARN("[Account %s] Already fetching %s", accountId_.c_str(), convId.c_str());
+            return;
+        }
+        onNeedSocket_(convId, deviceId, [=](const auto& channel) {
+            auto acc = account_.lock();
+            std::unique_lock<std::mutex> lk(pendingConversationsFetchMtx_);
+            auto& pending = pendingConversationsFetch_[convId];
+            if (channel && !pending.ready) {
+                pending.ready = true;
+                pending.deviceId = channel->deviceId().toString();
+                lk.unlock();
+                acc->addGitSocket(channel->deviceId(), convId, channel);
+                checkConversationsEvents();
+                return true;
+            }
+            return false;
+        });
+
+        JAMI_INFO("[Account %s] New conversation detected: %s. Ask device %s to clone it",
+                  accountId_.c_str(),
+                  convId.c_str(),
+                  deviceId.c_str());
+    } else {
+        JAMI_INFO("[Account %s] Already have conversation %s", accountId_.c_str(), convId.c_str());
+    }
+}
+
+void
+ConversationModule::Impl::fetchNewCommits(const std::string& peer,
+                                          const std::string& deviceId,
+                                          const std::string& conversationId,
+                                          const std::string& commitId)
+{
+    JAMI_DBG("[Account %s] fetch commits for peer %s on device %s",
+             accountId_.c_str(),
+             peer.c_str(),
+             deviceId.c_str());
+
+    std::unique_lock<std::mutex> lk(conversationsMtx_);
+    auto conversation = conversations_.find(conversationId);
+    if (conversation != conversations_.end() && conversation->second) {
+        if (!conversation->second->isMember(peer, true)) {
+            JAMI_WARN("[Account %s] %s is not a member of %s",
+                      accountId_.c_str(),
+                      peer.c_str(),
+                      conversationId.c_str());
+            return;
+        }
+        if (conversation->second->isBanned(deviceId)) {
+            JAMI_WARN("[Account %s] %s is a banned device in conversation %s",
+                      accountId_.c_str(),
+                      deviceId.c_str(),
+                      conversationId.c_str());
+            return;
+        }
+
+        // Retrieve current last message
+        auto lastMessageId = conversation->second->lastCommitId();
+        if (lastMessageId.empty()) {
+            JAMI_ERR("[Account %s] No message detected. This is a bug", accountId_.c_str());
+            return;
+        }
+
+        if (!startFetch(conversationId, deviceId)) {
+            JAMI_WARN("[Account %s] Already fetching %s",
+                      accountId_.c_str(),
+                      conversationId.c_str());
+            return;
+        }
+        onNeedSocket_(conversationId,
+                         deviceId,
+                         [this,
+                          conversationId = std::move(conversationId),
+                          peer = std::move(peer),
+                          deviceId = std::move(deviceId),
+                          commitId = std::move(commitId)](const auto& channel) {
+                             auto conversation = conversations_.find(conversationId);
+                             auto acc = account_.lock();
+                             if (!channel || !acc || conversation == conversations_.end()
+                                 || !conversation->second) {
+                                 std::lock_guard<std::mutex> lk(pendingConversationsFetchMtx_);
+                                 pendingConversationsFetch_.erase(conversationId);
+                                 return false;
+                             }
+                             acc->addGitSocket(channel->deviceId(), conversationId, channel);
+                             conversation->second->sync(
+                                 peer,
+                                 deviceId,
+                                 [this,
+                                  conversationId = std::move(conversationId),
+                                  peer = std::move(peer),
+                                  deviceId = std::move(deviceId),
+                                  commitId = std::move(commitId)](bool ok) {
+                                     if (!ok) {
+                                         JAMI_WARN("[Account %s] Could not fetch new commit from "
+                                                   "%s for %s, other "
+                                                   "peer may be disconnected",
+                                                   accountId_.c_str(),
+                                                   deviceId.c_str(),
+                                                   conversationId.c_str());
+                                         JAMI_INFO("[Account %s] Relaunch sync with %s for %s",
+                                                   accountId_.c_str(),
+                                                   deviceId.c_str(),
+                                                   conversationId.c_str());
+                                     }
+                                     std::lock_guard<std::mutex> lk(pendingConversationsFetchMtx_);
+                                     pendingConversationsFetch_.erase(conversationId);
+                                 },
+                                 commitId);
+                             return true;
+                         });
+    } else {
+        if (getRequest(conversationId) != std::nullopt)
+            return;
+        {
+            // Check if the conversation is cloning
+            std::lock_guard<std::mutex> lk(pendingConversationsFetchMtx_);
+            if (pendingConversationsFetch_.find(conversationId) != pendingConversationsFetch_.end())
+                return;
+        }
+        bool clone = false;
+        {
+            std::lock_guard<std::mutex> lkCi(convInfosMtx_);
+            auto convIt = convInfos_.find(conversationId);
+            clone = convIt != convInfos_.end();
+        }
+        if (clone) {
+            cloneConversation(deviceId, peer, conversationId);
+            return;
+        }
+        lk.unlock();
+        JAMI_WARN("[Account %s] Could not find conversation %s, ask for an invite",
+                  accountId_.c_str(),
+                  conversationId.c_str());
+        sendMsgCb_(peer,
+                   std::move(std::map<std::string, std::string> {
+                       {"application/invite", conversationId}}));
+    }
+}
+
+void
+ConversationModule::Impl::checkConversationsEvents()
+{
+    bool hasHandler = conversationsEventHandler and not conversationsEventHandler->isCancelled();
+    std::lock_guard<std::mutex> lk(pendingConversationsFetchMtx_);
+    if (not pendingConversationsFetch_.empty() and not hasHandler) {
+        conversationsEventHandler = Manager::instance().scheduler().scheduleAtFixedRate(
+            [w = weak()] {
+                if (auto this_ = w.lock())
+                    return this_->handlePendingConversations();
+                return false;
+            },
+            std::chrono::milliseconds(10));
+    } else if (pendingConversationsFetch_.empty() and hasHandler) {
+        conversationsEventHandler->cancel();
+        conversationsEventHandler.reset();
+    }
+}
+
+bool
+ConversationModule::Impl::handlePendingConversations()
+{
+    std::lock_guard<std::mutex> lk(pendingConversationsFetchMtx_);
+    for (auto it = pendingConversationsFetch_.begin(); it != pendingConversationsFetch_.end();) {
+        if (it->second.ready && !it->second.cloning) {
+            it->second.cloning = true;
+            dht::ThreadPool::io().run([w = weak(),
+                                       conversationId = it->first,
+                                       deviceId = it->second.deviceId]() {
+                auto sthis = w.lock();
+                if (!sthis)
+                    return;
+                // Clone and store conversation
+                auto erasePending = [&] {
+                    std::lock_guard<std::mutex> lk(sthis->pendingConversationsFetchMtx_);
+                    sthis->pendingConversationsFetch_.erase(conversationId);
+                };
+                try {
+                    auto conversation = std::make_shared<Conversation>(sthis->account_,
+                                                                       deviceId,
+                                                                       conversationId);
+                    if (!conversation->isMember(sthis->username_, true)) {
+                        JAMI_ERR("Conversation cloned but doesn't seems to be a valid member");
+                        conversation->erase();
+                        erasePending();
+                        return;
+                    }
+                    if (conversation) {
+                        auto removeRepo = false;
+                        {
+                            std::lock_guard<std::mutex> lk(sthis->conversationsMtx_);
+                            // Note: a removeContact while cloning. In this case, the conversation
+                            // must not be announced and removed.
+                            auto itConv = sthis->convInfos_.find(conversationId);
+                            if (itConv != sthis->convInfos_.end() && itConv->second.removed)
+                                removeRepo = true;
+                            sthis->conversations_.emplace(conversationId, conversation);
+                        }
+                        if (removeRepo) {
+                            sthis->removeRepository(conversationId, false, true);
+                            erasePending();
+                            return;
+                        }
+                        auto commitId = conversation->join();
+                        if (!commitId.empty())
+                            sthis->sendMessageNotification(conversationId, commitId, false);
+                        // Inform user that the conversation is ready
+                        emitSignal<DRing::ConversationSignal::ConversationReady>(sthis->accountId_,
+                                                                                 conversationId);
+                        sthis->needsSyncingCb_();
+                    }
+                } catch (const std::exception& e) {
+                    emitSignal<DRing::ConversationSignal::OnConversationError>(sthis->accountId_,
+                                                                               conversationId,
+                                                                               EFETCH,
+                                                                               e.what());
+                    JAMI_WARN("Something went wrong when cloning conversation: %s", e.what());
+                }
+                erasePending();
+            });
+        }
+        ++it;
+    }
+    return !pendingConversationsFetch_.empty();
+}
+
+std::optional<ConversationRequest>
+ConversationModule::Impl::getRequest(const std::string& id) const
+{
+    std::lock_guard<std::mutex> lk(conversationsRequestsMtx_);
+    auto it = conversationsRequests_.find(id);
+    if (it != conversationsRequests_.end())
+        return it->second;
+    return std::nullopt;
+}
+
+void
+ConversationModule::Impl::removeRepository(const std::string& conversationId, bool sync, bool force)
+{
+    std::unique_lock<std::mutex> lk(conversationsMtx_);
+    auto it = conversations_.find(conversationId);
+    if (it != conversations_.end() && it->second && (force || it->second->isRemoving())) {
+        try {
+            if (it->second->mode() == ConversationMode::ONE_TO_ONE) {
+                auto account = account_.lock();
+                for (const auto& member : it->second->getInitialMembers()) {
+                    if (member != account->getUsername()) {
+                        account->accountManager()->removeContactConversation(member);
+                    }
+                }
+            }
+        } catch (const std::exception& e) {
+            JAMI_ERR() << e.what();
+        }
+        JAMI_DBG() << "Remove conversation: " << conversationId;
+        it->second->erase();
+        conversations_.erase(it);
+        lk.unlock();
+
+        if (!sync)
+            return;
+        std::lock_guard<std::mutex> lkCi(convInfosMtx_);
+        auto convIt = convInfos_.find(conversationId);
+        if (convIt != convInfos_.end()) {
+            convIt->second.erased = std::time(nullptr);
+            needsSyncingCb_();
+        }
+        saveConvInfos();
+    }
+}
+
+void
+ConversationModule::Impl::sendMessageNotification(const std::string& conversationId,
+                                                  const std::string& commitId,
+                                                  bool sync)
+{
+    std::lock_guard<std::mutex> lk(conversationsMtx_);
+    auto it = conversations_.find(conversationId);
+    if (it != conversations_.end() && it->second) {
+        sendMessageNotification(*it->second, commitId, sync);
+    }
+}
+
+void
+ConversationModule::Impl::sendMessageNotification(const Conversation& conversation,
+                                                  const std::string& commitId,
+                                                  bool sync)
+{
+    Json::Value message;
+    message["id"] = conversation.id();
+    message["commit"] = commitId;
+    message["deviceId"] = deviceId_;
+    Json::StreamWriterBuilder builder;
+    const auto text = Json::writeString(builder, message);
+    for (const auto& members : conversation.getMembers()) {
+        auto uri = members.at("uri");
+        // Do not send to ourself, it's synced via convInfos
+        if (!sync && username_.find(uri) != std::string::npos)
+            continue;
+        // Announce to all members that a new message is sent
+        sendMsgCb_(uri,
+                   std::move(std::map<std::string, std::string> {
+                       {"application/im-gitmessage-id", text}}));
+    }
+}
+
+////////////////////////////////////////////////////////////////
+
+void
+ConversationModule::saveConvRequests(
+    const std::string& accountId,
+    const std::map<std::string, ConversationRequest>& conversationsRequests)
+{
+    auto path = fileutils::get_data_dir() + DIR_SEPARATOR_STR + accountId;
+    saveConvRequestsToPath(path, conversationsRequests);
+}
+
+void
+ConversationModule::saveConvRequestsToPath(
+    const std::string& path, const std::map<std::string, ConversationRequest>& conversationsRequests)
+{
+    std::ofstream file(path + DIR_SEPARATOR_STR + "convRequests",
+                       std::ios::trunc | std::ios::binary);
+    msgpack::pack(file, conversationsRequests);
+}
+
+void
+ConversationModule::saveConvInfos(const std::string& accountId,
+                                  const std::map<std::string, ConvInfo>& conversations)
+{
+    auto path = fileutils::get_data_dir() + DIR_SEPARATOR_STR + accountId;
+    saveConvInfosToPath(path, conversations);
+}
+
+void
+ConversationModule::saveConvInfosToPath(const std::string& path,
+                                        const std::map<std::string, ConvInfo>& conversations)
+{
+    std::ofstream file(path + DIR_SEPARATOR_STR + "convInfo", std::ios::trunc | std::ios::binary);
+    msgpack::pack(file, conversations);
+}
+
+////////////////////////////////////////////////////////////////
+
+ConversationModule::ConversationModule(std::weak_ptr<JamiAccount>&& account,
+                                       NeedsSyncingCb&& needsSyncingCb,
+                                       SengMsgCb&& sendMsgCb,
+                                       NeedSocketCb&& onNeedSocket)
+    : pimpl_ {std::make_unique<Impl>(std::move(account),
+                                     std::move(needsSyncingCb),
+                                     std::move(sendMsgCb),
+                                     std::move(onNeedSocket))}
+{
+    loadConversations();
+}
+
+void
+ConversationModule::loadConversations()
+{
+    JAMI_INFO("[Account %s] Start loading conversations…", pimpl_->accountId_.c_str());
+    auto conversationsRepositories = fileutils::readDirectory(
+        fileutils::get_data_dir() + DIR_SEPARATOR_STR + pimpl_->accountId_ + DIR_SEPARATOR_STR
+        + "conversations");
+    std::lock_guard<std::mutex> lk(pimpl_->conversationsMtx_);
+    pimpl_->conversations_.clear();
+    for (const auto& repository : conversationsRepositories) {
+        try {
+            auto conv = std::make_shared<Conversation>(pimpl_->account_, repository);
+            pimpl_->conversations_.emplace(repository, std::move(conv));
+        } catch (const std::logic_error& e) {
+            JAMI_WARN("[Account %s] Conversations not loaded : %s",
+                      pimpl_->accountId_.c_str(),
+                      e.what());
+        }
+    }
+    pimpl_->convInfos_ = convInfos(pimpl_->accountId_);
+
+    // Set removed flag
+    for (auto& [key, info] : pimpl_->convInfos_) {
+        auto itConv = pimpl_->conversations_.find(info.id);
+        if (itConv != pimpl_->conversations_.end() && info.removed)
+            itConv->second->setRemovingFlag();
+    }
+
+    JAMI_INFO("[Account %s] Conversations loaded!", pimpl_->accountId_.c_str());
+}
+
+std::vector<std::string>
+ConversationModule::getConversations() const
+{
+    std::vector<std::string> result;
+    std::lock_guard<std::mutex> lk(pimpl_->convInfosMtx_);
+    result.reserve(pimpl_->convInfos_.size());
+    for (const auto& [key, conv] : pimpl_->convInfos_) {
+        if (conv.removed)
+            continue;
+        result.emplace_back(key);
+    }
+    return result;
+}
+
+std::string
+ConversationModule::getOneToOneConversation(const std::string& uri) const noexcept
+{
+    auto acc = pimpl_->account_.lock();
+    if (!acc)
+        return {};
+    auto details = acc->getContactDetails(uri);
+    auto it = details.find(DRing::Account::TrustRequest::CONVERSATIONID);
+    if (it != details.end())
+        return it->second;
+    return {};
+}
+
+std::vector<std::map<std::string, std::string>>
+ConversationModule::getConversationRequests() const
+{
+    std::vector<std::map<std::string, std::string>> requests;
+    std::lock_guard<std::mutex> lk(pimpl_->conversationsRequestsMtx_);
+    requests.reserve(pimpl_->conversationsRequests_.size());
+    for (const auto& [id, request] : pimpl_->conversationsRequests_) {
+        if (request.declined)
+            continue; // Do not add declined requests
+        requests.emplace_back(request.toMap());
+    }
+    return requests;
+}
+
+void
+ConversationModule::onTrustRequest(const std::string& uri,
+                                   const std::string& conversationId,
+                                   const std::vector<uint8_t>& payload,
+                                   time_t received)
+{
+    {
+        std::lock_guard<std::mutex> lk(pimpl_->conversationsMtx_);
+        auto itConv = pimpl_->conversations_.find(conversationId);
+        if (itConv != pimpl_->conversations_.end()) {
+            JAMI_INFO("[Account %s] Received a request for a conversation "
+                      "already handled. Ignore",
+                      pimpl_->accountId_.c_str());
+            return;
+        }
+    }
+    if (pimpl_->getRequest(conversationId) != std::nullopt) {
+        JAMI_INFO("[Account %s] Received a request for a conversation "
+                  "already existing. Ignore",
+                  pimpl_->accountId_.c_str());
+        return;
+    }
+    emitSignal<DRing::ConfigurationSignal::IncomingTrustRequest>(pimpl_->accountId_,
+                                                                 conversationId,
+                                                                 uri,
+                                                                 payload,
+                                                                 received);
+    ConversationRequest req;
+    req.from = uri;
+    req.conversationId = conversationId;
+    req.received = std::time(nullptr);
+    auto details = vCard::utils::toMap(
+        std::string_view(reinterpret_cast<const char*>(payload.data()), payload.size()));
+    req.metadatas = ConversationRepository::infosFromVCard(details);
+    auto reqMap = req.toMap();
+    pimpl_->addConversationRequest(conversationId, std::move(req));
+    emitSignal<DRing::ConversationSignal::ConversationRequestReceived>(pimpl_->accountId_,
+                                                                       conversationId,
+                                                                       reqMap);
+}
+
+void
+ConversationModule::onConversationRequest(const std::string& from, const Json::Value& value)
+{
+    ConversationRequest req(value);
+    JAMI_INFO("[Account %s] Receive a new conversation request for conversation %s from %s",
+              pimpl_->accountId_.c_str(),
+              req.conversationId.c_str(),
+              from.c_str());
+    auto convId = req.conversationId;
+    req.from = from;
+
+    if (pimpl_->getRequest(convId) != std::nullopt) {
+        JAMI_INFO("[Account %s] Received a request for a conversation already existing. "
+                  "Ignore",
+                  pimpl_->accountId_.c_str());
+        return;
+    }
+    req.received = std::time(nullptr);
+    auto reqMap = req.toMap();
+    pimpl_->addConversationRequest(convId, std::move(req));
+    // Note: no need to sync here because other connected devices should receive
+    // the same conversation request. Will sync when the conversation will be added
+
+    emitSignal<DRing::ConversationSignal::ConversationRequestReceived>(pimpl_->accountId_,
+                                                                       convId,
+                                                                       reqMap);
+}
+
+void
+ConversationModule::onNeedConversationRequest(const std::string& from,
+                                              const std::string& conversationId)
+{
+    // Check if the conversation exists
+    std::unique_lock<std::mutex> lk(pimpl_->conversationsMtx_);
+    auto itConv = pimpl_->conversations_.find(conversationId);
+    if (itConv != pimpl_->conversations_.end() && !itConv->second->isRemoving()) {
+        if (!itConv->second->isMember(from, true)) {
+            JAMI_WARN("%s is asking a new invite for %s, but not a member",
+                      from.c_str(),
+                      conversationId.c_str());
+            return;
+        }
+
+        // Send new invite
+        auto invite = itConv->second->generateInvitation();
+        lk.unlock();
+        JAMI_DBG("%s is asking a new invite for %s", from.c_str(), conversationId.c_str());
+        pimpl_->sendMsgCb_(from, std::move(invite));
+    }
+}
+
+void
+ConversationModule::acceptConversationRequest(const std::string& conversationId)
+{
+    auto acc = pimpl_->account_.lock();
+    // For all conversation members, try to open a git channel with this conversation ID
+    auto request = pimpl_->getRequest(conversationId);
+    if (!acc || request == std::nullopt) {
+        JAMI_WARN("[Account %s] Request not found for conversation %s",
+                  pimpl_->accountId_.c_str(),
+                  conversationId.c_str());
+        return;
+    }
+    auto memberHash = dht::InfoHash(request->from);
+    if (!memberHash) {
+        JAMI_WARN("Invalid member detected: %s", request->from.c_str());
+        return;
+    }
+    acc->forEachDevice(memberHash,
+                       [w = pimpl_->weak(), request = *request, conversationId](
+                           const std::shared_ptr<dht::crypto::PublicKey>& pk) {
+                           auto sthis = w.lock();
+                           auto deviceId = pk->getLongId().toString();
+                           if (!sthis or deviceId == sthis->deviceId_)
+                               return;
+
+                           if (!sthis->startFetch(conversationId, deviceId)) {
+                               JAMI_WARN("[Account %s] Already fetching %s",
+                                         sthis->accountId_.c_str(),
+                                         conversationId.c_str());
+                               return;
+                           }
+                           sthis->onNeedSocket_(
+                               conversationId, pk->getLongId().toString(), [=](const auto& channel) {
+                                   auto acc = sthis->account_.lock();
+                                   std::unique_lock<std::mutex> lk(
+                                       sthis->pendingConversationsFetchMtx_);
+                                   auto& pending = sthis->pendingConversationsFetch_[conversationId];
+                                   if (channel && !pending.ready) {
+                                       pending.ready = true;
+                                       pending.deviceId = channel->deviceId().toString();
+                                       lk.unlock();
+                                       // Save the git socket
+                                       acc->addGitSocket(channel->deviceId(),
+                                                         conversationId,
+                                                         channel);
+                                       sthis->checkConversationsEvents();
+                                       return true;
+                                   }
+                                   return false;
+                               });
+                       });
+    pimpl_->rmConversationRequest(conversationId);
+    ConvInfo info;
+    info.id = conversationId;
+    info.created = std::time(nullptr);
+    info.members.emplace_back(pimpl_->username_);
+    info.members.emplace_back(request->from);
+    addConvInfo(info);
+}
+
+void
+ConversationModule::declineConversationRequest(const std::string& conversationId)
+{
+    std::lock_guard<std::mutex> lk(pimpl_->conversationsRequestsMtx_);
+    auto it = pimpl_->conversationsRequests_.find(conversationId);
+    if (it != pimpl_->conversationsRequests_.end()) {
+        it->second.declined = std::time(nullptr);
+        pimpl_->saveConvRequests();
+    }
+    emitSignal<DRing::ConversationSignal::ConversationRequestDeclined>(pimpl_->accountId_,
+                                                                       conversationId);
+    pimpl_->needsSyncingCb_();
+}
+
+std::string
+ConversationModule::startConversation(ConversationMode mode, const std::string& otherMember)
+{
+    // Create the conversation object
+    std::shared_ptr<Conversation> conversation;
+    try {
+        conversation = std::make_shared<Conversation>(pimpl_->account_, mode, otherMember);
+    } catch (const std::exception& e) {
+        JAMI_ERR("[Account %s] Error while generating a conversation %s",
+                 pimpl_->accountId_.c_str(),
+                 e.what());
+        return {};
+    }
+    auto convId = conversation->id();
+    {
+        std::lock_guard<std::mutex> lk(pimpl_->conversationsMtx_);
+        pimpl_->conversations_[convId] = std::move(conversation);
+    }
+
+    // Update convInfo
+    ConvInfo info;
+    info.id = convId;
+    info.created = std::time(nullptr);
+    info.members.emplace_back(pimpl_->username_);
+    if (!otherMember.empty())
+        info.members.emplace_back(otherMember);
+    addConvInfo(info);
+
+    pimpl_->needsSyncingCb_();
+
+    emitSignal<DRing::ConversationSignal::ConversationReady>(pimpl_->accountId_, convId);
+    return convId;
+}
+
+// Message send/load
+void
+ConversationModule::sendMessage(const std::string& conversationId,
+                                const std::string& message,
+                                const std::string& parent,
+                                const std::string& type,
+                                bool announce,
+                                const OnDoneCb& cb)
+{
+    Json::Value json;
+    json["body"] = message;
+    json["type"] = type;
+    sendMessage(conversationId, json, parent, announce, cb);
+}
+
+void
+ConversationModule::sendMessage(const std::string& conversationId,
+                                const Json::Value& value,
+                                const std::string& parent,
+                                bool announce,
+                                const OnDoneCb& cb)
+{
+    std::lock_guard<std::mutex> lk(pimpl_->conversationsMtx_);
+    auto conversation = pimpl_->conversations_.find(conversationId);
+    if (conversation != pimpl_->conversations_.end() && conversation->second) {
+        conversation->second->sendMessage(
+            value,
+            parent,
+            [this, conversationId, announce, cb = std::move(cb)](bool ok,
+                                                                 const std::string& commitId) {
+                if (cb)
+                    cb(ok, commitId);
+                if (!announce)
+                    return;
+                if (ok)
+                    pimpl_->sendMessageNotification(conversationId, commitId, true);
+                else
+                    JAMI_ERR("Failed to send message to conversation %s", conversationId.c_str());
+            });
+    }
+}
+
+void
+ConversationModule::addCallHistoryMessage(const std::string& uri, uint64_t duration_ms)
+{
+    auto finalUri = uri.substr(0, uri.find("@ring.dht"));
+    finalUri = finalUri.substr(0, uri.find("@jami.dht"));
+    auto convId = getOneToOneConversation(finalUri);
+    if (!convId.empty()) {
+        Json::Value value;
+        value["to"] = finalUri;
+        value["type"] = "application/call-history+json";
+        value["duration"] = std::to_string(duration_ms);
+        sendMessage(convId, value);
+    }
+}
+
+void
+ConversationModule::onMessageDisplayed(const std::string& peer,
+                                       const std::string& conversationId,
+                                       const std::string& interactionId)
+{
+    std::unique_lock<std::mutex> lk(pimpl_->conversationsMtx_);
+    auto conversation = pimpl_->conversations_.find(conversationId);
+    if (conversation != pimpl_->conversations_.end() && conversation->second) {
+        conversation->second->setMessageDisplayed(peer, interactionId);
+    }
+}
+
+uint32_t
+ConversationModule::loadConversationMessages(const std::string& conversationId,
+                                             const std::string& fromMessage,
+                                             size_t n)
+{
+    std::lock_guard<std::mutex> lk(pimpl_->conversationsMtx_);
+    auto acc = pimpl_->account_.lock();
+    auto conversation = pimpl_->conversations_.find(conversationId);
+    if (acc && conversation != pimpl_->conversations_.end() && conversation->second) {
+        const uint32_t id = std::uniform_int_distribution<uint32_t> {}(acc->rand);
+        conversation->second->loadMessages(
+            [accountId = pimpl_->accountId_, conversationId, id](auto&& messages) {
+                emitSignal<DRing::ConversationSignal::ConversationLoaded>(id,
+                                                                          accountId,
+                                                                          conversationId,
+                                                                          messages);
+            },
+            fromMessage,
+            n);
+        return id;
+    }
+    return 0;
+}
+
+std::shared_ptr<TransferManager>
+ConversationModule::dataTransfer(const std::string& id) const
+{
+    std::lock_guard<std::mutex> lk(pimpl_->conversationsMtx_);
+    auto conversation = pimpl_->conversations_.find(id);
+    if (conversation != pimpl_->conversations_.end() && conversation->second)
+        return conversation->second->dataTransfer();
+    return {};
+}
+
+bool
+ConversationModule::onFileChannelRequest(const std::string& conversationId,
+                                         const std::string& member,
+                                         const std::string& fileId,
+                                         bool verifyShaSum) const
+{
+    std::lock_guard<std::mutex> lk(pimpl_->conversationsMtx_);
+    auto conversation = pimpl_->conversations_.find(conversationId);
+    if (conversation != pimpl_->conversations_.end() && conversation->second)
+        return conversation->second->onFileChannelRequest(member, fileId, verifyShaSum);
+    return false;
+}
+
+bool
+ConversationModule::downloadFile(const std::string& conversationId,
+                                 const std::string& interactionId,
+                                 const std::string& fileId,
+                                 const std::string& path,
+                                 size_t start,
+                                 size_t end)
+{
+    std::string sha3sum = {};
+    std::lock_guard<std::mutex> lk(pimpl_->conversationsMtx_);
+    auto conversation = pimpl_->conversations_.find(conversationId);
+    if (conversation == pimpl_->conversations_.end() || !conversation->second)
+        return false;
+
+    return conversation->second->downloadFile(interactionId, fileId, path, "", "", start, end);
+}
+
+void
+ConversationModule::syncConversations(const std::string& peer, const std::string& deviceId)
+{
+    // Sync conversations where peer is member
+    std::set<std::string> toFetch;
+    std::set<std::string> toClone;
+    {
+        std::lock_guard<std::mutex> lk(pimpl_->conversationsMtx_);
+        std::lock_guard<std::mutex> lkCI(pimpl_->convInfosMtx_);
+        for (const auto& [key, ci] : pimpl_->convInfos_) {
+            auto it = pimpl_->conversations_.find(key);
+            if (it != pimpl_->conversations_.end() && it->second) {
+                if (!it->second->isRemoving() && it->second->isMember(peer, false))
+                    toFetch.emplace(key);
+            } else if (!ci.removed
+                       && std::find(ci.members.begin(), ci.members.end(), peer)
+                              != ci.members.end()) {
+                // In this case the conversation was never cloned (can be after an import)
+                toClone.emplace(key);
+            }
+        }
+    }
+    for (const auto& cid : toFetch)
+        pimpl_->fetchNewCommits(peer, deviceId, cid);
+    for (const auto& cid : toClone)
+        pimpl_->cloneConversation(deviceId, peer, cid);
+}
+
+void
+ConversationModule::onSyncData(const SyncMsg& msg,
+                               const std::string& peerId,
+                               const std::string& deviceId)
+{
+    for (const auto& [key, convInfo] : msg.c) {
+        auto convId = convInfo.id;
+        auto removed = convInfo.removed;
+        pimpl_->rmConversationRequest(convId);
+        if (not removed) {
+            // If multi devices, it can detect a conversation that was already
+            // removed, so just check if the convinfo contains a removed conv
+            auto itConv = pimpl_->convInfos_.find(convId);
+            if (itConv != pimpl_->convInfos_.end() && itConv->second.removed)
+                continue;
+            pimpl_->cloneConversation(deviceId, peerId, convId);
+        } else {
+            {
+                std::lock_guard<std::mutex> lk(pimpl_->conversationsMtx_);
+                auto itConv = pimpl_->conversations_.find(convId);
+                if (itConv != pimpl_->conversations_.end() && !itConv->second->isRemoving()) {
+                    emitSignal<DRing::ConversationSignal::ConversationRemoved>(pimpl_->accountId_,
+                                                                               convId);
+                    itConv->second->setRemovingFlag();
+                }
+            }
+            std::unique_lock<std::mutex> lk(pimpl_->convInfosMtx_);
+            auto& ci = pimpl_->convInfos_;
+            auto itConv = ci.find(convId);
+            if (itConv != ci.end()) {
+                itConv->second.removed = std::time(nullptr);
+                if (convInfo.erased) {
+                    itConv->second.erased = std::time(nullptr);
+                    pimpl_->saveConvInfos();
+                    lk.unlock();
+                    pimpl_->removeRepository(convId, false);
+                }
+                break;
+            }
+        }
+    }
+
+    for (const auto& [convId, req] : msg.cr) {
+        if (pimpl_->isConversation(convId)) {
+            // Already accepted request
+            pimpl_->rmConversationRequest(convId);
+            continue;
+        }
+
+        // New request
+        if (!pimpl_->addConversationRequest(convId, req))
+            continue;
+
+        if (req.declined != 0) {
+            // Request declined
+            JAMI_INFO("[Account %s] Declined request detected for conversation %s (device %s)",
+                      pimpl_->accountId_.c_str(),
+                      convId.c_str(),
+                      deviceId.c_str());
+            emitSignal<DRing::ConversationSignal::ConversationRequestDeclined>(pimpl_->accountId_,
+                                                                               convId);
+            continue;
+        }
+
+        JAMI_INFO("[Account %s] New request detected for conversation %s (device %s)",
+                  pimpl_->accountId_.c_str(),
+                  convId.c_str(),
+                  deviceId.c_str());
+
+        emitSignal<DRing::ConversationSignal::ConversationRequestReceived>(pimpl_->accountId_,
+                                                                           convId,
+                                                                           req.toMap());
+    }
+}
+
+bool
+ConversationModule::needsSyncingWith(const std::string& memberUri, const std::string& deviceId) const
+{
+    std::unique_lock<std::mutex> lk(pimpl_->conversationsMtx_);
+    for (const auto& [_, conv] : pimpl_->conversations_)
+        if (conv->isMember(memberUri, false) && conv->needsFetch(deviceId))
+            return true;
+    return false;
+}
+
+void
+ConversationModule::setFetched(const std::string& conversationId, const std::string& deviceId)
+{
+    auto remove = false;
+    {
+        std::unique_lock<std::mutex> lk(pimpl_->conversationsMtx_);
+        auto it = pimpl_->conversations_.find(conversationId);
+        if (it != pimpl_->conversations_.end() && it->second) {
+            remove = it->second->isRemoving();
+            it->second->hasFetched(deviceId);
+        }
+    }
+    if (remove)
+        pimpl_->removeRepository(conversationId, true);
+}
+
+void
+ConversationModule::onNewCommit(const std::string& peer,
+                                   const std::string& deviceId,
+                                   const std::string& conversationId,
+                                   const std::string& commitId)
+{
+    std::unique_lock<std::mutex> lk(pimpl_->conversationsMtx_);
+    auto itConv = pimpl_->convInfos_.find(conversationId);
+    if (itConv != pimpl_->convInfos_.end() && itConv->second.removed)
+        return; // ignore new commits for removed conversation
+    JAMI_DBG("[Account %s] on new commit notification from %s, for %s, commit %s",
+             pimpl_->accountId_.c_str(),
+             peer.c_str(),
+             conversationId.c_str(),
+             commitId.c_str());
+    lk.unlock();
+    pimpl_->fetchNewCommits(peer, deviceId, conversationId, commitId);
+}
+
+void
+ConversationModule::addConversationMember(const std::string& conversationId,
+                                          const std::string& contactUri,
+                                          bool sendRequest)
+{
+    std::unique_lock<std::mutex> lk(pimpl_->conversationsMtx_);
+    // Add a new member in the conversation
+    auto it = pimpl_->conversations_.find(conversationId);
+    if (it == pimpl_->conversations_.end()) {
+        JAMI_ERR("Conversation %s doesn't exist", conversationId.c_str());
+        return;
+    }
+
+    if (it->second->isMember(contactUri, true)) {
+        JAMI_DBG("%s is already a member of %s, resend invite",
+                 contactUri.c_str(),
+                 conversationId.c_str());
+        // Note: This should not be necessary, but if for whatever reason the other side didn't join
+        // we should not forbid new invites
+        auto invite = it->second->generateInvitation();
+        lk.unlock();
+        pimpl_->sendMsgCb_(contactUri, std::move(invite));
+        return;
+    }
+
+    it->second
+        ->addMember(contactUri,
+                    [this, conversationId, sendRequest, contactUri](bool ok,
+                                                                    const std::string& commitId) {
+                        if (ok) {
+                            std::unique_lock<std::mutex> lk(pimpl_->conversationsMtx_);
+                            auto it = pimpl_->conversations_.find(conversationId);
+                            if (it != pimpl_->conversations_.end() && it->second) {
+                                pimpl_->sendMessageNotification(*it->second,
+                                                                commitId,
+                                                                true); // For the other members
+                                if (sendRequest) {
+                                    auto invite = it->second->generateInvitation();
+                                    lk.unlock();
+                                    pimpl_->sendMsgCb_(contactUri, std::move(invite));
+                                }
+                            }
+                        }
+                    });
+}
+
+void
+ConversationModule::removeConversationMember(const std::string& conversationId,
+                                             const std::string& contactUri,
+                                             bool isDevice)
+{
+    std::lock_guard<std::mutex> lk(pimpl_->conversationsMtx_);
+    auto it = pimpl_->conversations_.find(conversationId);
+    if (it != pimpl_->conversations_.end() && it->second) {
+        it->second->removeMember(contactUri,
+                                 isDevice,
+                                 [this, conversationId](bool ok, const std::string& commitId) {
+                                     if (ok) {
+                                         pimpl_->sendMessageNotification(conversationId,
+                                                                         commitId,
+                                                                         true);
+                                     }
+                                 });
+    }
+}
+
+std::vector<std::map<std::string, std::string>>
+ConversationModule::getConversationMembers(const std::string& conversationId) const
+{
+    std::unique_lock<std::mutex> lk(pimpl_->conversationsMtx_);
+    auto conversation = pimpl_->conversations_.find(conversationId);
+    if (conversation != pimpl_->conversations_.end() && conversation->second)
+        return conversation->second->getMembers(true, true);
+
+    lk.unlock();
+    std::lock_guard<std::mutex> lkCI(pimpl_->convInfosMtx_);
+    auto convIt = pimpl_->convInfos_.find(conversationId);
+    if (convIt != pimpl_->convInfos_.end()) {
+        std::vector<std::map<std::string, std::string>> result;
+        result.reserve(convIt->second.members.size());
+        for (const auto& uri : convIt->second.members) {
+            result.emplace_back(std::map<std::string, std::string> {{"uri", uri}});
+        }
+        return result;
+    }
+    return {};
+}
+
+uint32_t
+ConversationModule::countInteractions(const std::string& convId,
+                                      const std::string& toId,
+                                      const std::string& fromId,
+                                      const std::string& authorUri) const
+{
+    std::lock_guard<std::mutex> lk(pimpl_->conversationsMtx_);
+    auto conversation = pimpl_->conversations_.find(convId);
+    if (conversation != pimpl_->conversations_.end() && conversation->second) {
+        return conversation->second->countInteractions(toId, fromId, authorUri);
+    }
+    return 0;
+}
+
+void
+ConversationModule::updateConversationInfos(const std::string& conversationId,
+                                            const std::map<std::string, std::string>& infos,
+                                            bool sync)
+{
+    std::lock_guard<std::mutex> lk(pimpl_->conversationsMtx_);
+    // Add a new member in the conversation
+    auto it = pimpl_->conversations_.find(conversationId);
+    if (it == pimpl_->conversations_.end()) {
+        JAMI_ERR("Conversation %s doesn't exist", conversationId.c_str());
+        return;
+    }
+
+    it->second->updateInfos(infos,
+                            [this, conversationId, sync](bool ok, const std::string& commitId) {
+                                if (ok && sync) {
+                                    pimpl_->sendMessageNotification(conversationId, commitId, true);
+                                } else if (sync)
+                                    JAMI_WARN("Couldn't update infos on %s", conversationId.c_str());
+                            });
+}
+
+std::map<std::string, std::string>
+ConversationModule::conversationInfos(const std::string& conversationId) const
+{
+    std::lock_guard<std::mutex> lk(pimpl_->conversationsMtx_);
+    // Add a new member in the conversation
+    auto it = pimpl_->conversations_.find(conversationId);
+    if (it == pimpl_->conversations_.end() or not it->second) {
+        std::lock_guard<std::mutex> lkCi(pimpl_->convInfosMtx_);
+        auto itConv = pimpl_->convInfos_.find(conversationId);
+        if (itConv == pimpl_->convInfos_.end()) {
+            JAMI_ERR("Conversation %s doesn't exist", conversationId.c_str());
+            return {};
+        }
+        return {{"syncing", "true"}};
+    }
+
+    return it->second->infos();
+}
+
+std::vector<uint8_t>
+ConversationModule::conversationVCard(const std::string& conversationId) const
+{
+    std::lock_guard<std::mutex> lk(pimpl_->conversationsMtx_);
+    // Add a new member in the conversation
+    auto it = pimpl_->conversations_.find(conversationId);
+    if (it == pimpl_->conversations_.end() || !it->second) {
+        JAMI_ERR("Conversation %s doesn't exist", conversationId.c_str());
+        return {};
+    }
+
+    return it->second->vCard();
+}
+
+bool
+ConversationModule::isBannedDevice(const std::string& convId, const std::string& deviceId) const
+{
+    std::unique_lock<std::mutex> lk(pimpl_->conversationsMtx_);
+    auto conversation = pimpl_->conversations_.find(convId);
+    return conversation == pimpl_->conversations_.end() || !conversation->second
+           || conversation->second->isBanned(deviceId);
+}
+
+void
+ConversationModule::removeContact(const std::string& uri, bool ban)
+{
+    // Remove related conversation
+    auto isSelf = uri == pimpl_->username_;
+    bool updateConvInfos = false;
+    std::vector<std::string> toRm;
+    {
+        std::lock_guard<std::mutex> lk(pimpl_->conversationsMtx_);
+        std::lock_guard<std::mutex> lkCi(pimpl_->convInfosMtx_);
+        for (auto& [convId, conv] : pimpl_->convInfos_) {
+            auto itConv = pimpl_->conversations_.find(convId);
+            if (itConv != pimpl_->conversations_.end() && itConv->second) {
+                try {
+                    // Note it's important to check getUsername(), else
+                    // removing self can remove all conversations
+                    if (itConv->second->mode() == ConversationMode::ONE_TO_ONE) {
+                        auto initMembers = itConv->second->getInitialMembers();
+                        if ((isSelf && initMembers.size() == 1)
+                            || std::find(initMembers.begin(), initMembers.end(), uri)
+                                   != initMembers.end())
+                            toRm.emplace_back(convId);
+                    }
+                } catch (const std::exception& e) {
+                    JAMI_WARN("%s", e.what());
+                }
+            } else if (std::find(conv.members.begin(), conv.members.end(), uri)
+                       != conv.members.end()) {
+                // It's syncing with uri, mark as removed!
+                conv.removed = std::time(nullptr);
+                updateConvInfos = true;
+            }
+        }
+        if (updateConvInfos)
+            pimpl_->saveConvInfos();
+    }
+    // Note, if we ban the device, we don't send the leave cause the other peer will just
+    // never got the notifications, so just erase the datas
+    for (const auto& id : toRm)
+        if (ban)
+            pimpl_->removeRepository(id, false, true);
+        else
+            removeConversation(id);
+}
+
+bool
+ConversationModule::removeConversation(const std::string& conversationId)
+{
+    std::unique_lock<std::mutex> lk(pimpl_->conversationsMtx_);
+    auto it = pimpl_->conversations_.find(conversationId);
+    if (it == pimpl_->conversations_.end()) {
+        JAMI_ERR("Conversation %s doesn't exist", conversationId.c_str());
+        return false;
+    }
+    auto members = it->second->getMembers();
+    auto hasMembers = !(members.size() == 1
+                        && pimpl_->username_.find(members[0]["uri"]) != std::string::npos);
+    // Update convInfos
+    std::unique_lock<std::mutex> lockCi(pimpl_->convInfosMtx_);
+    auto itConv = pimpl_->convInfos_.find(conversationId);
+    if (itConv != pimpl_->convInfos_.end()) {
+        itConv->second.removed = std::time(nullptr);
+        // Sync now, because it can take some time to really removes the datas
+        if (hasMembers)
+            pimpl_->needsSyncingCb_();
+    }
+    pimpl_->saveConvInfos();
+    lockCi.unlock();
+    auto commitId = it->second->leave();
+    emitSignal<DRing::ConversationSignal::ConversationRemoved>(pimpl_->accountId_, conversationId);
+    if (hasMembers) {
+        JAMI_DBG() << "Wait that someone sync that user left conversation " << conversationId;
+        // Commit that we left
+        if (!commitId.empty()) {
+            // Do not sync as it's synched by convInfos
+            pimpl_->sendMessageNotification(*it->second, commitId, false);
+        } else {
+            JAMI_ERR("Failed to send message to conversation %s", conversationId.c_str());
+        }
+        // In this case, we wait that another peer sync the conversation
+        // to definitely remove it from the device. This is to inform the
+        // peer that we left the conversation and never want to receive
+        // any messages
+        return true;
+    }
+    lk.unlock();
+    // Else we are the last member, so we can remove
+    pimpl_->removeRepository(conversationId, true);
+    return true;
+}
+
+void
+ConversationModule::checkIfRemoveForCompat(const std::string& peerUri)
+{
+    auto convId = getOneToOneConversation(peerUri);
+    if (convId.empty())
+        return;
+    std::unique_lock<std::mutex> lk(pimpl_->conversationsMtx_);
+    auto it = pimpl_->conversations_.find(convId);
+    if (it == pimpl_->conversations_.end()) {
+        JAMI_ERR("Conversation %s doesn't exist", convId.c_str());
+        return;
+    }
+    // We will only removes the conversation if the member is invited
+    // the contact can have mutiple devices with only some with swarm
+    // support, in this case, just go with recent versions.
+    if (it->second->isMember(peerUri))
+        return;
+    lk.unlock();
+    removeConversation(convId);
+}
+
+std::map<std::string, ConvInfo>
+ConversationModule::convInfos(const std::string& accountId)
+{
+    auto path = fileutils::get_data_dir() + DIR_SEPARATOR_STR + accountId;
+    return convInfosFromPath(path);
+}
+
+std::map<std::string, ConvInfo>
+ConversationModule::convInfosFromPath(const std::string& path)
+{
+    std::map<std::string, ConvInfo> convInfos;
+    try {
+        // read file
+        auto file = fileutils::loadFile("convInfo", path);
+        // load values
+        msgpack::object_handle oh = msgpack::unpack((const char*) file.data(), file.size());
+        oh.get().convert(convInfos);
+    } catch (const std::exception& e) {
+        JAMI_WARN("[convInfo] error loading convInfo: %s", e.what());
+    }
+    return convInfos;
+}
+
+std::map<std::string, ConversationRequest>
+ConversationModule::convRequests(const std::string& accountId)
+{
+    auto path = fileutils::get_data_dir() + DIR_SEPARATOR_STR + accountId;
+    return convRequestsFromPath(path);
+}
+
+std::map<std::string, ConversationRequest>
+ConversationModule::convRequestsFromPath(const std::string& path)
+{
+    std::map<std::string, ConversationRequest> convRequests;
+    try {
+        // read file
+        auto file = fileutils::loadFile("convRequests", path);
+        // load values
+        msgpack::object_handle oh = msgpack::unpack((const char*) file.data(), file.size());
+        oh.get().convert(convRequests);
+    } catch (const std::exception& e) {
+        JAMI_WARN("[convInfo] error loading convInfo: %s", e.what());
+    }
+    return convRequests;
+}
+
+void
+ConversationModule::addConvInfo(const ConvInfo& info)
+{
+    std::lock_guard<std::mutex> lk(pimpl_->convInfosMtx_);
+    pimpl_->convInfos_[info.id] = info;
+    pimpl_->saveConvInfos();
+}
+
+void
+ConversationModule::setConversationMembers(const std::string& convId,
+                                           const std::vector<std::string>& members)
+{
+    std::lock_guard<std::mutex> lk(pimpl_->convInfosMtx_);
+    auto convIt = pimpl_->convInfos_.find(convId);
+    if (convIt != pimpl_->convInfos_.end()) {
+        convIt->second.members = members;
+        pimpl_->saveConvInfos();
+    }
+}
+
+} // namespace jami
\ No newline at end of file
diff --git a/src/jamidht/conversation_module.h b/src/jamidht/conversation_module.h
new file mode 100644
index 0000000000000000000000000000000000000000..ff8209520eb4aad9430275c09a292500b01cfbcd
--- /dev/null
+++ b/src/jamidht/conversation_module.h
@@ -0,0 +1,343 @@
+/*
+ *  Copyright (C) 2021 Savoir-faire Linux Inc.
+ *
+ *  Author: Sébastien Blin <sebastien.blin@savoirfairelinux.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
+ */
+
+#pragma once
+
+#include "scheduled_executor.h"
+#include "jamidht/conversation.h"
+#include "jamidht/conversationrepository.h"
+#include "jamidht/jami_contact.h"
+
+#include <mutex>
+#include <msgpack.hpp>
+
+namespace jami {
+
+struct SyncMsg
+{
+    jami::DeviceSync ds;
+    std::map<std::string, jami::ConvInfo> c;
+    std::map<std::string, jami::ConversationRequest> cr;
+    MSGPACK_DEFINE(ds, c, cr)
+};
+
+using ChannelCb = std::function<bool(const std::shared_ptr<ChannelSocket>&)>;
+using NeedSocketCb = std::function<void(const std::string&, const std::string&, ChannelCb&&)>;
+using SengMsgCb = std::function<void(const std::string&, std::map<std::string, std::string>&&)>;
+using NeedsSyncingCb = std::function<void()>;
+
+class ConversationModule
+{
+public:
+    ConversationModule(std::weak_ptr<JamiAccount>&& account,
+                       NeedsSyncingCb&& needsSyncingCb,
+                       SengMsgCb&& sendMsgCb,
+                       NeedSocketCb&& onNeedSocket);
+    ~ConversationModule() = default;
+
+    /**
+     * Refresh informations about conversations
+     */
+    void loadConversations();
+
+    /**
+     * Return all conversation's id (including syncing ones)
+     */
+    std::vector<std::string> getConversations() const;
+
+    /**
+     * Get related conversation with member
+     * @param uri       The member to search for
+     * @return the conversation id if found else empty
+     */
+    std::string getOneToOneConversation(const std::string& uri) const noexcept;
+
+    /**
+     * Return conversation's requests
+     */
+    std::vector<std::map<std::string, std::string>> getConversationRequests() const;
+
+    /**
+     * Called when detecting a new trust request with linked one to one
+     * @param uri               Sender's URI
+     * @param conversationId    Related conversation's id
+     * @param payload           VCard
+     * @param received          Received time
+     */
+    void onTrustRequest(const std::string& uri,
+                        const std::string& conversationId,
+                        const std::vector<uint8_t>& payload,
+                        time_t received);
+
+    /**
+     * Called when receiving a new conversation's request
+     * @param from      Sender
+     * @param value     Conversation's request
+     */
+    void onConversationRequest(const std::string& from, const Json::Value& value);
+
+    /**
+     * Called when a peer needs an invite for a conversation (generally after that they received
+     * a commit notification for a conversation they don't have yet)
+     * @param from
+     * @param conversationId
+     */
+    void onNeedConversationRequest(const std::string& from, const std::string& conversationId);
+
+    /**
+     * Accepts a conversation's request
+     * @param convId
+     */
+    void acceptConversationRequest(const std::string& conversationId);
+
+    /**
+     * Decline a conversation's request
+     * @param convId
+     */
+    void declineConversationRequest(const std::string& conversationId);
+
+    /**
+     * Starts a new conversation
+     * @param mode          Wanted mode
+     * @param otherMember   If needed (one to one)
+     * @return conversation's id
+     */
+    std::string startConversation(ConversationMode mode = ConversationMode::INVITES_ONLY,
+                                  const std::string& otherMember = "");
+
+    // Message send/load
+    void sendMessage(const std::string& conversationId,
+                     const Json::Value& value,
+                     const std::string& parent = "",
+                     bool announce = true,
+                     const OnDoneCb& cb = {});
+
+    void sendMessage(const std::string& conversationId,
+                     const std::string& message,
+                     const std::string& parent = "",
+                     const std::string& type = "text/plain",
+                     bool announce = true,
+                     const OnDoneCb& cb = {});
+
+    /**
+     * Add to the related conversation the call history message
+     * @param uri           Peer number
+     * @param duration_ms   The call duration in ms
+     */
+    void addCallHistoryMessage(const std::string& uri, uint64_t duration_ms);
+
+    // Received that a peer displayed a message
+    void onMessageDisplayed(const std::string& peer,
+                            const std::string& conversationId,
+                            const std::string& interactionId);
+
+    /**
+     * Load conversation's messages
+     * @param conversationId    Conversation to load
+     * @param fromMessage
+     * @param n                 Max interactions to load
+     * @return id of the operation
+     */
+    uint32_t loadConversationMessages(const std::string& conversationId,
+                                      const std::string& fromMessage = "",
+                                      size_t n = 0);
+
+    // File transfer
+    /**
+     * Returns related transfer manager
+     * @param id        Conversation's id
+     * @return nullptr if not found, else the manager
+     */
+    std::shared_ptr<TransferManager> dataTransfer(const std::string& id) const;
+
+    /**
+     * Choose if we can accept channel request
+     * @param member        Member to check
+     * @param fileId        File transfer to check (needs to be waiting)
+     * @param verifyShaSum  For debug only
+     * @return if we accept the channel request
+     */
+    bool onFileChannelRequest(const std::string& conversationId,
+                              const std::string& member,
+                              const std::string& fileId,
+                              bool verifyShaSum = true) const;
+
+    /**
+     * Ask conversation's members to send a file to this device
+     * @param conversationId    Related conversation
+     * @param interactionId     Related interaction
+     * @param fileId            Related fileId
+     * @param path              where to download the file
+     */
+    bool downloadFile(const std::string& conversationId,
+                      const std::string& interactionId,
+                      const std::string& fileId,
+                      const std::string& path,
+                      size_t start = 0,
+                      size_t end = 0);
+
+    // Sync
+    /**
+     * Sync conversations with detected peer
+     */
+    void syncConversations(const std::string& peer, const std::string& deviceId);
+
+    /**
+     * Detect new conversations and request from other devices
+     * @param msg       Received data
+     * @param peerId    Sender
+     * @param deviceId
+     */
+    void onSyncData(const SyncMsg& msg, const std::string& peerId, const std::string& deviceId);
+
+    /**
+     * Check if we need to share infos with a contact
+     * @param memberUri
+     * @param deviceId
+     */
+    bool needsSyncingWith(const std::string& memberUri, const std::string& deviceId) const;
+
+    /**
+     * Notify that a peer fetched a commit
+     * @note: this definitely remove the repository when needed (when we left and someone fetched the information)
+     * @param conversationId    Related conv
+     * @param deviceId          Device who synced
+     */
+    void setFetched(const std::string& conversationId, const std::string& deviceId);
+
+    /**
+     * Launch fetch on new commit
+     * @param peer              Who sent the notification
+     * @param deviceId          Who sent the notification
+     * @param conversationId    Related conversation
+     * @param commitId          Commit to retrieve
+     */
+    void onNewCommit(const std::string& peer,
+                        const std::string& deviceId,
+                        const std::string& conversationId,
+                        const std::string& commitId);
+
+    // Conversation's member
+    /**
+     * Adds a new member to a conversation (this will triggers a member event + new message on success)
+     * @param conversationId
+     * @param contactUri
+     * @param sendRequest   If we need to inform the peer (used for tests)
+     */
+    void addConversationMember(const std::string& conversationId,
+                               const std::string& contactUri,
+                               bool sendRequest = true);
+    /**
+     * Remove a member from a conversation (this will trigger a member event + new message on success)
+     * @param conversationId
+     * @param contactUri
+     * @param isDevice
+     */
+    void removeConversationMember(const std::string& conversationId,
+                                  const std::string& contactUri,
+                                  bool isDevice = false);
+    /**
+     * Get members
+     * @param conversationId
+     * @return a map of members with their role and details
+     */
+    std::vector<std::map<std::string, std::string>> getConversationMembers(
+        const std::string& conversationId) const;
+    /**
+     * Retrieve the number of interactions from interactionId to HEAD
+     * @param convId
+     * @param interactionId     "" for getting the whole history
+     * @param authorUri         Stop when detect author
+     * @return number of interactions since interactionId
+     */
+    uint32_t countInteractions(const std::string& convId,
+                               const std::string& toId,
+                               const std::string& fromId,
+                               const std::string& authorUri) const;
+
+    // Conversation's infos management
+    /**
+     * Update metadata from conversations (like title, avatar, etc)
+     * @param conversationId
+     * @param infos
+     * @param sync              If we need to sync with others (used for tests)
+     */
+    void updateConversationInfos(const std::string& conversationId,
+                                 const std::map<std::string, std::string>& infos,
+                                 bool sync = true);
+    std::map<std::string, std::string> conversationInfos(const std::string& conversationId) const;
+    // Get the map into a VCard format for storing
+    std::vector<uint8_t> conversationVCard(const std::string& conversationId) const;
+
+    /**
+     * Return if a device is banned from a conversation
+     * @param convId
+     * @param deviceId
+     */
+    bool isBannedDevice(const std::string& convId, const std::string& deviceId) const;
+
+    // Remove swarm
+    /**
+     * Remove one to one conversations related to a contact
+     * @param uri       Of the contact
+     * @param ban       If banned
+     */
+    void removeContact(const std::string& uri, bool ban);
+
+    /**
+     * Remove a conversation, but not the contact
+     * @param conversationId
+     * @return if successfully removed
+     */
+    bool removeConversation(const std::string& conversationId);
+
+    /**
+     * When a DHT message is coming, during swarm transition
+     * check if a swarm is linked to that contact and remove
+     * the swarm if needed
+     * @param peerUri   the one who sent a DHT message
+     */
+    void checkIfRemoveForCompat(const std::string& peerUri);
+
+    // The following methods modify what is stored on the disk
+    static void saveConvInfos(const std::string& accountId,
+                              const std::map<std::string, ConvInfo>& conversations);
+    static void saveConvInfosToPath(const std::string& path,
+                                    const std::map<std::string, ConvInfo>& conversations);
+    static void saveConvRequests(
+        const std::string& accountId,
+        const std::map<std::string, ConversationRequest>& conversationsRequests);
+    static void saveConvRequestsToPath(
+        const std::string& path,
+        const std::map<std::string, ConversationRequest>& conversationsRequests);
+
+    static std::map<std::string, ConvInfo> convInfos(const std::string& accountId);
+    static std::map<std::string, ConvInfo> convInfosFromPath(const std::string& path);
+    static std::map<std::string, ConversationRequest> convRequests(const std::string& accountId);
+    static std::map<std::string, ConversationRequest> convRequestsFromPath(const std::string& path);
+    void addConvInfo(const ConvInfo& info);
+    void setConversationMembers(const std::string& convId, const std::vector<std::string>& members);
+
+private:
+    class Impl;
+    std::shared_ptr<Impl> pimpl_;
+};
+
+} // namespace jami
\ No newline at end of file
diff --git a/src/jamidht/jamiaccount.cpp b/src/jamidht/jamiaccount.cpp
index 21388952392bd4c0b55f28a15f763028533b87ae..1dee510574746b4607df6542d932dd203e5a312d 100644
--- a/src/jamidht/jamiaccount.cpp
+++ b/src/jamidht/jamiaccount.cpp
@@ -38,6 +38,7 @@
 #include "server_account_manager.h"
 #include "jamidht/channeled_transport.h"
 #include "multiplexed_socket.h"
+#include "conversation_channel_handler.h"
 
 #include "sip/sdp.h"
 #include "sip/sipvoiplink.h"
@@ -106,17 +107,19 @@
 
 using namespace std::placeholders;
 
-struct SyncMsg
-{
-    jami::DeviceSync ds;
-    std::map<std::string, jami::ConvInfo> c;
-    std::map<std::string, jami::ConversationRequest> cr;
-    MSGPACK_DEFINE(ds, c, cr)
-};
-
 namespace jami {
 
 constexpr pj_str_t STR_MESSAGE_ID = jami::sip_utils::CONST_PJ_STR("Message-ID");
+static constexpr const char MIME_TYPE_IMDN[] {"message/imdn+xml"};
+static constexpr const char MIME_TYPE_IM_COMPOSING[] {"application/im-iscomposing+xml"};
+static constexpr const char MIME_TYPE_INVITE[] {"application/invite"};
+static constexpr const char MIME_TYPE_INVITE_JSON[] {"application/invite+json"};
+static constexpr const char MIME_TYPE_GIT[] {"application/im-gitmessage-id"};
+static constexpr const char FILE_URI[] {"file://"};
+static constexpr const char VCARD_URI[] {"vcard://"};
+static constexpr const char SYNC_URI[] {"sync://"};
+static constexpr const char DATA_TRANSFER_URI[] {"data-transfer://"};
+static constexpr std::chrono::steady_clock::duration COMPOSING_TIMEOUT {std::chrono::seconds(12)};
 
 struct PendingConfirmation
 {
@@ -204,13 +207,6 @@ struct JamiAccount::PendingCall
     std::shared_ptr<dht::crypto::Certificate> from_cert;
 };
 
-struct JamiAccount::PendingConversationFetch
-{
-    bool ready {false};
-    bool cloning {false};
-    std::string deviceId {};
-};
-
 struct JamiAccount::PendingMessage
 {
     std::set<DeviceId> to;
@@ -246,7 +242,6 @@ constexpr const char* const JamiAccount::ACCOUNT_TYPE;
 constexpr const std::pair<uint16_t, uint16_t> JamiAccount::DHT_PORT_RANGE {4000, 8888};
 
 using ValueIdDist = std::uniform_int_distribution<dht::Value::Id>;
-using LoadIdDist = std::uniform_int_distribution<uint32_t>;
 
 static std::string_view
 stripPrefix(std::string_view toUrl)
@@ -1131,45 +1126,17 @@ JamiAccount::loadAccount(const std::string& archive_password,
                const std::string& conversationId,
                const std::vector<uint8_t>& payload,
                time_t received) {
-            auto saveReq = false;
-            if (!conversationId.empty()) {
-                auto info = accountManager_->getInfo();
-                if (!info)
-                    return;
-                auto itConv = info->conversations.find(conversationId);
-                if (itConv != info->conversations.end()) {
-                    JAMI_INFO("[Account %s] Received a request for a conversation "
-                              "already handled. Ignore",
-                              getAccountID().c_str());
-                    return;
-                }
-                saveReq = true;
-                if (accountManager_->getRequest(conversationId) != std::nullopt) {
-                    JAMI_INFO("[Account %s] Received a request for a conversation "
-                              "already existing. Ignore",
-                              getAccountID().c_str());
-                    return;
-                }
-            }
-            emitSignal<DRing::ConfigurationSignal::IncomingTrustRequest>(getAccountID(),
-                                                                         conversationId,
-                                                                         uri,
-                                                                         payload,
-                                                                         received);
-            if (saveReq && !conversationId.empty()) {
-                ConversationRequest req;
-                req.from = uri;
-                req.conversationId = conversationId;
-                req.received = std::time(nullptr);
-                auto details = vCard::utils::toMap(
-                    std::string_view(reinterpret_cast<const char*>(payload.data()), payload.size()));
-                req.metadatas = ConversationRepository::infosFromVCard(details);
-                auto reqMap = req.toMap();
-                accountManager_->addConversationRequest(conversationId, std::move(req));
-                emitSignal<DRing::ConversationSignal::ConversationRequestReceived>(getAccountID(),
-                                                                                   conversationId,
-                                                                                   reqMap);
+            if (conversationId.empty()) {
+                // Old path
+                emitSignal<DRing::ConfigurationSignal::IncomingTrustRequest>(getAccountID(),
+                                                                             conversationId,
+                                                                             uri,
+                                                                             payload,
+                                                                             received);
+                return;
             }
+            if (auto cm = convModule()) // Here account can be initializing
+                cm->onTrustRequest(uri, conversationId, payload, received);
         },
         [this](const std::map<DeviceId, KnownDevice>& devices) {
             std::map<std::string, std::string> ids;
@@ -1184,9 +1151,8 @@ JamiAccount::loadAccount(const std::string& archive_password,
         },
         [this](const std::string& conversationId) {
             dht::ThreadPool::computation().run([w = weak(), conversationId] {
-                if (auto acc = w.lock()) {
-                    acc->acceptConversationRequest(conversationId);
-                }
+                if (auto acc = w.lock())
+                    acc->convModule()->acceptConversationRequest(conversationId);
             });
         },
         [this](const std::string& uri, const std::string& convFromReq) {
@@ -1196,7 +1162,7 @@ JamiAccount::loadAccount(const std::string& archive_password,
             // In this case, delete current conversation linked with that
             // contact because he will not get messages anyway.
             if (convFromReq.empty())
-                checkIfRemoveForCompat(uri);
+                convModule()->checkIfRemoveForCompat(uri);
         }};
 
     try {
@@ -1231,7 +1197,7 @@ JamiAccount::loadAccount(const std::string& archive_password,
             if (not isEnabled()) {
                 setRegistrationState(RegistrationState::UNREGISTERED);
             }
-            loadConversations();
+            convModule()->loadConversations();
         } else if (isEnabled()) {
             if (not managerUri_.empty() and archive_password.empty()) {
                 Migration::setState(accountID_, Migration::State::INVALID);
@@ -1333,7 +1299,7 @@ JamiAccount::loadAccount(const std::string& archive_password,
                                                                                        info.photo);
                     setRegistrationState(RegistrationState::UNREGISTERED);
                     saveConfig();
-                    loadConversations();
+                    convModule()->loadConversations();
                     doRegister();
                 },
                 [w = weak(),
@@ -1860,67 +1826,51 @@ JamiAccount::trackPresence(const dht::InfoHash& h, BuddyInfo& buddy)
     if (not dht or not dht->isRunning()) {
         return;
     }
-    buddy.listenToken
-        = dht->listen<DeviceAnnouncement>(h, [this, h](DeviceAnnouncement&& dev, bool expired) {
-              bool wasConnected, isConnected;
-              {
-                  std::lock_guard<std::mutex> lock(buddyInfoMtx);
-                  auto buddy = trackedBuddies_.find(h);
-                  if (buddy == trackedBuddies_.end())
-                      return true;
-                  wasConnected = buddy->second.devices_cnt > 0;
-                  if (expired)
-                      --buddy->second.devices_cnt;
-                  else
-                      ++buddy->second.devices_cnt;
-                  isConnected = buddy->second.devices_cnt > 0;
-              }
-              // NOTE: the rest can use configurationMtx_, that can be locked during unregister so
-              // do not retrigger on dht
-              runOnMainThread([w = weak(), h, dev, expired, isConnected, wasConnected]() {
-                  auto sthis = w.lock();
-                  if (!sthis)
-                      return;
-                  if (not expired) {
-                      // Retry messages every time a new device announce its presence
-                      sthis->messageEngine_.onPeerOnline(h.toString());
-                      auto deviceId = dev.dev.toString();
-                      auto needsSync = false;
-                      {
-                          std::unique_lock<std::mutex> lk(sthis->conversationsMtx_);
-                          for (const auto& [_, conv] : sthis->conversations_) {
-                              if (conv->isMember(h.toString(), false)
-                                  && conv->needsFetch(deviceId)) {
-                                  needsSync = true;
-                                  break;
-                              }
-                          }
-                      }
-                      if (needsSync) {
-                          if (dev.pk) {
-                              sthis->requestSIPConnection(h.toString(), dev.pk->getLongId());
-                          } else {
-                              sthis->findCertificate(
-                                  dev.dev,
-                                  [sthis, h](const std::shared_ptr<dht::crypto::Certificate>& cert) {
-                                      if (cert) {
-                                          auto pk = std::make_shared<dht::crypto::PublicKey>(
-                                              cert->getPublicKey());
-                                          sthis->requestSIPConnection(h.toString(), pk->getLongId());
-                                      }
-                                  });
-                          }
-                      }
-                  }
-                  if (isConnected and not wasConnected) {
-                      sthis->onTrackedBuddyOnline(h);
-                  } else if (not isConnected and wasConnected) {
-                      sthis->onTrackedBuddyOffline(h);
-                  }
-              });
-
-              return true;
-          });
+    buddy.listenToken = dht->listen<
+        DeviceAnnouncement>(h, [this, h](DeviceAnnouncement&& dev, bool expired) {
+        bool wasConnected, isConnected;
+        {
+            std::lock_guard<std::mutex> lock(buddyInfoMtx);
+            auto buddy = trackedBuddies_.find(h);
+            if (buddy == trackedBuddies_.end())
+                return true;
+            wasConnected = buddy->second.devices_cnt > 0;
+            if (expired)
+                --buddy->second.devices_cnt;
+            else
+                ++buddy->second.devices_cnt;
+            isConnected = buddy->second.devices_cnt > 0;
+        }
+        // NOTE: the rest can use configurationMtx_, that can be locked during unregister so
+        // do not retrigger on dht
+        runOnMainThread([w = weak(), h, dev, expired, isConnected, wasConnected]() {
+            auto sthis = w.lock();
+            if (!sthis)
+                return;
+            if (not expired) {
+                // Retry messages every time a new device announce its presence
+                sthis->messageEngine_.onPeerOnline(h.toString());
+                sthis->findCertificate(
+                    dev.dev, [sthis, h](const std::shared_ptr<dht::crypto::Certificate>& cert) {
+                        if (cert) {
+                            auto pk = std::make_shared<dht::crypto::PublicKey>(cert->getPublicKey());
+                            if (sthis->convModule()->needsSyncingWith(h.toString(),
+                                                                      pk->getLongId().toString()))
+                                sthis->requestSIPConnection(
+                                    h.toString(),
+                                    pk->getLongId()); // Both sides will sync conversations
+                        }
+                    });
+            }
+            if (isConnected and not wasConnected) {
+                sthis->onTrackedBuddyOnline(h);
+            } else if (not isConnected and wasConnected) {
+                sthis->onTrackedBuddyOffline(h);
+            }
+        });
+
+        return true;
+    });
     JAMI_DBG("[Account %s] tracking buddy %s", getAccountID().c_str(), h.to_c_str());
 }
 
@@ -1944,14 +1894,14 @@ JamiAccount::onTrackedBuddyOnline(const dht::InfoHash& contactId)
     auto details = getContactDetails(id);
     auto it = details.find("confirmed");
     if (it == details.end() or it->second == "false") {
-        auto convId = getOneToOneConversation(id);
+        auto convId = convModule()->getOneToOneConversation(id);
         if (convId.empty())
             return;
         // In this case, the TrustRequest was sent but never confirmed (cause the contact was
         // offline maybe) To avoid the contact to never receive the conv request, retry there
         std::lock_guard<std::mutex> lock(configurationMutex_);
         if (accountManager_)
-            accountManager_->sendTrustRequest(id, convId, conversationVCard(convId));
+            accountManager_->sendTrustRequest(id, convId, convModule()->conversationVCard(convId));
     }
 }
 
@@ -1965,34 +1915,6 @@ JamiAccount::onTrackedBuddyOffline(const dht::InfoHash& contactId)
                                                             "");
 }
 
-void
-JamiAccount::syncConversations(const std::string& peer, const DeviceId& deviceId)
-{
-    // Sync conversations where peer is member
-    std::set<std::string> toFetch;
-    std::set<std::string> toClone;
-    {
-        if (auto infos = accountManager_->getInfo()) {
-            for (const auto& [key, ci] : infos->conversations) {
-                auto it = conversations_.find(key);
-                if (it != conversations_.end() && it->second) {
-                    if (!it->second->isRemoving() && it->second->isMember(peer, false))
-                        toFetch.emplace(key);
-                } else if (!ci.removed
-                           && std::find(ci.members.begin(), ci.members.end(), peer)
-                                  != ci.members.end()) {
-                    // In this case the conversation was never cloned (can be after an import)
-                    toClone.emplace(key);
-                }
-            }
-        }
-    }
-    for (const auto& cid : toFetch)
-        fetchNewCommits(peer, deviceId, cid);
-    for (const auto& cid : toClone)
-        cloneConversation(deviceId, peer, cid);
-}
-
 void
 JamiAccount::doRegister_()
 {
@@ -2158,8 +2080,7 @@ JamiAccount::doRegister_()
                         return;
 
                     std::unique_lock<std::mutex> lk(connManagerMtx_);
-                    if (!connectionManager_)
-                        connectionManager_ = std::make_unique<ConnectionManager>(*this);
+                    initConnectionManager();
                     auto channelName = "sync://" + deviceId;
                     if (connectionManager_->isConnecting(crt->getLongId(), channelName)) {
                         JAMI_INFO("[Account %s] Already connecting to %s",
@@ -2195,8 +2116,7 @@ JamiAccount::doRegister_()
         accountManager_->setDht(dht_);
 
         std::unique_lock<std::mutex> lkCM(connManagerMtx_);
-        if (!connectionManager_)
-            connectionManager_ = std::make_unique<ConnectionManager>(*this);
+        initConnectionManager();
         connectionManager_->onDhtConnected(*accountManager_->getInfo()->devicePk);
         connectionManager_->onICERequest([this](const DeviceId& deviceId) {
             std::promise<bool> accept;
@@ -2221,61 +2141,28 @@ JamiAccount::doRegister_()
         });
         connectionManager_->onChannelRequest([this](const DeviceId& deviceId,
                                                     const std::string& name) {
-            auto isFile = name.substr(0, 7) == "file://";
-            auto isVCard = name.substr(0, 8) == "vcard://";
-            auto isDataTransfer = name.substr(0, 16) == "data-transfer://";
-
             JAMI_WARN("[Account %s] New channel asked from %s with name %s",
                       getAccountID().c_str(),
                       deviceId.to_c_str(),
                       name.c_str());
 
-            if (name.find("git://") == 0) {
-                // Pre-check before acceptance. Sometimes, another device can start a conversation
-                // which is still not synced. So, here we decline channel's request in this case
-                // to avoid the other device to want to sync with us till we're not ready.
-                auto sep = name.find_last_of('/');
-                auto conversationId = name.substr(sep + 1);
-                auto remoteDevice = name.substr(6, sep - 6);
-
-                std::unique_lock<std::mutex> lk(conversationsMtx_);
-                auto conversation = conversations_.find(conversationId);
-                if (conversation == conversations_.end()) {
-                    JAMI_WARN("[Account %s] Git server requested, but for a non existing "
-                              "conversation (%s)",
-                              getAccountID().c_str(),
-                              conversationId.c_str());
-                    return false;
-                }
-                // Also check if peer is banned.
-                if (conversation->second->isBanned(remoteDevice)) {
-                    JAMI_WARN("[Account %s] %s is a banned device in conversation %s",
-                              getAccountID().c_str(),
-                              remoteDevice.c_str(),
-                              conversationId.c_str());
-                    return false;
-                }
-                return true;
-            } else if (name == "sip") {
+            auto uri = Uri(name);
+            auto itHandler = channelHandlers_.find(uri.scheme());
+            if (itHandler != channelHandlers_.end() && itHandler->second)
+                return itHandler->second->onRequest(deviceId, name);
+            // TODO replace
+            auto isFile = name.substr(0, 7) == FILE_URI;
+            auto isVCard = name.substr(0, 8) == VCARD_URI;
+            auto isDataTransfer = name.substr(0, 16) == DATA_TRANSFER_URI;
+
+            auto cert = tls::CertificateStore::instance().getCertificate(deviceId.toString());
+            if (!cert)
+                return false;
+
+            if (name == "sip") {
                 return true;
-            } else if (name.find("sync://") == 0) {
-                // Check if sync request is from same account
-                std::promise<bool> accept;
-                std::future<bool> fut = accept.get_future();
-                accountManager_
-                    ->findCertificate(deviceId,
-                                      [this, &accept](
-                                          const std::shared_ptr<dht::crypto::Certificate>& cert) {
-                                          if (not cert) {
-                                              accept.set_value(false);
-                                              return;
-                                          }
-                                          accept.set_value(cert->getIssuerUID()
-                                                           == accountManager_->getInfo()->accountId);
-                                      });
-                fut.wait();
-                auto result = fut.get();
-                return result;
+            } else if (name.find(SYNC_URI) == 0) {
+                return cert->getIssuerUID() == accountManager_->getInfo()->accountId;
             } else if (isFile or isVCard) {
                 auto tid = isFile ? name.substr(7) : name.substr(8);
                 std::lock_guard<std::mutex> lk(transfersMtx_);
@@ -2300,30 +2187,19 @@ JamiAccount::doRegister_()
                     fileId = fileId.substr(0, sep);
                 }
 
-                accountManager_->findCertificate(
-                    deviceId, [&](const std::shared_ptr<dht::crypto::Certificate>& cert) {
-                        if (not cert) {
-                            accept.set_value(false);
-                            return;
-                        }
-                        // Check if peer is member of the conversation
-                        std::unique_lock<std::mutex> lk(conversationsMtx_);
-                        auto conversation = conversations_.find(conversationId);
-                        if (conversation == conversations_.end() or not conversation->second) {
-                            accept.set_value(false);
-                            return;
-                        }
-                        if (fileId == "profile.vcf") {
-                            accept.set_value(conversation->second->isMember(cert->getIssuerUID()));
-                        } else {
-                            accept.set_value(
-                                conversation->second->onFileChannelRequest(cert->getIssuerUID(),
-                                                                           fileId,
-                                                                           !noSha3sumVerification_));
-                        }
-                    });
-                fut.wait();
-                return fut.get();
+                // Check if peer is member of the conversation
+                if (fileId == "profile.vcf") {
+                    auto members = convModule()->getConversationMembers(conversationId);
+                    return std::find_if(members.begin(),
+                                        members.end(),
+                                        [&](auto m) { return m["uri"] == cert->getIssuerUID(); })
+                           != members.end();
+                }
+
+                return convModule()->onFileChannelRequest(conversationId,
+                                                          cert->getIssuerUID(),
+                                                          fileId,
+                                                          !noSha3sumVerification_);
             }
             return false;
         });
@@ -2335,11 +2211,11 @@ JamiAccount::doRegister_()
                 if (!cert || !cert->issuer)
                     return;
                 auto peerId = cert->issuer->getId().toString();
-                auto isFile = name.substr(0, 7) == "file://";
-                auto isVCard = name.substr(0, 8) == "vcard://";
+                auto isFile = name.substr(0, 7) == FILE_URI;
+                auto isVCard = name.substr(0, 8) == VCARD_URI;
                 if (name == "sip") {
                     cacheSIPConnection(std::move(channel), peerId, deviceId);
-                } else if (name.find("sync://") == 0) {
+                } else if (name.find(SYNC_URI) == 0) {
                     cacheSyncConnection(std::move(channel), peerId, deviceId);
                 } else if (isFile or isVCard) {
                     auto tid = isFile ? name.substr(7) : name.substr(8);
@@ -2388,11 +2264,13 @@ JamiAccount::doRegister_()
                         return;
                     }
 
-                    if (!isConversation(conversationId)) {
-                        JAMI_WARN("[Account %s] Git server requested, but for a non existing "
-                                  "conversation (%s)",
+                    // Check if pull from banned device
+                    if (convModule()->isBannedDevice(conversationId, remoteDevice)) {
+                        JAMI_WARN("[Account %s] Git server requested for conversation %s, but the device is "
+                                  "unauthorized (%s) ",
                                   getAccountID().c_str(),
-                                  conversationId.c_str());
+                                  conversationId.c_str(),
+                                  remoteDevice.c_str());
                         channel->shutdown();
                         return;
                     }
@@ -2412,20 +2290,8 @@ JamiAccount::doRegister_()
                               channel->channel());
                     auto gs = std::make_unique<GitServer>(accountId, conversationId, channel);
                     gs->setOnFetched([w = weak(), conversationId, deviceId](const std::string&) {
-                        auto shared = w.lock();
-                        if (!shared)
-                            return;
-                        auto remove = false;
-                        {
-                            std::unique_lock<std::mutex> lk(shared->conversationsMtx_);
-                            auto it = shared->conversations_.find(conversationId);
-                            if (it != shared->conversations_.end() && it->second) {
-                                remove = it->second->isRemoving();
-                                it->second->hasFetched(deviceId.toString());
-                            }
-                        }
-                        if (remove)
-                            shared->removeRepository(conversationId, true);
+                        if (auto shared = w.lock())
+                            shared->convModule()->setFetched(conversationId, deviceId.toString());
                     });
                     const dht::Value::Id serverId = ValueIdDist()(rand);
                     {
@@ -2442,7 +2308,7 @@ JamiAccount::doRegister_()
                             shared->gitServers_.erase(serverId);
                         });
                     });
-                } else if (name.find("data-transfer://") == 0) {
+                } else if (name.find(DATA_TRANSFER_URI) == 0) {
                     auto idstr = name.substr(16);
                     auto sep = idstr.find('/');
                     auto lastSep = idstr.find_last_of('/');
@@ -2548,6 +2414,69 @@ JamiAccount::doRegister_()
     }
 }
 
+ConversationModule*
+JamiAccount::convModule()
+{
+    if (!accountManager() || currentDeviceId() == "") {
+        JAMI_ERR() << "Calling convModule() with an uninitialized account.";
+        return nullptr;
+    }
+    if (!convModule_) {
+        convModule_ = std::make_unique<ConversationModule>(
+            weak(),
+            std::move([this] {
+                runOnMainThread([w = weak()] {
+                    if (auto shared = w.lock())
+                        shared->syncWithConnected();
+                });
+            }),
+            std::move([this](auto&& uri, auto&& msg) {
+                runOnMainThread([w = weak(), uri, msg] {
+                    if (auto shared = w.lock())
+                        shared->sendTextMessage(uri, msg);
+                });
+            }),
+            std::move([this](const auto& convId, const auto& deviceId, auto&& cb) {
+                runOnMainThread([w = weak(), convId, deviceId, cb = std::move(cb)] {
+                    auto shared = w.lock();
+                    if (!shared)
+                        return;
+                    auto gs = shared->gitSocket(DeviceId(deviceId), convId);
+                    if (gs != std::nullopt) {
+                        if (auto socket = gs->lock()) {
+                            if (!cb(socket))
+                                socket->shutdown();
+                            else
+                                cb({});
+                            return;
+                        }
+                    }
+                    std::lock_guard<std::mutex> lkCM(shared->connManagerMtx_);
+                    if (!shared->connectionManager_) {
+                        cb({});
+                        return;
+                    }
+                    shared->connectionManager_
+                        ->connectDevice(DeviceId(deviceId),
+                                        "git://" + deviceId + "/" + convId,
+                                        [shared, cb, convId](std::shared_ptr<ChannelSocket> socket,
+                                                        const DeviceId&) {
+                                            if (socket) {
+                                                socket->onShutdown(
+                                                    [shared, deviceId = socket->deviceId(), convId] {
+                                                        shared->removeGitSocket(deviceId, convId);
+                                                    });
+                                                if (!cb(socket))
+                                                    socket->shutdown();
+                                            } else
+                                                cb({});
+                                        });
+                });
+            }));
+    }
+    return convModule_.get();
+}
+
 void
 JamiAccount::onTextMessage(const std::string& id,
                            const std::string& from,
@@ -2999,6 +2928,97 @@ JamiAccount::getToUri(const std::string& to) const
     return fmt::format("<sips:{};transport=tls>", to);
 }
 
+std::string
+getIsComposing(const std::string& conversationId, bool isWriting)
+{
+    // implementing https://tools.ietf.org/rfc/rfc3994.txt
+    return fmt::format("<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n"
+                       "<isComposing><state>{}</state>{}</isComposing>",
+                       isWriting ? "active"sv : "idle"sv,
+                       conversationId.empty()
+                           ? ""
+                           : "<conversation>" + conversationId + "</conversation>");
+}
+
+std::string
+getDisplayed(const std::string& conversationId, const std::string& messageId)
+{
+    // implementing https://tools.ietf.org/rfc/rfc5438.txt
+    return fmt::format(
+        "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n"
+        "<imdn><message-id>{}</message-id>\n"
+        "{}"
+        "<display-notification><status><displayed/></status></display-notification>\n"
+        "</imdn>",
+        messageId,
+        conversationId.empty() ? "" : "<conversation>" + conversationId + "</conversation>");
+}
+
+void
+JamiAccount::setIsComposing(const std::string& conversationUri, bool isWriting)
+{
+    Uri uri(conversationUri);
+    std::string conversationId = {};
+    if (uri.scheme() == Uri::Scheme::SWARM)
+        conversationId = uri.authority();
+    const auto& uid = uri.authority();
+
+    if (not isWriting and conversationUri != composingUri_)
+        return;
+
+    if (composingTimeout_) {
+        composingTimeout_->cancel();
+        composingTimeout_.reset();
+    }
+    if (isWriting) {
+        if (not composingUri_.empty() and composingUri_ != conversationUri) {
+            sendInstantMessage(uid,
+                               {{MIME_TYPE_IM_COMPOSING, getIsComposing(conversationId, false)}});
+            composingTime_ = std::chrono::steady_clock::time_point::min();
+        }
+        composingUri_.clear();
+        composingUri_.insert(composingUri_.end(), conversationUri.begin(), conversationUri.end());
+        auto now = std::chrono::steady_clock::now();
+        if (now >= composingTime_ + COMPOSING_TIMEOUT) {
+            sendInstantMessage(uid,
+                               {{MIME_TYPE_IM_COMPOSING, getIsComposing(conversationId, true)}});
+            composingTime_ = now;
+        }
+        composingTimeout_ = Manager::instance().scheduleTask(
+            [w = weak(), uid, conversationId]() {
+                if (auto sthis = w.lock()) {
+                    sthis->sendInstantMessage(uid,
+                                              {{MIME_TYPE_IM_COMPOSING,
+                                                getIsComposing(conversationId, false)}});
+                    sthis->composingUri_.clear();
+                    sthis->composingTime_ = std::chrono::steady_clock::time_point::min();
+                }
+            },
+            now + COMPOSING_TIMEOUT);
+    } else {
+        sendInstantMessage(uid, {{MIME_TYPE_IM_COMPOSING, getIsComposing(conversationId, false)}});
+        composingUri_.clear();
+        composingTime_ = std::chrono::steady_clock::time_point::min();
+    }
+}
+
+bool
+JamiAccount::setMessageDisplayed(const std::string& conversationUri,
+                                 const std::string& messageId,
+                                 int status)
+{
+    Uri uri(conversationUri);
+    std::string conversationId = {};
+    if (uri.scheme() == Uri::Scheme::SWARM)
+        conversationId = uri.authority();
+    if (!conversationId.empty())
+        convModule()->onMessageDisplayed(getUsername(), conversationId, messageId);
+    if (status == (int) DRing::Account::MessageStates::DISPLAYED && isReadReceiptEnabled())
+        sendInstantMessage(uri.authority(),
+                           {{MIME_TYPE_IMDN, getDisplayed(conversationId, messageId)}});
+    return true;
+}
+
 pj_str_t
 JamiAccount::getContactHeader(pjsip_transport* t)
 {
@@ -3032,9 +3052,9 @@ JamiAccount::getContactHeader(pjsip_transport* t)
 void
 JamiAccount::addContact(const std::string& uri, bool confirmed)
 {
-    auto conversation = getOneToOneConversation(uri);
+    auto conversation = convModule()->getOneToOneConversation(uri);
     if (!confirmed && conversation.empty())
-        conversation = startConversation(ConversationMode::ONE_TO_ONE, uri);
+        conversation = convModule()->startConversation(ConversationMode::ONE_TO_ONE, uri);
     std::lock_guard<std::mutex> lock(configurationMutex_);
     if (accountManager_)
         accountManager_->addContact(uri, confirmed, conversation);
@@ -3046,81 +3066,25 @@ void
 JamiAccount::removeContact(const std::string& uri, bool ban)
 {
     std::unique_lock<std::mutex> lock(configurationMutex_);
-    if (!accountManager_) {
+    if (accountManager_) {
+        accountManager_->removeContact(uri, ban);
+    } else {
         JAMI_WARN("[Account %s] removeContact: account not loaded", getAccountID().c_str());
         return;
     }
-    accountManager_->removeContact(uri, ban);
-    auto convInfos = accountManager_->getInfo()->conversations;
-
-    // Remove related conversation
-    auto isSelf = uri == getUsername();
-    bool updateConvInfos = false;
-    std::vector<std::string> toRm;
-    {
-        std::lock_guard<std::mutex> lk(conversationsMtx_);
-        for (auto& [convId, conv] : convInfos) {
-            auto itConv = conversations_.find(convId);
-            if (itConv != conversations_.end() && itConv->second) {
-                try {
-                    // Note it's important to check getUsername(), else
-                    // removing self can remove all conversations
-                    if (itConv->second->mode() == ConversationMode::ONE_TO_ONE) {
-                        auto initMembers = itConv->second->getInitialMembers();
-                        if ((isSelf && initMembers.size() == 1)
-                            || std::find(initMembers.begin(), initMembers.end(), uri)
-                                   != initMembers.end())
-                            toRm.emplace_back(convId);
-                    }
-                } catch (const std::exception& e) {
-                    JAMI_WARN("%s", e.what());
-                }
-            } else if (std::find(conv.members.begin(), conv.members.end(), uri)
-                       != conv.members.end()) {
-                // It's syncing with uri, mark as removed!
-                conv.removed = std::time(nullptr);
-                updateConvInfos = true;
-            }
-        }
-    }
-    if (updateConvInfos)
-        accountManager_->setConversations(convInfos);
     lock.unlock();
-    for (const auto& id : toRm) {
-        // Note, if we ban the device, we don't send the leave cause the other peer will just
-        // never got the notifications, so just erase the datas
-        if (!ban)
-            removeConversation(id);
-        else
-            removeRepository(id, false, true);
-    }
 
-    // Remove current connections with contact
-    dht::ThreadPool::io().run([w = weak(), uri] {
-        auto shared = w.lock();
-        if (!shared)
-            return;
-        // Remove current connections with contact
-        std::set<DeviceId> devices;
-        {
-            std::unique_lock<std::mutex> lk(shared->sipConnsMtx_);
-            for (auto it = shared->sipConns_.begin(); it != shared->sipConns_.end();) {
-                const auto& [key, value] = *it;
-                if (key.first == uri) {
-                    devices.emplace(key.second);
-                    it = shared->sipConns_.erase(it);
-                } else {
-                    ++it;
-                }
-            }
-        }
+    convModule()->removeContact(uri, ban);
 
-        std::lock_guard<std::mutex> lk(shared->connManagerMtx_);
-        if (!shared->connectionManager_)
-            return;
-        for (const auto& device : devices)
-            shared->connectionManager_->closeConnectionsWith(device);
-    });
+    // Remove current connections with contact
+    std::unique_lock<std::mutex> lk(sipConnsMtx_);
+    for (auto it = sipConns_.begin(); it != sipConns_.end();) {
+        const auto& [key, value] = *it;
+        if (key.first == uri)
+            it = sipConns_.erase(it);
+        else
+            ++it;
+    }
 }
 
 std::map<std::string, std::string>
@@ -3171,7 +3135,7 @@ JamiAccount::acceptTrustRequest(const std::string& from, bool includeConversatio
             info.created = std::time(nullptr);
             info.members.emplace_back(getUsername());
             info.members.emplace_back(from);
-            addNewConversation(info);
+            convModule()->addConvInfo(info);
         }
         return true;
     }
@@ -3186,7 +3150,8 @@ JamiAccount::discardTrustRequest(const std::string& from)
     auto requests = getTrustRequests();
     for (const auto& req : requests) {
         if (req.at(DRing::Account::TrustRequest::FROM) == from) {
-            declineConversationRequest(req.at(DRing::Account::TrustRequest::CONVERSATIONID));
+            convModule()->declineConversationRequest(
+                req.at(DRing::Account::TrustRequest::CONVERSATIONID));
         }
     }
 
@@ -3201,9 +3166,9 @@ JamiAccount::discardTrustRequest(const std::string& from)
 void
 JamiAccount::sendTrustRequest(const std::string& to, const std::vector<uint8_t>& payload)
 {
-    auto conversation = getOneToOneConversation(to);
+    auto conversation = convModule()->getOneToOneConversation(to);
     if (conversation.empty())
-        conversation = startConversation(ConversationMode::ONE_TO_ONE, to);
+        conversation = convModule()->startConversation(ConversationMode::ONE_TO_ONE, to);
     if (not conversation.empty()) {
         std::lock_guard<std::mutex> lock(configurationMutex_);
         if (accountManager_)
@@ -3483,7 +3448,10 @@ JamiAccount::onIsComposing(const std::string& conversationId,
 {
     try {
         const std::string fromUri {parseJamiUri(peer)};
-        Account::onIsComposing(conversationId, fromUri, isWriting);
+        emitSignal<DRing::ConfigurationSignal::ComposingStatusChanged>(accountID_,
+                                                                       conversationId,
+                                                                       peer,
+                                                                       isWriting ? 1 : 0);
     } catch (...) {
         JAMI_ERR("[Account %s] Can't parse URI: %s", getAccountID().c_str(), peer.c_str());
     }
@@ -3688,841 +3656,144 @@ JamiAccount::setActiveCodecs(const std::vector<unsigned>& list)
     }
 }
 
-std::string
-JamiAccount::startConversation(ConversationMode mode, const std::string& otherMember)
+// Member management
+void
+JamiAccount::saveMembers(const std::string& convId, const std::vector<std::string>& members)
 {
-    // Create the conversation object
-    std::shared_ptr<Conversation> conversation;
-    try {
-        conversation = std::make_shared<Conversation>(weak(), mode, otherMember);
-    } catch (const std::exception& e) {
-        JAMI_ERR("[Account %s] Error while generating a conversation %s",
-                 getAccountID().c_str(),
-                 e.what());
-        return {};
-    }
-    auto convId = conversation->id();
-    {
-        std::lock_guard<std::mutex> lk(conversationsMtx_);
-        conversations_[convId] = std::move(conversation);
-    }
-
-    // Update convInfo
-    ConvInfo info;
-    info.id = convId;
-    info.created = std::time(nullptr);
-    info.members.emplace_back(getUsername());
-    if (!otherMember.empty())
-        info.members.emplace_back(otherMember);
-    addNewConversation(info);
-
-    runOnMainThread([w = weak()]() {
-        // Invite connected devices for the same user
-        auto shared = w.lock();
-        if (!shared or !shared->accountManager_)
-            return;
-
-        // Send to connected devices
-        shared->syncWithConnected();
-    });
-
-    emitSignal<DRing::ConversationSignal::ConversationReady>(accountID_, convId);
-    return convId;
+    convModule()->setConversationMembers(convId, members);
 }
 
 void
-JamiAccount::acceptConversationRequest(const std::string& conversationId)
+JamiAccount::sendInstantMessage(const std::string& convId,
+                                const std::map<std::string, std::string>& msg)
 {
-    // For all conversation members, try to open a git channel with this conversation ID
-    auto request = accountManager_->getRequest(conversationId);
-    if (request == std::nullopt) {
-        JAMI_WARN("[Account %s] Request not found for conversation %s",
-                  getAccountID().c_str(),
-                  conversationId.c_str());
-        return;
-    }
-    if (!startFetch(conversationId))
-        return;
-    auto memberHash = dht::InfoHash(request->from);
-    if (!memberHash) {
-        JAMI_WARN("Invalid member detected: %s", request->from.c_str());
+    auto members = convModule()->getConversationMembers(convId);
+    if (members.empty()) {
+        // TODO remove, it's for old API for contacts
+        sendTextMessage(convId, msg);
         return;
     }
-    forEachDevice(memberHash,
-                  [this, request = *request](const std::shared_ptr<dht::crypto::PublicKey>& dev) {
-                      auto deviceId = dev->getLongId();
-                      if (deviceId == dht()->getPublicKey()->getLongId())
-                          return;
-                      connectionManager().connectDevice(
-                          deviceId,
-                          "git://" + deviceId.toString() + "/" + request.conversationId,
-                          [this, request](std::shared_ptr<ChannelSocket> socket,
-                                          const DeviceId& dev) {
-                              if (socket) {
-                                  std::unique_lock<std::mutex> lk(pendingConversationsFetchMtx_);
-                                  auto& pending = pendingConversationsFetch_[request.conversationId];
-                                  if (!pending.ready) {
-                                      pending.ready = true;
-                                      pending.deviceId = dev.toString();
-                                      lk.unlock();
-                                      // Save the git socket
-                                      addGitSocket(dev, request.conversationId, socket);
-                                  } else {
-                                      lk.unlock();
-                                      socket->shutdown();
-                                  }
-                              }
-                          });
-                  });
-    accountManager_->rmConversationRequest(conversationId);
-    ConvInfo info;
-    info.id = conversationId;
-    info.created = std::time(nullptr);
-    info.members.emplace_back(getUsername());
-    info.members.emplace_back(request->from);
-    runOnMainThread([w = weak(), info = std::move(info)] {
-        if (auto shared = w.lock())
-            shared->addNewConversation(info);
-    });
-    checkConversationsEvents();
-}
-
-void
-JamiAccount::checkConversationsEvents()
-{
-    bool hasHandler = conversationsEventHandler and not conversationsEventHandler->isCancelled();
-    std::lock_guard<std::mutex> lk(pendingConversationsFetchMtx_);
-    if (not pendingConversationsFetch_.empty() and not hasHandler) {
-        conversationsEventHandler = Manager::instance().scheduler().scheduleAtFixedRate(
-            [w = weak()] {
-                if (auto this_ = w.lock())
-                    return this_->handlePendingConversations();
-                return false;
-            },
-            std::chrono::milliseconds(10));
-    } else if (pendingConversationsFetch_.empty() and hasHandler) {
-        conversationsEventHandler->cancel();
-        conversationsEventHandler.reset();
+    for (const auto& m : members) {
+        auto uri = m.at("uri");
+        auto token = std::uniform_int_distribution<uint64_t> {1, JAMI_ID_MAX_VAL}(rand);
+        // Announce to all members that a new message is sent
+        sendTextMessage(uri, msg, token, false, true);
     }
 }
 
 bool
-JamiAccount::handlePendingConversations()
-{
-    std::lock_guard<std::mutex> lk(pendingConversationsFetchMtx_);
-    for (auto it = pendingConversationsFetch_.begin(); it != pendingConversationsFetch_.end();) {
-        if (it->second.ready && !it->second.cloning) {
-            it->second.cloning = true;
-            dht::ThreadPool::io().run([w = weak(),
-                                       conversationId = it->first,
-                                       deviceId = it->second.deviceId]() {
-                auto shared = w.lock();
-                auto info = shared->accountManager_->getInfo();
-                if (!shared or !info)
-                    return;
-                // Clone and store conversation
-                try {
-                    auto conversation = std::make_shared<Conversation>(w, deviceId, conversationId);
-                    auto erasePending = [&] {
-                        std::lock_guard<std::mutex> lk(shared->pendingConversationsFetchMtx_);
-                        shared->pendingConversationsFetch_.erase(conversationId);
-                    };
-
-                    if (!conversation->isMember(shared->getUsername(), true)) {
-                        JAMI_ERR("Conversation cloned but doesn't seems to be a valid member");
-                        conversation->erase();
-                        erasePending();
-                        return;
-                    }
-                    if (conversation) {
-                        auto removeRepo = false;
-                        {
-                            std::lock_guard<std::mutex> lk(shared->conversationsMtx_);
-                            // Note: a removeContact while cloning. In this case, the conversation
-                            // must not be announced and removed.
-                            auto& ci = info->conversations;
-                            auto itConv = ci.find(conversationId);
-                            if (itConv != ci.end() && itConv->second.removed)
-                                removeRepo = true;
-                            shared->conversations_.emplace(conversationId, conversation);
-                        }
-                        if (removeRepo) {
-                            shared->removeRepository(conversationId, false, true);
-                            erasePending();
-                            return;
-                        }
-                        auto commitId = conversation->join();
-                        if (!commitId.empty()) {
-                            runOnMainThread([w, conversationId, commitId]() {
-                                if (auto shared = w.lock()) {
-                                    std::lock_guard<std::mutex> lk(shared->conversationsMtx_);
-                                    auto it = shared->conversations_.find(conversationId);
-                                    // Do not sync as it's synched by convInfos
-                                    if (it != shared->conversations_.end())
-                                        shared->sendMessageNotification(*it->second,
-                                                                        commitId,
-                                                                        false);
-                                }
-                            });
-                        }
-                        erasePending();
-                        // Inform user that the conversation is ready
-                        emitSignal<DRing::ConversationSignal::ConversationReady>(shared->accountID_,
-                                                                                 conversationId);
-                        shared
-                            ->syncWithConnected(); // This informs other devices to clone the conversation
-                    }
-                } catch (const std::exception& e) {
-                    emitSignal<DRing::ConversationSignal::OnConversationError>(shared->accountID_,
-                                                                               conversationId,
-                                                                               EFETCH,
-                                                                               e.what());
-                    JAMI_WARN("Something went wrong when cloning conversation: %s", e.what());
-                }
-            });
+JamiAccount::handleMessage(const std::string& from, const std::pair<std::string, std::string>& m)
+{
+    if (m.first == MIME_TYPE_GIT) {
+        Json::Value json;
+        std::string err;
+        Json::CharReaderBuilder rbuilder;
+        auto reader = std::unique_ptr<Json::CharReader>(rbuilder.newCharReader());
+        if (!reader->parse(m.second.data(), m.second.data() + m.second.size(), &json, &err)) {
+            JAMI_ERR("Can't parse server response: %s", err.c_str());
+            return false;
         }
-        ++it;
-    }
-    return !pendingConversationsFetch_.empty();
-}
 
-void
-JamiAccount::declineConversationRequest(const std::string& conversationId)
-{
-    auto request = accountManager_->getRequest(conversationId);
-    if (request == std::nullopt)
-        return;
-    request->declined = std::time(nullptr);
-    accountManager_->addConversationRequest(conversationId, std::move(*request));
-    emitSignal<DRing::ConversationSignal::ConversationRequestDeclined>(getAccountID(),
-                                                                       conversationId);
-
-    syncWithConnected();
-}
+        JAMI_WARN("Received indication for new commit available in conversation %s",
+                  json["id"].asString().c_str());
 
-bool
-JamiAccount::removeConversation(const std::string& conversationId)
-{
-    std::unique_lock<std::mutex> lock(configurationMutex_);
-    if (!accountManager_)
-        return false;
-    std::unique_lock<std::mutex> lk(conversationsMtx_);
-    auto it = conversations_.find(conversationId);
-    if (it == conversations_.end()) {
-        JAMI_ERR("Conversation %s doesn't exist", conversationId.c_str());
-        return false;
-    }
-    auto members = it->second->getMembers();
-    auto hasMembers = !(members.size() == 1
-                        && username_.find(members[0]["uri"]) != std::string::npos);
-    // Update convInfos
-    auto infos = accountManager_->getInfo();
-    if (!infos)
-        return false;
-    auto ci = infos->conversations;
-    auto itConv = ci.find(conversationId);
-    if (itConv != ci.end()) {
-        itConv->second.removed = std::time(nullptr);
-        // Sync now, because it can take some time to really removes the datas
-        runOnMainThread([w = weak(), hasMembers]() {
-            // Invite connected devices for the same user
-            auto shared = w.lock();
-            // Send to connected devices
-            if (shared && hasMembers)
-                shared->syncWithConnected();
-        });
-    }
-    accountManager_->setConversations(ci);
-    lock.unlock();
-    auto commitId = it->second->leave();
-    emitSignal<DRing::ConversationSignal::ConversationRemoved>(accountID_, conversationId);
-    if (hasMembers) {
-        JAMI_DBG() << "Wait that someone sync that user left conversation " << conversationId;
-        // Commit that we left
-        if (!commitId.empty()) {
-            // Do not sync as it's synched by convInfos
-            sendMessageNotification(*it->second, commitId, false);
-        } else {
-            JAMI_ERR("Failed to send message to conversation %s", conversationId.c_str());
+        convModule()->onNewCommit(from,
+                                     json["deviceId"].asString(),
+                                     json["id"].asString(),
+                                     json["commit"].asString());
+        return true;
+    } else if (m.first == MIME_TYPE_INVITE) {
+        convModule()->onNeedConversationRequest(from, m.second);
+        return true;
+    } else if (m.first == MIME_TYPE_INVITE_JSON) {
+        Json::Value json;
+        std::string err;
+        Json::CharReaderBuilder rbuilder;
+        auto reader = std::unique_ptr<Json::CharReader>(rbuilder.newCharReader());
+        if (!reader->parse(m.second.data(), m.second.data() + m.second.size(), &json, &err)) {
+            JAMI_ERR("Can't parse server response: %s", err.c_str());
+            return false;
         }
-        // In this case, we wait that another peer sync the conversation
-        // to definitely remove it from the device. This is to inform the
-        // peer that we left the conversation and never want to receives
-        // any messages
+        convModule()->onConversationRequest(from, json);
         return true;
-    }
-    lk.unlock();
-    // Else we are the last member, so we can remove
-    removeRepository(conversationId, true);
-    return true;
-}
-
-std::vector<std::string>
-JamiAccount::getConversations()
-{
-    std::vector<std::string> result;
-    if (auto info = accountManager_->getInfo()) {
-        std::lock_guard<std::mutex> lk(conversationsMtx_);
-        result.reserve(info->conversations.size());
-        for (const auto& [key, conv] : info->conversations) {
-            if (conv.removed)
-                continue;
-            result.emplace_back(key);
+    } else if (m.first == MIME_TYPE_TEXT_PLAIN) {
+        // This means that a text is received, so that
+        // the conversation is not a swarm. For compatibility,
+        // check if we have a swarm created. It can be the case
+        // when the trust request confirm was not received.
+        convModule()->checkIfRemoveForCompat(from);
+    } else if (m.first == MIME_TYPE_IM_COMPOSING) {
+        try {
+            static const std::regex COMPOSING_REGEX("<state>\\s*(\\w+)\\s*<\\/state>");
+            std::smatch matched_pattern;
+            std::regex_search(m.second, matched_pattern, COMPOSING_REGEX);
+            bool isComposing {false};
+            if (matched_pattern.ready() && !matched_pattern.empty() && matched_pattern[1].matched) {
+                isComposing = matched_pattern[1] == "active";
+            }
+            static const std::regex CONVID_REGEX("<conversation>\\s*(\\w+)\\s*<\\/conversation>");
+            std::regex_search(m.second, matched_pattern, CONVID_REGEX);
+            std::string conversationId = "";
+            if (matched_pattern.ready() && !matched_pattern.empty() && matched_pattern[1].matched) {
+                conversationId = matched_pattern[1];
+            }
+            onIsComposing(conversationId, from, isComposing);
+            return true;
+        } catch (const std::exception& e) {
+            JAMI_WARN("Error parsing composing state: %s", e.what());
         }
-    }
-    return result;
-}
+    } else if (m.first == MIME_TYPE_IMDN) {
+        try {
+            static const std::regex IMDN_MSG_ID_REGEX("<message-id>\\s*(\\w+)\\s*<\\/message-id>");
+            static const std::regex IMDN_STATUS_REGEX("<status>\\s*<(\\w+)\\/>\\s*<\\/status>");
+            std::smatch matched_pattern;
+
+            std::regex_search(m.second, matched_pattern, IMDN_MSG_ID_REGEX);
+            std::string messageId;
+            if (matched_pattern.ready() && !matched_pattern.empty() && matched_pattern[1].matched) {
+                messageId = matched_pattern[1];
+            } else {
+                JAMI_WARN("Message displayed: can't parse message ID");
+                return false;
+            }
 
-std::vector<std::map<std::string, std::string>>
-JamiAccount::getConversationRequests()
-{
-    std::vector<std::map<std::string, std::string>> requests;
-    if (auto info = accountManager_->getInfo()) {
-        auto& convReq = info->conversationsRequests;
-        std::lock_guard<std::mutex> lk(accountManager_->conversationsRequestsMtx);
-        requests.reserve(convReq.size());
-        for (const auto& [id, request] : convReq) {
-            if (request.declined)
-                continue; // Do not add declined requests
-            requests.emplace_back(request.toMap());
+            std::regex_search(m.second, matched_pattern, IMDN_STATUS_REGEX);
+            bool isDisplayed {false};
+            if (matched_pattern.ready() && !matched_pattern.empty() && matched_pattern[1].matched) {
+                isDisplayed = matched_pattern[1] == "displayed";
+            } else {
+                JAMI_WARN("Message displayed: can't parse status");
+                return false;
+            }
+
+            static const std::regex CONVID_REGEX("<conversation>\\s*(\\w+)\\s*<\\/conversation>");
+            std::regex_search(m.second, matched_pattern, CONVID_REGEX);
+            std::string conversationId = "";
+            if (matched_pattern.ready() && !matched_pattern.empty() && matched_pattern[1].matched) {
+                conversationId = matched_pattern[1];
+            }
+
+            if (!isReadReceiptEnabled())
+                return true;
+            if (conversationId.empty()) // Old method
+                messageEngine_.onMessageDisplayed(from, from_hex_string(messageId), isDisplayed);
+            else if (isDisplayed) {
+                convModule()->onMessageDisplayed(from, conversationId, messageId);
+                JAMI_DBG() << "[message " << messageId << "] Displayed by peer";
+                emitSignal<DRing::ConfigurationSignal::AccountMessageStatusChanged>(
+                    accountID_,
+                    conversationId,
+                    from,
+                    messageId,
+                    static_cast<int>(DRing::Account::MessageStates::DISPLAYED));
+            }
+            return true;
+        } catch (const std::exception& e) {
+            JAMI_WARN("Error parsing display notification: %s", e.what());
         }
     }
-    return requests;
-}
-
-void
-JamiAccount::updateConversationInfos(const std::string& conversationId,
-                                     const std::map<std::string, std::string>& infos,
-                                     bool sync)
-{
-    std::lock_guard<std::mutex> lk(conversationsMtx_);
-    // Add a new member in the conversation
-    auto it = conversations_.find(conversationId);
-    if (it == conversations_.end()) {
-        JAMI_ERR("Conversation %s doesn't exist", conversationId.c_str());
-        return;
-    }
-
-    it->second
-        ->updateInfos(infos,
-                      [w = weak(), conversationId, sync](bool ok, const std::string& commitId) {
-                          if (ok && sync) {
-                              auto shared = w.lock();
-                              if (shared) {
-                                  std::lock_guard<std::mutex> lk(shared->conversationsMtx_);
-                                  auto it = shared->conversations_.find(conversationId);
-                                  if (it != shared->conversations_.end() && it->second) {
-                                      shared->sendMessageNotification(*it->second, commitId, true);
-                                  }
-                              }
-                          } else if (sync)
-                              JAMI_WARN("Couldn't update infos on %s", conversationId.c_str());
-                      });
-}
-
-std::map<std::string, std::string>
-JamiAccount::conversationInfos(const std::string& conversationId) const
-{
-    if (auto info = accountManager_->getInfo()) {
-        std::lock_guard<std::mutex> lk(conversationsMtx_);
-        // Add a new member in the conversation
-        auto it = conversations_.find(conversationId);
-        if (it == conversations_.end() or not it->second) {
-            auto itConv = info->conversations.find(conversationId);
-            if (itConv == info->conversations.end()) {
-                JAMI_ERR("Conversation %s doesn't exist", conversationId.c_str());
-                return {};
-            }
-            return {{"syncing", "true"}};
-        }
-
-        return it->second->infos();
-    }
-    return {};
-}
-
-std::vector<uint8_t>
-JamiAccount::conversationVCard(const std::string& conversationId) const
-{
-    std::lock_guard<std::mutex> lk(conversationsMtx_);
-    // Add a new member in the conversation
-    auto it = conversations_.find(conversationId);
-    if (it == conversations_.end()) {
-        JAMI_ERR("Conversation %s doesn't exist", conversationId.c_str());
-        return {};
-    }
-
-    return it->second->vCard();
-}
-
-// Member management
-void
-JamiAccount::saveMembers(const std::string& convId, const std::vector<std::string>& members)
-{
-    auto infos = accountManager_->getInfo();
-    if (!infos)
-        return;
-    accountManager_->setConversationMembers(convId, members);
-}
-
-void
-JamiAccount::addConversationMember(const std::string& conversationId,
-                                   const std::string& contactUri,
-                                   bool sendRequest)
-{
-    std::unique_lock<std::mutex> lk(conversationsMtx_);
-    // Add a new member in the conversation
-    auto it = conversations_.find(conversationId);
-    if (it == conversations_.end()) {
-        JAMI_ERR("Conversation %s doesn't exist", conversationId.c_str());
-        return;
-    }
-
-    if (it->second->isMember(contactUri, true)) {
-        JAMI_DBG("%s is already a member of %s, resend invite",
-                 contactUri.c_str(),
-                 conversationId.c_str());
-        // Note: This should not be necessary, but if for whatever reason the other side didn't join
-        // we should not forbid new invites
-        auto invite = it->second->generateInvitation();
-        lk.unlock();
-        sendTextMessage(contactUri, invite);
-        return;
-    }
-
-    it->second->addMember(contactUri,
-                          [w = weak(),
-                           conversationId,
-                           sendRequest,
-                           contactUri](bool ok, const std::string& commitId) {
-                              if (ok) {
-                                  auto shared = w.lock();
-                                  if (shared) {
-                                      std::unique_lock<std::mutex> lk(shared->conversationsMtx_);
-                                      auto it = shared->conversations_.find(conversationId);
-                                      if (it != shared->conversations_.end() && it->second) {
-                                          shared->sendMessageNotification(*it->second,
-                                                                          commitId,
-                                                                          true);
-                                          if (sendRequest) {
-                                              auto invite = it->second->generateInvitation();
-                                              lk.unlock();
-                                              shared->sendTextMessage(contactUri, invite);
-                                          }
-                                      }
-                                  }
-                              }
-                          });
-}
-
-void
-JamiAccount::removeConversationMember(const std::string& conversationId,
-                                      const std::string& contactUri,
-                                      bool isDevice)
-{
-    std::lock_guard<std::mutex> lk(conversationsMtx_);
-    auto it = conversations_.find(conversationId);
-    if (it != conversations_.end() && it->second) {
-        it->second->removeMember(contactUri,
-                                 isDevice,
-                                 [w = weak(), conversationId](bool ok, const std::string& commitId) {
-                                     if (ok) {
-                                         auto shared = w.lock();
-                                         if (shared) {
-                                             std::lock_guard<std::mutex> lk(
-                                                 shared->conversationsMtx_);
-                                             auto it = shared->conversations_.find(conversationId);
-                                             if (it != shared->conversations_.end() && it->second) {
-                                                 shared->sendMessageNotification(*it->second,
-                                                                                 commitId,
-                                                                                 true);
-                                             }
-                                         }
-                                     }
-                                 });
-    }
-}
-
-std::vector<std::map<std::string, std::string>>
-JamiAccount::getConversationMembers(const std::string& conversationId) const
-{
-    std::unique_lock<std::mutex> lk(conversationsMtx_);
-    auto conversation = conversations_.find(conversationId);
-    if (conversation != conversations_.end() && conversation->second)
-        return conversation->second->getMembers(true, true);
-
-    lk.unlock();
-    if (auto info = accountManager_->getInfo()) {
-        auto convIt = info->conversations.find(conversationId);
-        if (convIt != info->conversations.end()) {
-            std::vector<std::map<std::string, std::string>> result;
-            result.reserve(convIt->second.members.size());
-            for (const auto& uri : convIt->second.members) {
-                result.emplace_back(std::map<std::string, std::string> {{"uri", uri}});
-            }
-            return result;
-        }
-    }
-    return {};
-}
-
-// Message send/load
-void
-JamiAccount::sendMessage(const std::string& conversationId,
-                         const std::string& message,
-                         const std::string& parent,
-                         const std::string& type,
-                         bool announce,
-                         const OnDoneCb& cb)
-{
-    Json::Value json;
-    json["body"] = message;
-    json["type"] = type;
-    sendMessage(conversationId, json, parent, announce, cb);
-}
-
-void
-JamiAccount::sendMessage(const std::string& conversationId,
-                         const Json::Value& value,
-                         const std::string& parent,
-                         bool announce,
-                         const OnDoneCb& cb)
-{
-    std::lock_guard<std::mutex> lk(conversationsMtx_);
-    auto conversation = conversations_.find(conversationId);
-    if (conversation != conversations_.end() && conversation->second) {
-        conversation->second->sendMessage(
-            value,
-            parent,
-            [w = weak(), conversationId, announce, cb = std::move(cb)](bool ok,
-                                                                       const std::string& commitId) {
-                if (cb)
-                    cb(ok, commitId);
-                if (!announce)
-                    return;
-                if (ok) {
-                    auto shared = w.lock();
-                    if (shared) {
-                        std::lock_guard<std::mutex> lk(shared->conversationsMtx_);
-                        auto it = shared->conversations_.find(conversationId);
-                        if (it != shared->conversations_.end() && it->second) {
-                            shared->sendMessageNotification(*it->second, commitId, true);
-                        }
-                    }
-                } else
-                    JAMI_ERR("Failed to send message to conversation %s", conversationId.c_str());
-            });
-    }
-}
-
-void
-JamiAccount::sendInstantMessage(const std::string& convId,
-                                const std::map<std::string, std::string>& msg)
-{
-    std::unique_lock<std::mutex> lk(conversationsMtx_);
-    auto conversation = conversations_.find(convId);
-    if (conversation != conversations_.end() && conversation->second) {
-        for (const auto& members : conversation->second->getMembers()) {
-            auto uri = members.at("uri");
-            auto token = std::uniform_int_distribution<uint64_t> {1, DRING_ID_MAX_VAL}(rand);
-            // Announce to all members that a new message is sent
-            sendTextMessage(uri, msg, token, false, true);
-        }
-    } else {
-        lk.unlock();
-        // TODO remove, it's for old API for contacts
-        sendTextMessage(convId, msg);
-    }
-}
-
-uint32_t
-JamiAccount::loadConversationMessages(const std::string& conversationId,
-                                      const std::string& fromMessage,
-                                      size_t n)
-{
-    if (!isConversation(conversationId))
-        return 0;
-    const uint32_t id = LoadIdDist()(rand);
-    std::lock_guard<std::mutex> lk(conversationsMtx_);
-    auto conversation = conversations_.find(conversationId);
-    if (conversation != conversations_.end() && conversation->second) {
-        conversation->second->loadMessages(
-            [w = weak(), conversationId, id](auto&& messages) {
-                if (auto shared = w.lock()) {
-                    emitSignal<DRing::ConversationSignal::ConversationLoaded>(id,
-                                                                              shared->getAccountID(),
-                                                                              conversationId,
-                                                                              messages);
-                }
-            },
-            fromMessage,
-            n);
-    }
-    return id;
-}
-
-uint32_t
-JamiAccount::countInteractions(const std::string& convId,
-                               const std::string& toId,
-                               const std::string& fromId,
-                               const std::string& authorUri) 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, authorUri);
-    }
-    return 0;
-}
-
-void
-JamiAccount::onNewGitCommit(const std::string& peer,
-                            const std::string& deviceId,
-                            const std::string& conversationId,
-                            const std::string& commitId)
-{
-    auto info = accountManager_->getInfo();
-    if (!info)
-        return;
-    auto itConv = info->conversations.find(conversationId);
-    if (itConv != info->conversations.end() && itConv->second.removed)
-        return; // ignore new commits for removed conversation
-    JAMI_DBG("[Account %s] on new commit notification from %s, for %s, commit %s",
-             getAccountID().c_str(),
-             peer.c_str(),
-             conversationId.c_str(),
-             commitId.c_str());
-    fetchNewCommits(peer, DeviceId(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 DeviceId& deviceId,
-                             const std::string& conversationId,
-                             const std::string& commitId)
-{
-    JAMI_DBG("[Account %s] fetch commits for peer %s on device %s",
-             getAccountID().c_str(),
-             peer.c_str(),
-             deviceId.to_c_str());
-
-    std::unique_lock<std::mutex> lk(conversationsMtx_);
-    auto conversation = conversations_.find(conversationId);
-    if (conversation != conversations_.end() && conversation->second) {
-        if (!conversation->second->isMember(peer, true)) {
-            JAMI_WARN("[Account %s] %s is not a member of %s",
-                      getAccountID().c_str(),
-                      peer.c_str(),
-                      conversationId.c_str());
-            return;
-        }
-        if (conversation->second->isBanned(deviceId.toString())) {
-            JAMI_WARN("[Account %s] %s is a banned device in conversation %s",
-                      getAccountID().c_str(),
-                      deviceId.to_c_str(),
-                      conversationId.c_str());
-            return;
-        }
-
-        // Retrieve current last message
-        auto lastMessageId = conversation->second->lastCommitId();
-        if (lastMessageId.empty()) {
-            JAMI_ERR("[Account %s] No message detected. This is a bug", getAccountID().c_str());
-            return;
-        }
-
-        if (hasGitSocket(deviceId, conversationId)) {
-            conversation->second->sync(
-                peer,
-                deviceId.toString(),
-                [peer, deviceId, conversationId, commitId, w = weak()](bool ok) {
-                    auto shared = w.lock();
-                    if (!shared)
-                        return;
-                    if (!ok) {
-                        JAMI_WARN("[Account %s] Could not fetch new commit from %s for %s, other "
-                                  "peer may be disconnected",
-                                  shared->getAccountID().c_str(),
-                                  deviceId.to_c_str(),
-                                  conversationId.c_str());
-                        shared->removeGitSocket(deviceId, conversationId);
-                        JAMI_INFO("[Account %s] Relaunch sync with %s for %s",
-                                  shared->getAccountID().c_str(),
-                                  deviceId.to_c_str(),
-                                  conversationId.c_str());
-                        runOnMainThread([=]() {
-                            if (auto shared = w.lock())
-                                shared->fetchNewCommits(peer, deviceId, conversationId, commitId);
-                        });
-                    }
-                },
-                commitId);
-        } else {
-            lk.unlock();
-            // Else we need to add a new gitSocket
-            std::lock_guard<std::mutex> lkCM(connManagerMtx_);
-            if (!connectionManager_ || !startFetch(conversationId))
-                return;
-            connectionManager_->connectDevice(
-                deviceId,
-                "git://" + deviceId.toString() + "/" + conversationId,
-                [this, conversationId, commitId, peer](std::shared_ptr<ChannelSocket> socket,
-                                                       const DeviceId& deviceId) {
-                    dht::ThreadPool::io().run([w = weak(),
-                                               conversationId,
-                                               socket = std::move(socket),
-                                               peer,
-                                               deviceId,
-                                               commitId] {
-                        auto shared = w.lock();
-                        if (!shared)
-                            return;
-                        std::unique_lock<std::mutex> lk(shared->conversationsMtx_);
-                        auto conversation = shared->conversations_.find(conversationId);
-                        if (!conversation->second)
-                            return;
-                        if (socket) {
-                            shared->addGitSocket(deviceId, conversationId, socket);
-
-                            conversation->second->sync(
-                                peer,
-                                deviceId.toString(),
-                                [deviceId, conversationId, w](bool ok) {
-                                    auto shared = w.lock();
-                                    if (!shared)
-                                        return;
-                                    if (!ok) {
-                                        JAMI_WARN("[Account %s] Could not fetch new commit "
-                                                  "from %s for %s",
-                                                  shared->getAccountID().c_str(),
-                                                  deviceId.to_c_str(),
-                                                  conversationId.c_str());
-                                        shared->removeGitSocket(deviceId, conversationId);
-                                    }
-                                },
-                                commitId);
-                        } else {
-                            JAMI_ERR("[Account %s] Couldn't open a new git channel with %s for "
-                                     "conversation %s",
-                                     shared->getAccountID().c_str(),
-                                     deviceId.to_c_str(),
-                                     conversationId.c_str());
-                        }
-                        {
-                            std::lock_guard<std::mutex> lk(shared->pendingConversationsFetchMtx_);
-                            shared->pendingConversationsFetch_.erase(conversationId);
-                        }
-                    });
-                });
-        }
-    } else {
-        lk.unlock();
-
-        if (accountManager_->getRequest(conversationId) != std::nullopt)
-            return;
-        {
-            // Check if the conversation is cloning
-            std::lock_guard<std::mutex> lk(pendingConversationsFetchMtx_);
-            if (pendingConversationsFetch_.find(conversationId)
-                != pendingConversationsFetch_.end()) {
-                return;
-            }
-        }
-        auto infos = accountManager_->getInfo();
-        if (!infos)
-            return;
-        auto itConv = infos->conversations.find(conversationId);
-        if (itConv != infos->conversations.end()) {
-            cloneConversation(deviceId, peer, itConv->first);
-            return;
-        }
-        JAMI_WARN("[Account %s] Could not find conversation %s, ask for an invite",
-                  getAccountID().c_str(),
-                  conversationId.c_str());
-        sendTextMessage(peer, {{"application/invite", conversationId}});
-    }
-}
-
-void
-JamiAccount::onConversationRequest(const std::string& from, const Json::Value& value)
-{
-    ConversationRequest req(value);
-    JAMI_INFO("[Account %s] Receive a new conversation request for conversation %s from %s",
-              getAccountID().c_str(),
-              req.conversationId.c_str(),
-              from.c_str());
-    auto convId = req.conversationId;
-    req.from = from;
-
-    if (accountManager_->getRequest(convId) != std::nullopt) {
-        JAMI_INFO("[Account %s] Received a request for a conversation already existing. "
-                  "Ignore",
-                  getAccountID().c_str());
-        return;
-    }
-    req.received = std::time(nullptr);
-    auto reqMap = req.toMap();
-    accountManager_->addConversationRequest(convId, std::move(req));
-    // Note: no need to sync here because over connected devices should receives
-    // the same conversation request. Will sync when the conversation will be added
-
-    emitSignal<DRing::ConversationSignal::ConversationRequestReceived>(accountID_, convId, reqMap);
-}
-
-void
-JamiAccount::onNeedConversationRequest(const std::string& from, const std::string& conversationId)
-{
-    // Check if conversation exists
-    std::unique_lock<std::mutex> lk(conversationsMtx_);
-    auto itConv = conversations_.find(conversationId);
-    if (itConv != conversations_.end() && !itConv->second->isRemoving()) {
-        // Check if isMember
-        if (!itConv->second->isMember(from, true)) {
-            JAMI_WARN("%s is asking a new invite for %s, but not a member",
-                      from.c_str(),
-                      conversationId.c_str());
-            return;
-        }
-
-        // Send new invite
-        auto invite = itConv->second->generateInvitation();
-        lk.unlock();
-        JAMI_DBG("%s is asking a new invite for %s", from.c_str(), conversationId.c_str());
-        sendTextMessage(from, invite);
-    }
-}
-
-void
-JamiAccount::checkIfRemoveForCompat(const std::string& peerUri)
-{
-    auto convId = getOneToOneConversation(peerUri);
-    if (convId.empty())
-        return;
-    std::unique_lock<std::mutex> lk(conversationsMtx_);
-    auto it = conversations_.find(convId);
-    if (it == conversations_.end()) {
-        JAMI_ERR("Conversation %s doesn't exist", convId.c_str());
-        return;
-    }
-    // We will only removes the conversation if the member is invited
-    // the contact can have mutiple devices with only some with swarm
-    // support, in this case, just go with recent versions.
-    if (it->second->isMember(peerUri))
-        return;
-    lk.unlock();
-    removeConversation(convId);
+
+    return false;
 }
 
 void
@@ -4728,18 +3999,6 @@ JamiAccount::needToSendProfile(const std::string& deviceId)
     return not fileutils::isFile(vCardPath + DIR_SEPARATOR_STR + deviceId);
 }
 
-bool
-JamiAccount::startFetch(const std::string& convId)
-{
-    std::lock_guard<std::mutex> lk(pendingConversationsFetchMtx_);
-    auto it = pendingConversationsFetch_.find(convId);
-    if (it == pendingConversationsFetch_.end()) {
-        pendingConversationsFetch_[convId] = PendingConversationFetch {};
-        return true;
-    }
-    return it->second.ready;
-}
-
 bool
 JamiAccount::sendSIPMessage(SipConnection& conn,
                             const std::string& to,
@@ -4905,7 +4164,7 @@ JamiAccount::cacheSIPConnection(std::shared_ptr<ChannelSocket>&& socket,
 
     sendProfile(deviceId.toString());
 
-    syncConversations(peerId, deviceId);
+    convModule()->syncConversations(peerId, deviceId.toString());
 
     // Retry messages
     messageEngine_.onPeerOnline(peerId);
@@ -4986,7 +4245,7 @@ JamiAccount::cacheSyncConnection(std::shared_ptr<ChannelSocket>&& socket,
         }
     });
 
-    socket->setOnRecv([this, deviceId = device, peerId](const uint8_t* buf, size_t len) {
+    socket->setOnRecv([this, device, peerId](const uint8_t* buf, size_t len) {
         if (!buf)
             return len;
 
@@ -5000,85 +4259,10 @@ JamiAccount::cacheSyncConnection(std::shared_ptr<ChannelSocket>&& socket,
             return len;
         }
 
-        std::unique_lock<std::mutex> lock(configurationMutex_);
-
-        if (auto manager = dynamic_cast<ArchiveAccountManager*>(accountManager_.get())) {
+        if (auto manager = dynamic_cast<ArchiveAccountManager*>(accountManager_.get()))
             manager->onSyncData(std::move(msg.ds), false);
-        }
-
-        std::vector<std::string> toRm;
-
-        for (const auto& [key, convInfo] : msg.c) {
-            auto convId = convInfo.id;
-            auto removed = convInfo.removed;
-            accountManager_->rmConversationRequest(convId);
-            auto info = accountManager_->getInfo();
-            if (not removed) {
-                // If multi devices, it can detect a conversation that was already
-                // removed, so just check if the convinfo contains a removed conv
-                auto itConv = info->conversations.find(convId);
-                if (itConv != info->conversations.end() && itConv->second.removed)
-                    continue;
-                cloneConversation(deviceId, peerId, convId);
-            } else {
-                {
-                    std::lock_guard<std::mutex> lk(conversationsMtx_);
-                    auto itConv = conversations_.find(convId);
-                    if (itConv != conversations_.end() && !itConv->second->isRemoving()) {
-                        emitSignal<DRing::ConversationSignal::ConversationRemoved>(accountID_,
-                                                                                   convId);
-                        itConv->second->setRemovingFlag();
-                    }
-                }
-                auto ci = info->conversations;
-                auto itConv = ci.find(convId);
-                if (itConv != ci.end()) {
-                    itConv->second.removed = std::time(nullptr);
-                    if (convInfo.erased) {
-                        itConv->second.erased = std::time(nullptr);
-                        toRm.emplace_back(convId);
-                    }
-                    break;
-                }
-                accountManager_->setConversations(std::move(ci));
-            }
-        }
-
-        for (const auto& [convId, req] : msg.cr) {
-            if (isConversation(convId)) {
-                // Already accepted request
-                accountManager_->rmConversationRequest(convId);
-                continue;
-            }
-
-            // New request
-            if (!accountManager_->addConversationRequest(convId, req))
-                continue; // Already added
-
-            if (req.declined != 0) {
-                // Request declined
-                JAMI_INFO("[Account %s] Declined request detected for conversation %s (device %s)",
-                          getAccountID().c_str(),
-                          convId.c_str(),
-                          deviceId.to_c_str());
-                emitSignal<DRing::ConversationSignal::ConversationRequestDeclined>(getAccountID(),
-                                                                                   convId);
-                continue;
-            }
 
-            JAMI_INFO("[Account %s] New request detected for conversation %s (device %s)",
-                      getAccountID().c_str(),
-                      convId.c_str(),
-                      deviceId.to_c_str());
-
-            emitSignal<DRing::ConversationSignal::ConversationRequestReceived>(getAccountID(),
-                                                                               convId,
-                                                                               req.toMap());
-        }
-        lock.unlock();
-        for (const auto& convId : toRm) {
-            removeRepository(convId, false);
-        }
+        convModule()->onSyncData(msg, peerId, device.toString());
         return len;
     });
     sendProfile(device.toString());
@@ -5117,20 +4301,18 @@ JamiAccount::syncWith(const DeviceId& deviceId, const std::shared_ptr<ChannelSoc
 void
 JamiAccount::syncInfos(const std::shared_ptr<ChannelSocket>& socket)
 {
-    auto info = accountManager_->getInfo();
-    std::unique_lock<std::mutex> lk(accountManager_->conversationsRequestsMtx);
-    // Sync conversations
-    if (not info or not socket
-        or (info->conversations.empty() and info->conversationsRequests.empty()))
-        return;
     Json::Value syncValue;
     std::error_code ec;
     msgpack::sbuffer buffer(8192);
     SyncMsg msg;
-    if (info->contacts)
-        msg.ds = info->contacts->getSyncData();
-    msg.c = info->conversations;
-    msg.cr = info->conversationsRequests;
+    {
+        std::lock_guard<std::mutex> lock(configurationMutex_);
+        if (auto info = accountManager_->getInfo())
+            if (info->contacts)
+                msg.ds = info->contacts->getSyncData();
+    }
+    msg.c = ConversationModule::convInfos(accountID_);
+    msg.cr = ConversationModule::convRequests(accountID_);
     msgpack::pack(buffer, msg);
     socket->write(reinterpret_cast<const unsigned char*>(buffer.data()), buffer.size(), ec);
     if (ec)
@@ -5147,175 +4329,12 @@ JamiAccount::syncWithConnected()
     }
 }
 
-void
-JamiAccount::loadConvInfos()
-{
-    std::map<std::string, ConvInfo> convInfos;
-    try {
-        // read file
-        auto file = fileutils::loadFile("convInfo", idPath_);
-        // load values
-        msgpack::object_handle oh = msgpack::unpack((const char*) file.data(), file.size());
-        oh.get().convert(convInfos);
-    } catch (const std::exception& e) {
-        JAMI_WARN("[convInfo] error loading convInfo: %s", e.what());
-        return;
-    }
-
-    for (auto& [key, info] : convInfos) {
-        std::lock_guard<std::mutex> lk(conversationsMtx_);
-        auto itConv = conversations_.find(info.id);
-        if (itConv != conversations_.end() && info.removed) {
-            itConv->second->setRemovingFlag();
-        }
-    }
-    accountManager_->setConversations(convInfos);
-}
-
-void
-JamiAccount::loadConvRequests()
-{
-    try {
-        // read file
-        auto file = fileutils::loadFile("convRequests", idPath_);
-        // load values
-        msgpack::object_handle oh = msgpack::unpack((const char*) file.data(), file.size());
-        std::map<std::string, ConversationRequest> map;
-        oh.get().convert(map);
-        accountManager_->setConversationsRequests(map);
-    } catch (const std::exception& e) {
-        JAMI_WARN("Error loading conversationsRequests: %s", e.what());
-    }
-}
-
-void
-JamiAccount::loadConversations()
-{
-    JAMI_INFO("[Account %s] Start loading conversations…", getAccountID().c_str());
-    auto conversationsRepositories = fileutils::readDirectory(idPath_ + DIR_SEPARATOR_STR
-                                                              + "conversations");
-    conversations_.clear();
-    for (const auto& repository : conversationsRepositories) {
-        try {
-            auto conv = std::make_shared<Conversation>(weak(), repository);
-            std::lock_guard<std::mutex> lk(conversationsMtx_);
-            conversations_.emplace(repository, std::move(conv));
-        } catch (const std::logic_error& e) {
-            JAMI_WARN("[Account %s] Conversations not loaded : %s",
-                      getAccountID().c_str(),
-                      e.what());
-        }
-    }
-    loadConvInfos();
-    loadConvRequests();
-    JAMI_INFO("[Account %s] Conversations loaded!", getAccountID().c_str());
-}
-
 std::shared_ptr<TransferManager>
-JamiAccount::dataTransfer(const std::string& id) const
+JamiAccount::dataTransfer(const std::string& id)
 {
     if (id.empty())
         return nonSwarmTransferManager_;
-    std::unique_lock<std::mutex> lk(conversationsMtx_);
-    auto it = conversations_.find(id);
-    if (it != conversations_.end() && it->second)
-        return it->second->dataTransfer();
-    return nullptr;
-}
-
-void
-JamiAccount::removeRepository(const std::string& conversationId, bool sync, bool force)
-{
-    std::lock_guard<std::mutex> lock(configurationMutex_);
-    std::unique_lock<std::mutex> lk(conversationsMtx_);
-    auto it = conversations_.find(conversationId);
-    if (it != conversations_.end() && it->second && (force || it->second->isRemoving())) {
-        if (!accountManager_)
-            return;
-        try {
-            if (it->second->mode() == ConversationMode::ONE_TO_ONE) {
-                for (const auto& member : it->second->getInitialMembers()) {
-                    if (member != getUsername())
-                        accountManager_->removeContactConversation(member);
-                }
-            }
-        } catch (const std::exception& e) {
-            JAMI_ERR() << e.what();
-        }
-        JAMI_DBG() << "Remove conversation: " << conversationId;
-        it->second->erase();
-        conversations_.erase(it);
-        lk.unlock();
-        auto info = accountManager_->getInfo();
-        // Update convInfos
-        if (!sync or !info)
-            return;
-        auto ci = info->conversations;
-        auto convIt = ci.find(conversationId);
-        if (convIt != ci.end()) {
-            convIt->second.erased = std::time(nullptr);
-            runOnMainThread([w = weak()]() {
-                // Send to connected devices
-                if (auto shared = w.lock())
-                    shared->syncWithConnected();
-            });
-        }
-        accountManager_->setConversations(ci);
-    }
-}
-
-void
-JamiAccount::sendMessageNotification(const Conversation& conversation,
-                                     const std::string& commitId,
-                                     bool sync)
-{
-    Json::Value message;
-    message["id"] = conversation.id();
-    message["commit"] = commitId;
-    // TODO avoid lookup
-    message["deviceId"] = std::string(currentDeviceId());
-    Json::StreamWriterBuilder builder;
-    const auto text = Json::writeString(builder, message);
-    for (const auto& members : conversation.getMembers()) {
-        auto uri = members.at("uri");
-        // Do not send to ourself, it's synced via convInfos
-        if (!sync && username_.find(uri) != std::string::npos)
-            continue;
-        // Announce to all members that a new message is sent
-        sendTextMessage(uri, {{"application/im-gitmessage-id", text}});
-    }
-}
-
-std::string
-JamiAccount::getOneToOneConversation(const std::string& uri) const
-{
-    auto details = getContactDetails(uri);
-    auto it = details.find(DRing::Account::TrustRequest::CONVERSATIONID);
-    if (it != details.end())
-        return it->second;
-    return {};
-}
-
-void
-JamiAccount::addNewConversation(const ConvInfo& convInfo)
-{
-    std::lock_guard<std::mutex> lk(configurationMutex_);
-    if (accountManager_)
-        accountManager_->addConversation(convInfo);
-}
-
-void
-JamiAccount::addCallHistoryMessage(const std::string& uri, uint64_t duration_ms)
-{
-    auto finalUri = uri.substr(0, uri.find("@ring.dht"));
-    auto convId = getOneToOneConversation(finalUri);
-    if (!convId.empty()) {
-        Json::Value value;
-        value["to"] = finalUri;
-        value["type"] = "application/call-history+json";
-        value["duration"] = std::to_string(duration_ms);
-        sendMessage(convId, value);
-    }
+    return convModule()->dataTransfer(id);
 }
 
 void
@@ -5328,52 +4347,6 @@ JamiAccount::monitor() const
         connectionManager_->monitor();
 }
 
-void
-JamiAccount::cloneConversation(const DeviceId& deviceId,
-                               const std::string&,
-                               const std::string& convId)
-{
-    JAMI_DBG("[Account %s] Clone conversation on device %s",
-             getAccountID().c_str(),
-             deviceId.to_c_str());
-
-    if (!isConversation(convId)) {
-        std::lock_guard<std::mutex> lkCM(connManagerMtx_);
-        if (!connectionManager_ || !startFetch(convId))
-            return;
-        connectionManager_->connectDevice(deviceId,
-                                          fmt::format("git://{}/{}", deviceId, convId),
-                                          [this, convId](std::shared_ptr<ChannelSocket> socket,
-                                                         const DeviceId& deviceId) {
-                                              if (socket) {
-                                                  std::unique_lock<std::mutex> lk(
-                                                      pendingConversationsFetchMtx_);
-                                                  auto& pending = pendingConversationsFetch_[convId];
-                                                  if (!pending.ready) {
-                                                      pending.ready = true;
-                                                      pending.deviceId = deviceId.toString();
-                                                      lk.unlock();
-                                                      // Save the git socket
-                                                      addGitSocket(deviceId, convId, socket);
-                                                      checkConversationsEvents();
-                                                  } else {
-                                                      lk.unlock();
-                                                      socket->shutdown();
-                                                  }
-                                              }
-                                          });
-
-        JAMI_INFO("[Account %s] New conversation detected: %s. Ask device %s to clone it",
-                  getAccountID().c_str(),
-                  convId.c_str(),
-                  deviceId.to_c_str());
-    } else {
-        JAMI_INFO("[Account %s] Already have conversation %s",
-                  getAccountID().c_str(),
-                  convId.c_str());
-    }
-}
-
 void
 JamiAccount::sendFile(const std::string& conversationId,
                       const std::string& path,
@@ -5398,27 +4371,27 @@ JamiAccount::sendFile(const std::string& conversationId,
             value["sha3sum"] = fileutils::sha3File(path);
             value["type"] = "application/data-transfer+json";
 
-            shared->sendMessage(conversationId,
-                                value,
-                                parent,
-                                true,
-                                [accId = shared->getAccountID(),
-                                 conversationId,
-                                 tid,
-                                 path](bool, const std::string& commitId) {
-                                    // Create a symlink to answer to re-ask
-                                    auto filelinkPath = fileutils::get_data_dir()
-                                                        + DIR_SEPARATOR_STR + accId
-                                                        + DIR_SEPARATOR_STR + "conversation_data"
-                                                        + DIR_SEPARATOR_STR + conversationId
-                                                        + DIR_SEPARATOR_STR + commitId + "_"
-                                                        + std::to_string(tid);
-                                    auto extension = fileutils::getFileExtension(path);
-                                    if (!extension.empty())
-                                        filelinkPath += "." + extension;
-                                    if (path != filelinkPath && !fileutils::isSymLink(filelinkPath))
-                                        fileutils::createFileLink(filelinkPath, path, true);
-                                });
+            shared->convModule()
+                ->sendMessage(conversationId,
+                              value,
+                              parent,
+                              true,
+                              [accId = shared->getAccountID(),
+                               conversationId,
+                               tid,
+                               path](bool, const std::string& commitId) {
+                                  // Create a symlink to answer to re-ask
+                                  auto filelinkPath = fileutils::get_data_dir() + DIR_SEPARATOR_STR
+                                                      + accId + DIR_SEPARATOR_STR
+                                                      + "conversation_data" + DIR_SEPARATOR_STR
+                                                      + conversationId + DIR_SEPARATOR_STR
+                                                      + commitId + "_" + std::to_string(tid);
+                                  auto extension = fileutils::getFileExtension(path);
+                                  if (!extension.empty())
+                                      filelinkPath += "." + extension;
+                                  if (path != filelinkPath && !fileutils::isSymLink(filelinkPath))
+                                      fileutils::createFileLink(filelinkPath, path, true);
+                              });
         }
     });
 }
@@ -5445,7 +4418,7 @@ JamiAccount::transferFile(const std::string& conversationId,
                           size_t start,
                           size_t end)
 {
-    auto channelName = "data-transfer://" + conversationId + "/" + currentDeviceId() + "/" + fileId;
+    auto channelName = DATA_TRANSFER_URI + conversationId + "/" + currentDeviceId() + "/" + fileId;
     std::lock_guard<std::mutex> lkCM(connManagerMtx_);
     if (!connectionManager_)
         return;
@@ -5471,23 +4444,6 @@ JamiAccount::transferFile(const std::string& conversationId,
         });
 }
 
-bool
-JamiAccount::downloadFile(const std::string& conversationId,
-                          const std::string& interactionId,
-                          const std::string& fileId,
-                          const std::string& path,
-                          size_t start,
-                          size_t end)
-{
-    std::string sha3sum = {};
-    std::lock_guard<std::mutex> lk(conversationsMtx_);
-    auto conversation = conversations_.find(conversationId);
-    if (conversation == conversations_.end() || !conversation->second)
-        return false;
-
-    return conversation->second->downloadFile(interactionId, fileId, path, "", "", start, end);
-}
-
 void
 JamiAccount::askForFileChannel(const std::string& conversationId,
                                const std::string& deviceId,
@@ -5500,7 +4456,7 @@ JamiAccount::askForFileChannel(const std::string& conversationId,
         if (!connectionManager_)
             return;
 
-        auto channelName = "data-transfer://" + conversationId + "/" + currentDeviceId() + "/"
+        auto channelName = DATA_TRANSFER_URI + conversationId + "/" + currentDeviceId() + "/"
                            + fileId;
         if (start != 0 || end != 0) {
             channelName += "?start=" + std::to_string(start) + "&end=" + std::to_string(end);
@@ -5535,18 +4491,8 @@ JamiAccount::askForFileChannel(const std::string& conversationId,
     } else {
         // Only ask for connected devices. For others we will try
         // on new peer online
-        std::vector<std::string> members;
-        {
-            std::lock_guard<std::mutex> lk(conversationsMtx_);
-            auto conversation = conversations_.find(conversationId);
-            if (conversation != conversations_.end() && conversation->second) {
-                for (const auto& m : conversation->second->getMembers()) {
-                    members.emplace_back(m.at("uri"));
-                }
-            }
-        }
-        for (const auto& member : members) {
-            accountManager_->forEachDevice(dht::InfoHash(member),
+        for (const auto& m : convModule()->getConversationMembers(conversationId)) {
+            accountManager_->forEachDevice(dht::InfoHash(m.at("uri")),
                                            [this, tryDevice = std::move(tryDevice)](
                                                const std::shared_ptr<dht::crypto::PublicKey>& dev) {
                                                tryDevice(dev->getLongId());
@@ -5555,4 +4501,15 @@ JamiAccount::askForFileChannel(const std::string& conversationId,
     }
 }
 
+void
+JamiAccount::initConnectionManager()
+{
+    if (!connectionManager_) {
+        connectionManager_ = std::make_unique<ConnectionManager>(*this);
+        channelHandlers_[Uri::Scheme::GIT]
+            = std::make_unique<ConversationChannelHandler>(std::move(weak()),
+                                                           *connectionManager_.get());
+    }
+}
+
 } // namespace jami
diff --git a/src/jamidht/jamiaccount.h b/src/jamidht/jamiaccount.h
index 15675e2b6522a7053387cc1e1539a290478ec359..0338817b4aaea3f981cbadcf19f3856454ea4ca2 100644
--- a/src/jamidht/jamiaccount.h
+++ b/src/jamidht/jamiaccount.h
@@ -34,6 +34,7 @@
 #include "jamidht/conversation.h"
 #include "multiplexed_socket.h"
 #include "data_transfer.h"
+#include "uri.h"
 
 #include "noncopyable.h"
 #include "ip_utils.h"
@@ -42,6 +43,8 @@
 #include "scheduled_executor.h"
 #include "connectionmanager.h"
 #include "gitserver.h"
+#include "channel_handler.h"
+#include "conversation_module.h"
 #include "conversationrepository.h"
 
 #include <opendht/dhtrunner.h>
@@ -79,7 +82,6 @@ namespace jami {
 
 class IceTransport;
 struct Contact;
-struct DeviceSync;
 struct AccountArchive;
 class DhtPeerConnector;
 class ContactList;
@@ -239,6 +241,12 @@ public:
      */
     std::string getServerUri() const { return ""; };
 
+    void setIsComposing(const std::string& conversationUri, bool isWriting) override;
+
+    bool setMessageDisplayed(const std::string& conversationUri,
+                             const std::string& messageId,
+                             int status) override;
+
     /**
      * Get the contact header for
      * @return pj_str_t The contact header based on account information
@@ -331,10 +339,8 @@ public:
     uint64_t sendTextMessage(const std::string& to,
                              const std::map<std::string, std::string>& payloads) override;
     void sendInstantMessage(const std::string& convId,
-                            const std::map<std::string, std::string>& msg) override;
-    void onIsComposing(const std::string& conversationId,
-                       const std::string& peer,
-                       bool isWriting) override;
+                            const std::map<std::string, std::string>& msg);
+    void onIsComposing(const std::string& conversationId, const std::string& peer, bool isWriting);
 
     /* Devices */
     void addDevice(const std::string& password);
@@ -508,106 +514,18 @@ public:
     }
 
     std::string_view currentDeviceId() const;
-    // Conversation management
-    std::string startConversation(ConversationMode mode = ConversationMode::INVITES_ONLY,
-                                  const std::string& otherMember = "");
-    void acceptConversationRequest(const std::string& conversationId);
-    void declineConversationRequest(const std::string& conversationId);
-    std::vector<std::string> getConversations();
-    bool removeConversation(const std::string& conversationId);
-    std::vector<std::map<std::string, std::string>> getConversationRequests();
-
-    // Conversation's infos management
-    void updateConversationInfos(const std::string& conversationId,
-                                 const std::map<std::string, std::string>& infos,
-                                 bool sync = true);
-    std::map<std::string, std::string> conversationInfos(const std::string& conversationId) const;
-    std::vector<uint8_t> conversationVCard(const std::string& conversationId) const;
 
     // Member management
     void saveMembers(const std::string& convId,
                      const std::vector<std::string>& members); // Save confInfos
-    void addConversationMember(const std::string& conversationId,
-                               const std::string& contactUri,
-                               bool sendRequest = true);
-    void removeConversationMember(const std::string& conversationId,
-                                  const std::string& contactUri,
-                                  bool isDevice = false);
-    std::vector<std::map<std::string, std::string>> getConversationMembers(
-        const std::string& conversationId) const;
-
-    // Message send/load
-    void sendMessage(const std::string& conversationId,
-                     const Json::Value& value,
-                     const std::string& parent = "",
-                     bool announce = true,
-                     const OnDoneCb& cb = {});
-    void sendMessage(const std::string& conversationId,
-                     const std::string& message,
-                     const std::string& parent = "",
-                     const std::string& type = "text/plain",
-                     bool announce = true,
-                     const OnDoneCb& cb = {});
-    /**
-     * Add to the related conversation the call history message
-     * @param uri           Peer number
-     * @param duration_ms   The call duration in ms
-     */
-    void addCallHistoryMessage(const std::string& uri, uint64_t duration_ms);
-    uint32_t loadConversationMessages(const std::string& conversationId,
-                                      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
-     * @param authorUri         author to stop counting
-     * @return number of interactions since interactionId
-     */
-    uint32_t countInteractions(const std::string& convId,
-                               const std::string& toId,
-                               const std::string& fromId,
-                               const std::string& authorUri = "") 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
-     * @param deviceId          Contact's device
-     * @param conversationId
-     * @param commitId (optional)
-     */
-    void fetchNewCommits(const std::string& peer,
-                         const DeviceId& deviceId,
-                         const std::string& conversationId,
-                         const std::string& commitId = "");
 
-    // Invites
-    void onConversationRequest(const std::string& from, const Json::Value&) override;
-    void onNeedConversationRequest(const std::string& from,
-                                   const std::string& conversationId) override;
-    void checkIfRemoveForCompat(const std::string& /*peerUri*/) override;
+    bool handleMessage(const std::string& from,
+                       const std::pair<std::string, std::string>& message) override;
 
     void monitor() const;
 
-    /**
-     * Clone a conversation (initial) from device
-     * @param deviceId
-     * @param convId
-     */
-    void cloneConversation(const DeviceId& deviceId,
-                           const std::string& peer,
-                           const std::string& convId);
-
     // File transfer
     void sendFile(const std::string& conversationId,
                   const std::string& path,
@@ -626,19 +544,6 @@ public:
                       const std::string& interactionId,
                       size_t start = 0,
                       size_t end = 0);
-    /**
-     * Ask conversation's members to send back a previous transfer to this deviec
-     * @param conversationId    Related conversation
-     * @param interactionId     Related interaction
-     * @param fileId            Related fileId
-     * @param path              where to download the file
-     */
-    bool downloadFile(const std::string& conversationId,
-                      const std::string& interactionId,
-                      const std::string& fileId,
-                      const std::string& path,
-                      size_t start = 0,
-                      size_t end = 0);
 
     void askForFileChannel(const std::string& conversationId,
                            const std::string& deviceId,
@@ -646,14 +551,14 @@ public:
                            size_t start = 0,
                            size_t end = 0);
 
-    void loadConversations();
-
     /**
      * Retrieve linked transfer manager
      * @param id    conversationId or empty for fallback
      * @return linked transfer manager
      */
-    std::shared_ptr<TransferManager> dataTransfer(const std::string& id = "") const;
+    std::shared_ptr<TransferManager> dataTransfer(const std::string& id = "");
+
+    ConversationModule* convModule();
 
     /**
      * Send Profile via cached SIP connection
@@ -664,6 +569,8 @@ public:
 
     std::string profilePath() const;
 
+    AccountManager* accountManager() { return accountManager_.get(); }
+
 private:
     NON_COPYABLE(JamiAccount);
 
@@ -674,7 +581,6 @@ private:
      * Private structures
      */
     struct PendingCall;
-    struct PendingConversationFetch;
     struct PendingMessage;
     struct BuddyInfo;
     struct DiscoveredPeer;
@@ -733,11 +639,6 @@ private:
      */
     void onTrackedBuddyOnline(const dht::InfoHash&);
 
-    /**
-     * Sync conversations with detected peer
-     */
-    void syncConversations(const std::string& peer, const DeviceId& deviceId);
-
     /**
      * Maps require port via UPnP and other async ops
      */
@@ -800,10 +701,6 @@ private:
      */
     void generateDhParams();
 
-    void loadConvInfos();
-
-    void loadConvRequests();
-
     template<class... Args>
     std::shared_ptr<IceTransport> createIceTransport(const Args&... args);
     void newOutgoingCallHelper(const std::shared_ptr<SIPCall>& call, std::string_view toUri);
@@ -838,15 +735,6 @@ private:
     mutable std::mutex buddyInfoMtx;
     std::map<dht::InfoHash, BuddyInfo> trackedBuddies_;
 
-    /** Conversations */
-    mutable std::mutex conversationsMtx_ {};
-    std::map<std::string, std::shared_ptr<Conversation>> conversations_;
-    bool isConversation(const std::string& convId) const
-    {
-        std::lock_guard<std::mutex> lk(conversationsMtx_);
-        return conversations_.find(convId) != conversations_.end();
-    }
-
     mutable std::mutex dhtValuesMtx_;
     bool dhtPublicInCalls_ {true};
 
@@ -1021,53 +909,13 @@ private:
      */
     void sendProfile(const std::string& deviceId);
 
-    // Conversations
-    std::mutex pendingConversationsFetchMtx_ {};
-    std::map<std::string, PendingConversationFetch> pendingConversationsFetch_;
-    bool startFetch(const std::string& convId);
-
     std::mutex gitServersMtx_ {};
     std::map<dht::Value::Id, std::unique_ptr<GitServer>> gitServers_ {};
 
-    std::shared_ptr<RepeatedTask> conversationsEventHandler {};
-    void checkConversationsEvents();
-    bool handlePendingConversations();
-
     void syncWith(const DeviceId& deviceId, const std::shared_ptr<ChannelSocket>& socket);
     void syncInfos(const std::shared_ptr<ChannelSocket>& socket);
     void syncWithConnected();
 
-    /**
-     * Remove a repository and all files
-     * @param convId
-     * @param sync      If we send an update to other account's devices
-     * @param force     True if ignore the removing flag
-     */
-    void removeRepository(const std::string& convId, bool sync, bool force = false);
-
-    /**
-     * Send a message notification to all members
-     * @param conversation
-     * @param commit
-     * @param sync      If we send an update to other account's devices
-     */
-    void sendMessageNotification(const Conversation& conversation,
-                                 const std::string& commitId,
-                                 bool sync);
-
-    /**
-     * Get related conversation with member
-     * @param uri       The member to search for
-     * @return the conversation id if found else empty
-     */
-    std::string getOneToOneConversation(const std::string& uri) const;
-
-    /**
-     * Add a new ConvInfo
-     * @param id of the conversation
-     */
-    void addNewConversation(const ConvInfo& convInfo);
-
     std::atomic_bool deviceAnnounced_ {false};
 
     //// File transfer
@@ -1076,6 +924,12 @@ private:
     std::map<std::string, std::shared_ptr<TransferManager>> transferManagers_ {};
 
     bool noSha3sumVerification_ {false};
+
+    std::map<Uri::Scheme, std::unique_ptr<ChannelHandlerInterface>> channelHandlers_ {};
+
+    std::unique_ptr<ConversationModule> convModule_;
+
+    void initConnectionManager();
 };
 
 static inline std::ostream&
diff --git a/src/jamidht/p2p.cpp b/src/jamidht/p2p.cpp
index 8edef02a9c714fc82b86e190ab84a927ae6affe0..c3fddd403d5afcd5add557a448bd3479d5380d10 100644
--- a/src/jamidht/p2p.cpp
+++ b/src/jamidht/p2p.cpp
@@ -232,14 +232,17 @@ DhtPeerConnector::requestConnection(
         // In a one_to_one conv with an old version, the contact here can be in an invited
         // state and will not support data-transfer. So if one_to_oe with non accepted, just
         // force to file:// for now.
-        auto members = acc->getConversationMembers(info.conversationId);
+        auto members = acc->convModule()->getConversationMembers(info.conversationId);
         auto preSwarmCompat = members.size() == 2 && members[1]["role"] == "invited";
         if (preSwarmCompat) {
-            auto infos = acc->conversationInfos(info.conversationId);
+            auto infos = acc->convModule()->conversationInfos(info.conversationId);
             preSwarmCompat = infos["mode"] == "0";
         }
         if (!preSwarmCompat)
-            channelName = fmt::format("data-transfer://{}/{}/{}", info.conversationId, acc->currentDeviceId(), tid);
+            channelName = fmt::format("data-transfer://{}/{}/{}",
+                                      info.conversationId,
+                                      acc->currentDeviceId(),
+                                      tid);
         // If peer is not empty this means that we want to send to one device only
         if (!info.peer.empty()) {
             acc->connectionManager().connectDevice(DeviceId(info.peer), channelName, channelReadyCb);
diff --git a/src/plugin/chatservicesmanager.cpp b/src/plugin/chatservicesmanager.cpp
index 6d8b48ed435e031da46831f5ccdbb15612c97886..f10bca75336ec6f65d57edf7b64d5db91cfa5e1d 100644
--- a/src/plugin/chatservicesmanager.cpp
+++ b/src/plugin/chatservicesmanager.cpp
@@ -64,7 +64,8 @@ ChatServicesManager::registerComponentsLifeCycleManagers(PluginManager& pluginMa
             for (auto& toggledList : chatHandlerToggled_) {
                 auto handlerId = std::find_if(toggledList.second.begin(),
                                               toggledList.second.end(),
-                                              [id = (uintptr_t) handlerIt->get()](uintptr_t handlerId) {
+                                              [id = (uintptr_t) handlerIt->get()](
+                                                  uintptr_t handlerId) {
                                                   return (handlerId == id);
                                               });
                 // If ChatHandler we're trying to destroy is currently in use, we deactivate it.
@@ -95,7 +96,7 @@ ChatServicesManager::registerChatService(PluginManager& pluginManager)
                 cm->accountId)) {
             try {
                 if (cm->isSwarm)
-                    acc->sendMessage(cm->peerId, cm->data.at("body"));
+                    acc->convModule()->sendMessage(cm->peerId, cm->data.at("body"));
                 else
                     jami::Manager::instance().sendTextMessage(cm->accountId,
                                                               cm->peerId,
diff --git a/src/sip/sipaccountbase.cpp b/src/sip/sipaccountbase.cpp
index f5cee89154195007df8ad8e79e4a1cc81bcd4c5f..3a8dc90784f3a0a3b64bdd4fe0c8e48978c1f9aa 100644
--- a/src/sip/sipaccountbase.cpp
+++ b/src/sip/sipaccountbase.cpp
@@ -60,13 +60,6 @@ using namespace std::literals;
 
 namespace jami {
 
-static constexpr const char MIME_TYPE_IMDN[] {"message/imdn+xml"};
-static constexpr const char MIME_TYPE_GIT[] {"application/im-gitmessage-id"};
-static constexpr const char MIME_TYPE_INVITE_JSON[] {"application/invite+json"};
-static constexpr const char MIME_TYPE_INVITE[] {"application/invite"};
-static constexpr const char MIME_TYPE_IM_COMPOSING[] {"application/im-iscomposing+xml"};
-static constexpr std::chrono::steady_clock::duration COMPOSING_TIMEOUT {std::chrono::seconds(12)};
-
 SIPAccountBase::SIPAccountBase(const std::string& accountID)
     : Account(accountID)
     , messageEngine_(*this,
@@ -140,82 +133,6 @@ SIPAccountBase::flush()
                       + DIR_SEPARATOR_STR "messages");
 }
 
-std::string
-getIsComposing(const std::string& conversationId, bool isWriting)
-{
-    // implementing https://tools.ietf.org/rfc/rfc3994.txt
-    return fmt::format("<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n"
-                       "<isComposing><state>{}</state>{}</isComposing>",
-                       isWriting ? "active"sv : "idle"sv,
-                       conversationId.empty()
-                           ? ""
-                           : "<conversation>" + conversationId + "</conversation>");
-}
-
-std::string
-getDisplayed(const std::string& conversationId, const std::string& messageId)
-{
-    // implementing https://tools.ietf.org/rfc/rfc5438.txt
-    return fmt::format(
-        "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n"
-        "<imdn><message-id>{}</message-id>\n"
-        "{}"
-        "<display-notification><status><displayed/></status></display-notification>\n"
-        "</imdn>",
-        messageId,
-        conversationId.empty() ? "" : "<conversation>" + conversationId + "</conversation>");
-}
-
-void
-SIPAccountBase::setIsComposing(const std::string& conversationUri, bool isWriting)
-{
-    Uri uri(conversationUri);
-    std::string conversationId = {};
-    if (uri.scheme() == Uri::Scheme::SWARM)
-        conversationId = uri.authority();
-    auto uid = uri.authority();
-
-    if (not isWriting and conversationUri != composingUri_)
-        return;
-
-    if (composingTimeout_) {
-        composingTimeout_->cancel();
-        composingTimeout_.reset();
-    }
-    if (isWriting) {
-        if (not composingUri_.empty() and composingUri_ != conversationUri) {
-            sendInstantMessage(uid,
-                               {{MIME_TYPE_IM_COMPOSING, getIsComposing(conversationId, false)}});
-            composingTime_ = std::chrono::steady_clock::time_point::min();
-        }
-        composingUri_.clear();
-        composingUri_.insert(composingUri_.end(), conversationUri.begin(), conversationUri.end());
-        auto now = std::chrono::steady_clock::now();
-        if (now >= composingTime_ + COMPOSING_TIMEOUT) {
-            sendInstantMessage(uid,
-                               {{MIME_TYPE_IM_COMPOSING, getIsComposing(conversationId, true)}});
-            composingTime_ = now;
-        }
-        std::weak_ptr<SIPAccountBase> weak = std::static_pointer_cast<SIPAccountBase>(
-            shared_from_this());
-        composingTimeout_ = Manager::instance().scheduleTask(
-            [weak, uid, conversationId]() {
-                if (auto sthis = weak.lock()) {
-                    sthis->sendInstantMessage(uid,
-                                              {{MIME_TYPE_IM_COMPOSING,
-                                                getIsComposing(conversationId, false)}});
-                    sthis->composingUri_.clear();
-                    sthis->composingTime_ = std::chrono::steady_clock::time_point::min();
-                }
-            },
-            now + COMPOSING_TIMEOUT);
-    } else {
-        sendInstantMessage(uid, {{MIME_TYPE_IM_COMPOSING, getIsComposing(conversationId, false)}});
-        composingUri_.clear();
-        composingTime_ = std::chrono::steady_clock::time_point::min();
-    }
-}
-
 template<typename T>
 static void
 validate(std::string& member, const std::string& param, const T& valid)
@@ -552,127 +469,8 @@ SIPAccountBase::onTextMessage(const std::string& id,
             JAMI_WARN("Dropping invalid message with MIME type %s", m.first.c_str());
             return;
         }
-        if (m.first == MIME_TYPE_IM_COMPOSING) {
-            try {
-                static const std::regex COMPOSING_REGEX("<state>\\s*(\\w+)\\s*<\\/state>");
-                std::smatch matched_pattern;
-                std::regex_search(m.second, matched_pattern, COMPOSING_REGEX);
-                bool isComposing {false};
-                if (matched_pattern.ready() && !matched_pattern.empty()
-                    && matched_pattern[1].matched) {
-                    isComposing = matched_pattern[1] == "active";
-                }
-                static const std::regex CONVID_REGEX(
-                    "<conversation>\\s*(\\w+)\\s*<\\/conversation>");
-                std::regex_search(m.second, matched_pattern, CONVID_REGEX);
-                std::string conversationId = "";
-                if (matched_pattern.ready() && !matched_pattern.empty()
-                    && matched_pattern[1].matched) {
-                    conversationId = matched_pattern[1];
-                }
-                onIsComposing(conversationId, from, isComposing);
-                if (payloads.size() == 1)
-                    return;
-            } catch (const std::exception& e) {
-                JAMI_WARN("Error parsing composing state: %s", e.what());
-            }
-        } else if (m.first == MIME_TYPE_IMDN) {
-            try {
-                static const std::regex IMDN_MSG_ID_REGEX(
-                    "<message-id>\\s*(\\w+)\\s*<\\/message-id>");
-                static const std::regex IMDN_STATUS_REGEX("<status>\\s*<(\\w+)\\/>\\s*<\\/status>");
-                std::smatch matched_pattern;
-
-                std::regex_search(m.second, matched_pattern, IMDN_MSG_ID_REGEX);
-                std::string messageId;
-                if (matched_pattern.ready() && !matched_pattern.empty()
-                    && matched_pattern[1].matched) {
-                    messageId = matched_pattern[1];
-                } else {
-                    JAMI_WARN("Message displayed: can't parse message ID");
-                    continue;
-                }
-
-                std::regex_search(m.second, matched_pattern, IMDN_STATUS_REGEX);
-                bool isDisplayed {false};
-                if (matched_pattern.ready() && !matched_pattern.empty()
-                    && matched_pattern[1].matched) {
-                    isDisplayed = matched_pattern[1] == "displayed";
-                } else {
-                    JAMI_WARN("Message displayed: can't parse status");
-                    continue;
-                }
-
-                static const std::regex CONVID_REGEX(
-                    "<conversation>\\s*(\\w+)\\s*<\\/conversation>");
-                std::regex_search(m.second, matched_pattern, CONVID_REGEX);
-                std::string conversationId = "";
-                if (matched_pattern.ready() && !matched_pattern.empty()
-                    && matched_pattern[1].matched) {
-                    conversationId = matched_pattern[1];
-                }
-
-                if (!isReadReceiptEnabled())
-                    return;
-                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_,
-                        conversationId,
-                        from,
-                        messageId,
-                        static_cast<int>(DRing::Account::MessageStates::DISPLAYED));
-                    return;
-                }
-                if (payloads.size() == 1)
-                    return;
-            } catch (const std::exception& e) {
-                JAMI_WARN("Error parsing display notification: %s", e.what());
-            }
-        } else if (m.first == MIME_TYPE_GIT) {
-            Json::Value json;
-            std::string err;
-            Json::CharReaderBuilder rbuilder;
-            auto reader = std::unique_ptr<Json::CharReader>(rbuilder.newCharReader());
-            if (!reader->parse(m.second.data(), m.second.data() + m.second.size(), &json, &err)) {
-                JAMI_ERR("Can't parse server response: %s", err.c_str());
-                return;
-            }
-
-            JAMI_WARN("Received indication for new commit available in conversation %s",
-                      json["id"].asString().c_str());
-
-            if (deviceId.empty()) {
-                JAMI_ERR() << "Incorrect deviceId. Can't retrieve history";
-                return;
-            }
-
-            onNewGitCommit(from, deviceId, json["id"].asString(), json["commit"].asString());
-            return;
-        } else if (m.first == MIME_TYPE_INVITE_JSON) {
-            Json::Value json;
-            std::string err;
-            Json::CharReaderBuilder rbuilder;
-            auto reader = std::unique_ptr<Json::CharReader>(rbuilder.newCharReader());
-            if (!reader->parse(m.second.data(), m.second.data() + m.second.size(), &json, &err)) {
-                JAMI_ERR("Can't parse server response: %s", err.c_str());
-                return;
-            }
-            onConversationRequest(from, json);
-            return;
-        } else if (m.first == MIME_TYPE_INVITE) {
-            onNeedConversationRequest(from, m.second);
+        if (handleMessage(from, m))
             return;
-        } else if (m.first == MIME_TYPE_TEXT_PLAIN) {
-            // This means that a text is received, so that
-            // the conversation is not a swarm. For compatibility,
-            // check if we have a swarm created. It can be the case
-            // when the trust request confirm was not received.
-            checkIfRemoveForCompat(from);
-        }
     }
 
 #ifdef ENABLE_PLUGIN
@@ -702,23 +500,6 @@ SIPAccountBase::onTextMessage(const std::string& id,
     }
 }
 
-bool
-SIPAccountBase::setMessageDisplayed(const std::string& conversationUri,
-                                    const std::string& messageId,
-                                    int status)
-{
-    Uri uri(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 && isReadReceiptEnabled())
-        sendInstantMessage(uri.authority(),
-                           {{MIME_TYPE_IMDN, getDisplayed(conversationId, messageId)}});
-    return true;
-}
-
 IpAddr
 SIPAccountBase::getPublishedIpAddress(uint16_t family) const
 {
diff --git a/src/sip/sipaccountbase.h b/src/sip/sipaccountbase.h
index 0f6bca8ad6d766ad25cdd5c5908381bf7ca63fa6..e5d571fe712d49e51687782627cba8762b79c483 100644
--- a/src/sip/sipaccountbase.h
+++ b/src/sip/sipaccountbase.h
@@ -258,12 +258,6 @@ public:
         return messageEngine_.sendMessage(to, payloads);
     }
 
-    void setIsComposing(const std::string& conversationUri, bool isWriting) override;
-
-    bool setMessageDisplayed(const std::string& conversationUri,
-                             const std::string& messageId,
-                             int status) override;
-
     im::MessageStatus getMessageStatus(uint64_t id) const override
     {
         return messageEngine_.getStatus(id);
diff --git a/src/uri.cpp b/src/uri.cpp
index decac06bab277801b3669a738eafedd454aae5e5..145bfb9fddc06cc024a0dff614c491f59ac69ed9 100644
--- a/src/uri.cpp
+++ b/src/uri.cpp
@@ -35,6 +35,8 @@ Uri::Uri(const std::string_view& uri)
             scheme_ = Uri::Scheme::SWARM;
         else if (scheme_str == "jami")
             scheme_ = Uri::Scheme::JAMI;
+        else if (scheme_str == "git")
+            scheme_ = Uri::Scheme::GIT;
         else
             scheme_ = Uri::Scheme::UNRECOGNIZED;
         authority_ = uri.substr(posSep + 1);
diff --git a/src/uri.h b/src/uri.h
index f62e579c43eea44505f63116a910361277637ead..9fd9d866b45dde43c97d0fab7cf6ae7d634fe8cf 100644
--- a/src/uri.h
+++ b/src/uri.h
@@ -31,6 +31,7 @@ public:
         JAMI,        // Start with "jami:" and 45 ASCII chars OR 40 ASCII chars
         SIP,         // Start with "sip:"
         SWARM,       // Start with "swarm:" and 40 ASCII chars
+        GIT,         // Start with "git:"
         UNRECOGNIZED // Anything that doesn't fit in other categories
     };
 
diff --git a/test/unitTest/conversation/conversation.cpp b/test/unitTest/conversation/conversation.cpp
index 7114437fcdbfdb91365bb2abe58316ab78a8f88c..a6214b6328d3837c6472c9153ae15db11b28ba8c 100644
--- a/test/unitTest/conversation/conversation.cpp
+++ b/test/unitTest/conversation/conversation.cpp
@@ -278,7 +278,7 @@ ConversationTest::testCreateConversation()
     DRing::registerSignalHandlers(confHandlers);
 
     // Start conversation
-    auto convId = aliceAccount->startConversation();
+    auto convId = DRing::startConversation(aliceId);
     cv.wait_for(lk, std::chrono::seconds(30), [&]() { return conversationReady; });
     CPPUNIT_ASSERT(conversationReady);
     ConversationRepository repo(aliceAccount, convId);
@@ -311,9 +311,9 @@ ConversationTest::testGetConversation()
 {
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
     auto uri = aliceAccount->getUsername();
-    auto convId = aliceAccount->startConversation();
+    auto convId = DRing::startConversation(aliceId);
 
-    auto conversations = aliceAccount->getConversations();
+    auto conversations = DRing::getConversations(aliceId);
     CPPUNIT_ASSERT(conversations.size() == 1);
     CPPUNIT_ASSERT(conversations.front() == convId);
 }
@@ -339,13 +339,13 @@ ConversationTest::testGetConversationsAfterRm()
     DRing::registerSignalHandlers(confHandlers);
 
     // Start conversation
-    auto convId = aliceAccount->startConversation();
+    auto convId = DRing::startConversation(aliceId);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return conversationReady; }));
 
-    auto conversations = aliceAccount->getConversations();
+    auto conversations = DRing::getConversations(aliceId);
     CPPUNIT_ASSERT(conversations.size() == 1);
-    CPPUNIT_ASSERT(aliceAccount->removeConversation(convId));
-    conversations = aliceAccount->getConversations();
+    CPPUNIT_ASSERT(DRing::removeConversation(aliceId, convId));
+    conversations = DRing::getConversations(aliceId);
     CPPUNIT_ASSERT(conversations.size() == 0);
 }
 
@@ -370,13 +370,13 @@ ConversationTest::testRemoveInvalidConversation()
     DRing::registerSignalHandlers(confHandlers);
 
     // Start conversation
-    auto convId = aliceAccount->startConversation();
+    auto convId = DRing::startConversation(aliceId);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return conversationReady; }));
 
-    auto conversations = aliceAccount->getConversations();
+    auto conversations = DRing::getConversations(aliceId);
     CPPUNIT_ASSERT(conversations.size() == 1);
-    CPPUNIT_ASSERT(!aliceAccount->removeConversation("foo"));
-    conversations = aliceAccount->getConversations();
+    CPPUNIT_ASSERT(!DRing::removeConversation(aliceId, "foo"));
+    conversations = DRing::getConversations(aliceId);
     CPPUNIT_ASSERT(conversations.size() == 1);
 }
 
@@ -401,7 +401,7 @@ ConversationTest::testRemoveConversationNoMember()
     DRing::registerSignalHandlers(confHandlers);
 
     // Start conversation
-    auto convId = aliceAccount->startConversation();
+    auto convId = DRing::startConversation(aliceId);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return conversationReady; }));
 
     // Assert that repository exists
@@ -412,11 +412,11 @@ ConversationTest::testRemoveConversationNoMember()
     CPPUNIT_ASSERT(fileutils::isDirectory(repoPath));
     CPPUNIT_ASSERT(fileutils::isDirectory(dataPath));
 
-    auto conversations = aliceAccount->getConversations();
+    auto conversations = DRing::getConversations(aliceId);
     CPPUNIT_ASSERT(conversations.size() == 1);
     // Removing the conversation will erase all related files
-    CPPUNIT_ASSERT(aliceAccount->removeConversation(convId));
-    conversations = aliceAccount->getConversations();
+    CPPUNIT_ASSERT(DRing::removeConversation(aliceId, convId));
+    conversations = DRing::getConversations(aliceId);
     CPPUNIT_ASSERT(conversations.size() == 0);
     CPPUNIT_ASSERT(!fileutils::isDirectory(repoPath));
     CPPUNIT_ASSERT(!fileutils::isDirectory(dataPath));
@@ -428,7 +428,7 @@ ConversationTest::testRemoveConversationWithMember()
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
     auto bobUri = bobAccount->getUsername();
-    auto convId = aliceAccount->startConversation();
+    auto convId = DRing::startConversation(aliceId);
 
     std::mutex mtx;
     std::unique_lock<std::mutex> lk {mtx};
@@ -469,7 +469,7 @@ ConversationTest::testRemoveConversationWithMember()
         }));
     DRing::registerSignalHandlers(confHandlers);
 
-    aliceAccount->addConversationMember(convId, bobUri);
+    DRing::addConversationMember(aliceId, convId, bobUri);
     CPPUNIT_ASSERT(
         cv.wait_for(lk, std::chrono::seconds(30), [&]() { return memberMessageGenerated; }));
 
@@ -483,7 +483,7 @@ ConversationTest::testRemoveConversationWithMember()
 
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return requestReceived; }));
     memberMessageGenerated = false;
-    bobAccount->acceptConversationRequest(convId);
+    DRing::acceptConversationRequest(bobId, convId);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return conversationReady; }));
     auto clonedPath = fileutils::get_data_dir() + DIR_SEPARATOR_STR + bobAccount->getAccountID()
                       + DIR_SEPARATOR_STR + "conversations" + DIR_SEPARATOR_STR + convId;
@@ -495,7 +495,7 @@ ConversationTest::testRemoveConversationWithMember()
         cv.wait_for(lk, std::chrono::seconds(30), [&]() { return memberMessageGenerated; }));
 
     bobSeeAliceRemoved = false;
-    aliceAccount->removeConversation(convId);
+    DRing::removeConversation(aliceId, convId);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return bobSeeAliceRemoved; }));
     std::this_thread::sleep_for(std::chrono::seconds(3));
     CPPUNIT_ASSERT(!fileutils::isDirectory(repoPath));
@@ -507,7 +507,7 @@ ConversationTest::testAddMember()
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
     auto bobUri = bobAccount->getUsername();
-    auto convId = aliceAccount->startConversation();
+    auto convId = DRing::startConversation(aliceId);
     std::mutex mtx;
     std::unique_lock<std::mutex> lk {mtx};
     std::condition_variable cv;
@@ -538,7 +538,7 @@ ConversationTest::testAddMember()
             }
         }));
     DRing::registerSignalHandlers(confHandlers);
-    aliceAccount->addConversationMember(convId, bobUri);
+    DRing::addConversationMember(aliceId, convId, bobUri);
     CPPUNIT_ASSERT(
         cv.wait_for(lk, std::chrono::seconds(30), [&]() { return memberMessageGenerated; }));
     // Assert that repository exists
@@ -549,7 +549,7 @@ ConversationTest::testAddMember()
     auto bobInvited = repoPath + DIR_SEPARATOR_STR + "invited" + DIR_SEPARATOR_STR + bobUri;
     CPPUNIT_ASSERT(fileutils::isFile(bobInvited));
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return requestReceived; }));
-    bobAccount->acceptConversationRequest(convId);
+    DRing::acceptConversationRequest(bobId, convId);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return conversationReady; }));
     auto clonedPath = fileutils::get_data_dir() + DIR_SEPARATOR_STR + bobAccount->getAccountID()
                       + DIR_SEPARATOR_STR + "conversations" + DIR_SEPARATOR_STR + convId;
@@ -567,7 +567,7 @@ ConversationTest::testMemberAddedNoBadFile()
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
     auto bobUri = bobAccount->getUsername();
-    auto convId = aliceAccount->startConversation();
+    auto convId = DRing::startConversation(aliceId);
     std::mutex mtx;
     std::unique_lock<std::mutex> lk {mtx};
     std::condition_variable cv;
@@ -606,7 +606,7 @@ ConversationTest::testMemberAddedNoBadFile()
                                     "{\"conversationId\":\"" + convId + "\"}"}});
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return requestReceived; }));
     errorDetected = false;
-    bobAccount->acceptConversationRequest(convId);
+    DRing::acceptConversationRequest(bobId, convId);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return errorDetected; }));
     DRing::unregisterSignalHandlers();
 }
@@ -618,7 +618,7 @@ ConversationTest::testAddOfflineMemberThenConnects()
     auto carlaAccount = Manager::instance().getAccount<JamiAccount>(carlaId);
     auto carlaUri = carlaAccount->getUsername();
     aliceAccount->trackBuddyPresence(carlaUri, true);
-    auto convId = aliceAccount->startConversation();
+    auto convId = DRing::startConversation(aliceId);
 
     std::mutex mtx;
     std::unique_lock<std::mutex> lk {mtx};
@@ -642,11 +642,11 @@ ConversationTest::testAddOfflineMemberThenConnects()
             }));
     DRing::registerSignalHandlers(confHandlers);
 
-    aliceAccount->addConversationMember(convId, carlaUri);
+    DRing::addConversationMember(aliceId, convId, carlaUri);
     Manager::instance().sendRegister(carlaId, true);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(60), [&] { return requestReceived; }));
 
-    carlaAccount->acceptConversationRequest(convId);
+    DRing::acceptConversationRequest(carlaId, convId);
     cv.wait_for(lk, std::chrono::seconds(30), [&]() { return conversationReady; });
     auto clonedPath = fileutils::get_data_dir() + DIR_SEPARATOR_STR + carlaAccount->getAccountID()
                       + DIR_SEPARATOR_STR + "conversations" + DIR_SEPARATOR_STR + convId;
@@ -694,10 +694,10 @@ ConversationTest::testGetMembers()
         }));
     DRing::registerSignalHandlers(confHandlers);
     // Start a conversation and add member
-    auto convId = aliceAccount->startConversation();
+    auto convId = DRing::startConversation(aliceId);
 
     messageReceived = false;
-    aliceAccount->addConversationMember(convId, bobUri);
+    DRing::addConversationMember(aliceId, convId, bobUri);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(60), [&]() { return messageReceived; }));
 
     // Assert that repository exists
@@ -705,7 +705,7 @@ ConversationTest::testGetMembers()
                     + DIR_SEPARATOR_STR + "conversations" + DIR_SEPARATOR_STR + convId;
     CPPUNIT_ASSERT(fileutils::isDirectory(repoPath));
 
-    auto members = aliceAccount->getConversationMembers(convId);
+    auto members = DRing::getConversationMembers(aliceId, convId);
     CPPUNIT_ASSERT(members.size() == 2);
     CPPUNIT_ASSERT(members[0]["uri"] == aliceAccount->getUsername());
     CPPUNIT_ASSERT(members[0]["role"] == "admin");
@@ -714,12 +714,12 @@ ConversationTest::testGetMembers()
 
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return requestReceived; }));
     messageReceived = false;
-    bobAccount->acceptConversationRequest(convId);
+    DRing::acceptConversationRequest(bobId, convId);
     cv.wait_for(lk, std::chrono::seconds(30), [&]() { return conversationReady; });
-    members = bobAccount->getConversationMembers(convId);
+    members = DRing::getConversationMembers(bobId, convId);
     CPPUNIT_ASSERT(members.size() == 2);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(60), [&]() { return messageReceived; }));
-    members = aliceAccount->getConversationMembers(convId);
+    members = DRing::getConversationMembers(aliceId, convId);
     CPPUNIT_ASSERT(members.size() == 2);
     CPPUNIT_ASSERT(members[0]["uri"] == aliceAccount->getUsername());
     CPPUNIT_ASSERT(members[0]["role"] == "admin");
@@ -770,12 +770,12 @@ ConversationTest::testSendMessage()
         }));
     DRing::registerSignalHandlers(confHandlers);
 
-    auto convId = aliceAccount->startConversation();
+    auto convId = DRing::startConversation(aliceId);
 
-    aliceAccount->addConversationMember(convId, bobUri);
+    DRing::addConversationMember(aliceId, convId, bobUri);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return requestReceived; }));
 
-    bobAccount->acceptConversationRequest(convId);
+    DRing::acceptConversationRequest(bobId, convId);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return conversationReady; }));
 
     // Assert that repository exists
@@ -783,9 +783,9 @@ ConversationTest::testSendMessage()
                     + DIR_SEPARATOR_STR + "conversations" + DIR_SEPARATOR_STR + convId;
     CPPUNIT_ASSERT(fileutils::isDirectory(repoPath));
     // Wait that alice sees Bob
-    cv.wait_for(lk, std::chrono::seconds(30), [&]() { return messageAliceReceived == 1; });
+    cv.wait_for(lk, std::chrono::seconds(30), [&]() { return messageAliceReceived == 2; });
 
-    aliceAccount->sendMessage(convId, "hi"s);
+    DRing::sendMessage(aliceId, convId, "hi"s, "");
     cv.wait_for(lk, std::chrono::seconds(30), [&]() { return messageBobReceived == 1; });
     DRing::unregisterSignalHandlers();
 }
@@ -814,10 +814,10 @@ ConversationTest::testSendMessageTriggerMessageReceived()
         }));
     DRing::registerSignalHandlers(confHandlers);
 
-    auto convId = aliceAccount->startConversation();
+    auto convId = DRing::startConversation(aliceId);
     cv.wait_for(lk, std::chrono::seconds(30), [&] { return conversationReady; });
 
-    aliceAccount->sendMessage(convId, "hi"s);
+    DRing::sendMessage(aliceId, convId, "hi"s, "");
     cv.wait_for(lk, std::chrono::seconds(30), [&] { return messageReceived == 1; });
     CPPUNIT_ASSERT(messageReceived == 1);
     DRing::unregisterSignalHandlers();
@@ -830,7 +830,7 @@ ConversationTest::testMergeTwoDifferentHeads()
     auto carlaAccount = Manager::instance().getAccount<JamiAccount>(carlaId);
     auto carlaUri = carlaAccount->getUsername();
     aliceAccount->trackBuddyPresence(carlaUri, true);
-    auto convId = aliceAccount->startConversation();
+    auto convId = DRing::startConversation(aliceId);
 
     std::mutex mtx;
     std::unique_lock<std::mutex> lk {mtx};
@@ -855,7 +855,7 @@ ConversationTest::testMergeTwoDifferentHeads()
         }));
     DRing::registerSignalHandlers(confHandlers);
 
-    aliceAccount->addConversationMember(convId, carlaUri, false);
+    aliceAccount->convModule()->addConversationMember(convId, carlaUri, false);
 
     // Cp conversations & convInfo
     auto repoPathAlice = fileutils::get_data_dir() + DIR_SEPARATOR_STR
@@ -873,9 +873,9 @@ ConversationTest::testMergeTwoDifferentHeads()
     ConversationRepository repo(carlaAccount, convId);
     repo.join();
 
-    aliceAccount->sendMessage(convId, "hi"s);
-    aliceAccount->sendMessage(convId, "sup"s);
-    aliceAccount->sendMessage(convId, "jami"s);
+    DRing::sendMessage(aliceId, convId, "hi"s, "");
+    DRing::sendMessage(aliceId, convId, "sup"s, "");
+    DRing::sendMessage(aliceId, convId, "jami"s, "");
 
     // Start Carla, should merge and all messages should be there
     Manager::instance().sendRegister(carlaId, true);
@@ -905,12 +905,12 @@ ConversationTest::testGetRequests()
             }));
     DRing::registerSignalHandlers(confHandlers);
 
-    auto convId = aliceAccount->startConversation();
+    auto convId = DRing::startConversation(aliceId);
 
-    aliceAccount->addConversationMember(convId, bobUri);
+    DRing::addConversationMember(aliceId, convId, bobUri);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return requestReceived; }));
 
-    auto requests = bobAccount->getConversationRequests();
+    auto requests = DRing::getConversationRequests(bobId);
     CPPUNIT_ASSERT(requests.size() == 1);
     CPPUNIT_ASSERT(requests.front()["id"] == convId);
     DRing::unregisterSignalHandlers();
@@ -938,14 +938,14 @@ ConversationTest::testDeclineRequest()
             }));
     DRing::registerSignalHandlers(confHandlers);
 
-    auto convId = aliceAccount->startConversation();
+    auto convId = DRing::startConversation(aliceId);
 
-    aliceAccount->addConversationMember(convId, bobUri);
+    DRing::addConversationMember(aliceId, convId, bobUri);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return requestReceived; }));
 
-    bobAccount->declineConversationRequest(convId);
+    DRing::declineConversationRequest(bobId, convId);
     // Decline request
-    auto requests = bobAccount->getConversationRequests();
+    auto requests = DRing::getConversationRequests(bobId);
     CPPUNIT_ASSERT(requests.size() == 0);
     DRing::unregisterSignalHandlers();
 }
@@ -1017,16 +1017,16 @@ ConversationTest::testSendMessageToMultipleParticipants()
         }));
     DRing::registerSignalHandlers(confHandlers);
 
-    auto convId = aliceAccount->startConversation();
+    auto convId = DRing::startConversation(aliceId);
 
-    aliceAccount->addConversationMember(convId, bobUri);
-    aliceAccount->addConversationMember(convId, carlaUri);
+    DRing::addConversationMember(aliceId, convId, bobUri);
+    DRing::addConversationMember(aliceId, convId, carlaUri);
     CPPUNIT_ASSERT(
         cv.wait_for(lk, std::chrono::seconds(60), [&]() { return requestReceived == 2; }));
 
     messageReceivedAlice = 0;
-    bobAccount->acceptConversationRequest(convId);
-    carlaAccount->acceptConversationRequest(convId);
+    DRing::acceptConversationRequest(bobId, convId);
+    DRing::acceptConversationRequest(carlaId, convId);
     // >= because we can have merges cause the accept commits
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(60), [&]() {
         return conversationReady == 3 && messageReceivedAlice >= 2;
@@ -1040,7 +1040,7 @@ ConversationTest::testSendMessageToMultipleParticipants()
                + DIR_SEPARATOR_STR + "conversations" + DIR_SEPARATOR_STR + convId;
     CPPUNIT_ASSERT(fileutils::isDirectory(repoPath));
 
-    aliceAccount->sendMessage(convId, "hi"s);
+    DRing::sendMessage(aliceId, convId, "hi"s, "");
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(60), [&]() {
         return messageReceivedBob >= 1 && messageReceivedCarla >= 1;
     }));
@@ -1089,11 +1089,11 @@ ConversationTest::testPingPongMessages()
             }
         }));
     DRing::registerSignalHandlers(confHandlers);
-    auto convId = aliceAccount->startConversation();
-    aliceAccount->addConversationMember(convId, bobUri);
+    auto convId = DRing::startConversation(aliceId);
+    DRing::addConversationMember(aliceId, convId, bobUri);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return requestReceived; }));
     messageAliceReceived = 0;
-    bobAccount->acceptConversationRequest(convId);
+    DRing::acceptConversationRequest(bobId, convId);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(60), [&]() {
         return conversationReady && messageAliceReceived == 1;
     }));
@@ -1103,19 +1103,19 @@ ConversationTest::testPingPongMessages()
     CPPUNIT_ASSERT(fileutils::isDirectory(repoPath));
     messageBobReceived = 0;
     messageAliceReceived = 0;
-    aliceAccount->sendMessage(convId, "ping"s);
+    DRing::sendMessage(aliceId, convId, "ping"s, "");
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() {
         return messageBobReceived == 1 && messageAliceReceived == 1;
     }));
-    bobAccount->sendMessage(convId, "pong"s);
+    DRing::sendMessage(bobId, convId, "pong"s, "");
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() {
         return messageBobReceived == 2 && messageAliceReceived == 2;
     }));
-    bobAccount->sendMessage(convId, "ping"s);
+    DRing::sendMessage(bobId, convId, "ping"s, "");
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() {
         return messageBobReceived == 3 && messageAliceReceived == 3;
     }));
-    aliceAccount->sendMessage(convId, "pong"s);
+    DRing::sendMessage(aliceId, convId, "pong"s, "");
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() {
         return messageBobReceived == 4 && messageAliceReceived == 4;
     }));
@@ -1129,7 +1129,7 @@ ConversationTest::testIsComposing()
     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
     auto aliceUri = aliceAccount->getUsername();
     auto bobUri = bobAccount->getUsername();
-    auto convId = aliceAccount->startConversation();
+    auto convId = DRing::startConversation(aliceId);
     std::mutex mtx;
     std::unique_lock<std::mutex> lk {mtx};
     std::condition_variable cv;
@@ -1172,7 +1172,7 @@ ConversationTest::testIsComposing()
                 }
             }));
     DRing::registerSignalHandlers(confHandlers);
-    aliceAccount->addConversationMember(convId, bobUri);
+    DRing::addConversationMember(aliceId, convId, bobUri);
     CPPUNIT_ASSERT(
         cv.wait_for(lk, std::chrono::seconds(30), [&]() { return memberMessageGenerated; }));
     // Assert that repository exists
@@ -1184,7 +1184,7 @@ ConversationTest::testIsComposing()
     CPPUNIT_ASSERT(fileutils::isFile(bobInvited));
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return requestReceived; }));
     memberMessageGenerated = false;
-    bobAccount->acceptConversationRequest(convId);
+    DRing::acceptConversationRequest(bobId, convId);
     CPPUNIT_ASSERT(
         cv.wait_for(lk, std::chrono::seconds(30), [&]() { return memberMessageGenerated; }));
 
@@ -1202,7 +1202,7 @@ ConversationTest::testSetMessageDisplayed()
     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
     auto aliceUri = aliceAccount->getUsername();
     auto bobUri = bobAccount->getUsername();
-    auto convId = aliceAccount->startConversation();
+    auto convId = DRing::startConversation(aliceId);
     std::mutex mtx;
     std::unique_lock<std::mutex> lk {mtx};
     std::condition_variable cv;
@@ -1247,7 +1247,7 @@ ConversationTest::testSetMessageDisplayed()
                 }
             }));
     DRing::registerSignalHandlers(confHandlers);
-    aliceAccount->addConversationMember(convId, bobUri);
+    DRing::addConversationMember(aliceId, convId, bobUri);
     CPPUNIT_ASSERT(
         cv.wait_for(lk, std::chrono::seconds(30), [&]() { return memberMessageGenerated; }));
     // Assert that repository exists
@@ -1259,19 +1259,19 @@ ConversationTest::testSetMessageDisplayed()
     CPPUNIT_ASSERT(fileutils::isFile(bobInvited));
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return requestReceived; }));
     memberMessageGenerated = false;
-    bobAccount->acceptConversationRequest(convId);
+    DRing::acceptConversationRequest(bobId, convId);
     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);
+    auto membersInfos = DRing::getConversationMembers(bobId, convId);
     CPPUNIT_ASSERT(std::find_if(membersInfos.begin(),
                                 membersInfos.end(),
                                 [&](auto infos) {
                                     return infos["uri"] == aliceUri && infos["lastDisplayed"] == "";
                                 })
                    != membersInfos.end());
-    membersInfos = aliceAccount->getConversationMembers(convId);
+    membersInfos = DRing::getConversationMembers(aliceId, convId);
     CPPUNIT_ASSERT(std::find_if(membersInfos.begin(),
                                 membersInfos.end(),
                                 [&](auto infos) {
@@ -1283,7 +1283,7 @@ ConversationTest::testSetMessageDisplayed()
     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);
+    membersInfos = DRing::getConversationMembers(bobId, convId);
     CPPUNIT_ASSERT(std::find_if(membersInfos.begin(),
                                 membersInfos.end(),
                                 [&](auto infos) {
@@ -1291,7 +1291,7 @@ ConversationTest::testSetMessageDisplayed()
                                            && infos["lastDisplayed"] == convId;
                                 })
                    != membersInfos.end());
-    membersInfos = aliceAccount->getConversationMembers(convId);
+    membersInfos = DRing::getConversationMembers(aliceId, convId);
     CPPUNIT_ASSERT(std::find_if(membersInfos.begin(),
                                 membersInfos.end(),
                                 [&](auto infos) {
@@ -1310,17 +1310,13 @@ ConversationTest::testSetMessageDisplayedPreference()
     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
     auto aliceUri = aliceAccount->getUsername();
     auto bobUri = bobAccount->getUsername();
-    auto convId = aliceAccount->startConversation();
-    auto details = aliceAccount->getAccountDetails();
-    CPPUNIT_ASSERT(details[ConfProperties::SENDREADRECEIPT] == "true");
-    details[ConfProperties::SENDREADRECEIPT] = "false";
-    DRing::setAccountDetails(aliceId, details);
+    auto convId = DRing::startConversation(aliceId);
     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,
-         msgDisplayed = false;
+         msgDisplayed = false, aliceRegistered = false;
     confHandlers.insert(
         DRing::exportable_callback<DRing::ConversationSignal::ConversationRequestReceived>(
             [&](const std::string& /*accountId*/,
@@ -1358,18 +1354,35 @@ ConversationTest::testSetMessageDisplayedPreference()
                     cv.notify_one();
                 }
             }));
+    confHandlers.insert(
+        DRing::exportable_callback<DRing::ConfigurationSignal::VolatileDetailsChanged>(
+            [&](const std::string&, const std::map<std::string, std::string>&) {
+                auto details = aliceAccount->getVolatileAccountDetails();
+                auto daemonStatus = details[DRing::Account::ConfProperties::Registration::STATUS];
+                if (daemonStatus == "REGISTERED") {
+                    aliceRegistered = true;
+                    cv.notify_one();
+                }
+            }));
     DRing::registerSignalHandlers(confHandlers);
-    aliceAccount->addConversationMember(convId, bobUri);
+
+    auto details = aliceAccount->getAccountDetails();
+    CPPUNIT_ASSERT(details[ConfProperties::SENDREADRECEIPT] == "true");
+    details[ConfProperties::SENDREADRECEIPT] = "false";
+    DRing::setAccountDetails(aliceId, details);
+    CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return aliceRegistered; }));
+
+    DRing::addConversationMember(aliceId, convId, bobUri);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() {
         return requestReceived && memberMessageGenerated;
     }));
     memberMessageGenerated = false;
-    bobAccount->acceptConversationRequest(convId);
+    DRing::acceptConversationRequest(bobId, convId);
     CPPUNIT_ASSERT(
         cv.wait_for(lk, std::chrono::seconds(30), [&]() { return memberMessageGenerated; }));
 
     // Last displayed messages should not be set yet
-    auto membersInfos = aliceAccount->getConversationMembers(convId);
+    auto membersInfos = DRing::getConversationMembers(aliceId, convId);
     CPPUNIT_ASSERT(std::find_if(membersInfos.begin(),
                                 membersInfos.end(),
                                 [&](auto infos) {
@@ -1382,7 +1395,7 @@ ConversationTest::testSetMessageDisplayedPreference()
     CPPUNIT_ASSERT(!cv.wait_for(lk, std::chrono::seconds(10), [&]() { return msgDisplayed; }));
 
     // Assert that message is set as displayed for self (for the read status)
-    membersInfos = aliceAccount->getConversationMembers(convId);
+    membersInfos = DRing::getConversationMembers(aliceId, convId);
     CPPUNIT_ASSERT(std::find_if(membersInfos.begin(),
                                 membersInfos.end(),
                                 [&](auto infos) {
@@ -1400,7 +1413,7 @@ ConversationTest::testRemoveMember()
     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
     auto aliceUri = aliceAccount->getUsername();
     auto bobUri = bobAccount->getUsername();
-    auto convId = aliceAccount->startConversation();
+    auto convId = DRing::startConversation(aliceId);
     std::mutex mtx;
     std::unique_lock<std::mutex> lk {mtx};
     std::condition_variable cv;
@@ -1436,21 +1449,21 @@ ConversationTest::testRemoveMember()
             }
         }));
     DRing::registerSignalHandlers(confHandlers);
-    aliceAccount->addConversationMember(convId, bobUri);
+    DRing::addConversationMember(aliceId, convId, bobUri);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return requestReceived; }));
     memberMessageGenerated = false;
-    bobAccount->acceptConversationRequest(convId);
+    DRing::acceptConversationRequest(bobId, convId);
     CPPUNIT_ASSERT(
         cv.wait_for(lk, std::chrono::seconds(30), [&]() { return memberMessageGenerated; }));
 
     // Now check that alice, has the only admin, can remove bob
     memberMessageGenerated = false;
     voteMessageGenerated = false;
-    aliceAccount->removeConversationMember(convId, bobUri);
+    DRing::removeConversationMember(aliceId, convId, bobUri);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() {
         return memberMessageGenerated && voteMessageGenerated;
     }));
-    auto members = aliceAccount->getConversationMembers(convId);
+    auto members = DRing::getConversationMembers(aliceId, convId);
     CPPUNIT_ASSERT(members.size() == 1);
     CPPUNIT_ASSERT(members[0]["uri"] == aliceAccount->getUsername());
     CPPUNIT_ASSERT(members[0]["role"] == "admin");
@@ -1464,7 +1477,7 @@ ConversationTest::testMemberBanNoBadFile()
     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
     auto aliceUri = aliceAccount->getUsername();
     auto bobUri = bobAccount->getUsername();
-    auto convId = aliceAccount->startConversation();
+    auto convId = DRing::startConversation(aliceId);
     auto carlaAccount = Manager::instance().getAccount<JamiAccount>(carlaId);
     auto carlaUri = carlaAccount->getUsername();
     aliceAccount->trackBuddyPresence(carlaUri, true);
@@ -1527,22 +1540,22 @@ ConversationTest::testMemberBanNoBadFile()
     DRing::registerSignalHandlers(confHandlers);
     Manager::instance().sendRegister(carlaId, true);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(60), [&] { return carlaConnected; }));
-    aliceAccount->addConversationMember(convId, bobUri);
+    DRing::addConversationMember(aliceId, convId, bobUri);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() {
         return requestReceived && memberMessageGenerated;
     }));
     memberMessageGenerated = false;
-    bobAccount->acceptConversationRequest(convId);
+    DRing::acceptConversationRequest(bobId, convId);
     CPPUNIT_ASSERT(
         cv.wait_for(lk, std::chrono::seconds(30), [&]() { return memberMessageGenerated; }));
     requestReceived = false;
-    aliceAccount->addConversationMember(convId, carlaUri);
+    DRing::addConversationMember(aliceId, convId, carlaUri);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() {
         return requestReceived && memberMessageGenerated;
     }));
     memberMessageGenerated = false;
     messageBobReceived = false;
-    carlaAccount->acceptConversationRequest(convId);
+    DRing::acceptConversationRequest(carlaId, convId);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() {
         return memberMessageGenerated && messageBobReceived;
     }));
@@ -1550,7 +1563,7 @@ ConversationTest::testMemberBanNoBadFile()
     memberMessageGenerated = false;
     voteMessageGenerated = false;
     addFile(aliceAccount, convId, "BADFILE");
-    aliceAccount->removeConversationMember(convId, carlaUri);
+    DRing::removeConversationMember(aliceId, convId, carlaUri);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return errorDetected; }));
     DRing::unregisterSignalHandlers();
 }
@@ -1563,7 +1576,7 @@ ConversationTest::testBanDevice()
     auto aliceUri = aliceAccount->getUsername();
     auto bobUri = bobAccount->getUsername();
     auto bobDeviceId = std::string(bobAccount->currentDeviceId());
-    auto convId = aliceAccount->startConversation();
+    auto convId = DRing::startConversation(aliceId);
     std::mutex mtx;
     std::unique_lock<std::mutex> lk {mtx};
     std::condition_variable cv;
@@ -1602,12 +1615,12 @@ ConversationTest::testBanDevice()
             cv.notify_one();
         }));
     DRing::registerSignalHandlers(confHandlers);
-    aliceAccount->addConversationMember(convId, bobUri);
+    DRing::addConversationMember(aliceId, convId, bobUri);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() {
         return requestReceived && memberMessageGenerated;
     }));
     memberMessageGenerated = false;
-    bobAccount->acceptConversationRequest(convId);
+    DRing::acceptConversationRequest(bobId, convId);
     CPPUNIT_ASSERT(
         cv.wait_for(lk, std::chrono::seconds(30), [&]() { return memberMessageGenerated; }));
 
@@ -1645,9 +1658,9 @@ ConversationTest::testBanDevice()
     memberMessageGenerated = false;
     voteMessageGenerated = false;
     bobGetMessage = false;
-    auto members = aliceAccount->getConversationMembers(convId);
+    auto members = DRing::getConversationMembers(aliceId, convId);
     CPPUNIT_ASSERT(members.size() == 2);
-    aliceAccount->removeConversationMember(convId, bobDeviceId, true);
+    DRing::removeConversationMember(aliceId, convId, bobDeviceId, true);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() {
         return memberMessageGenerated && voteMessageGenerated;
     }));
@@ -1657,7 +1670,7 @@ ConversationTest::testBanDevice()
                       + DIR_SEPARATOR_STR + "banned" + DIR_SEPARATOR_STR + "devices"
                       + DIR_SEPARATOR_STR + bobDeviceId + ".crt";
     CPPUNIT_ASSERT(fileutils::isFile(bannedFile));
-    members = aliceAccount->getConversationMembers(convId);
+    members = DRing::getConversationMembers(aliceId, convId);
     CPPUNIT_ASSERT(members.size() == 2);
 
     // Assert that bob2 get the message, not Bob
@@ -1673,7 +1686,7 @@ ConversationTest::testMemberTryToRemoveAdmin()
     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
     auto aliceUri = aliceAccount->getUsername();
     auto bobUri = bobAccount->getUsername();
-    auto convId = aliceAccount->startConversation();
+    auto convId = DRing::startConversation(aliceId);
     std::mutex mtx;
     std::unique_lock<std::mutex> lk {mtx};
     std::condition_variable cv;
@@ -1698,25 +1711,24 @@ ConversationTest::testMemberTryToRemoveAdmin()
         [&](const std::string& accountId,
             const std::string& conversationId,
             std::map<std::string, std::string> message) {
-            if (accountId == aliceId && conversationId == convId && message["type"] == "member") {
+            if (accountId == aliceId && conversationId == convId && message["type"] == "member")
                 memberMessageGenerated = true;
-                cv.notify_one();
-            }
+            cv.notify_one();
         }));
     DRing::registerSignalHandlers(confHandlers);
-    aliceAccount->addConversationMember(convId, bobUri);
+    DRing::addConversationMember(aliceId, convId, bobUri);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() {
         return requestReceived && memberMessageGenerated;
     }));
     memberMessageGenerated = false;
-    bobAccount->acceptConversationRequest(convId);
+    DRing::acceptConversationRequest(bobId, convId);
     CPPUNIT_ASSERT(
         cv.wait_for(lk, std::chrono::seconds(30), [&]() { return memberMessageGenerated; }));
 
     // Now check that alice, has the only admin, can remove bob
     memberMessageGenerated = false;
-    bobAccount->removeConversationMember(convId, aliceUri);
-    auto members = aliceAccount->getConversationMembers(convId);
+    DRing::removeConversationMember(bobId, convId, aliceUri);
+    auto members = DRing::getConversationMembers(aliceId, convId);
     CPPUNIT_ASSERT(members.size() == 2 && !memberMessageGenerated);
     DRing::unregisterSignalHandlers();
 }
@@ -1728,7 +1740,7 @@ ConversationTest::testBannedMemberCannotSendMessage()
     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
     auto aliceUri = aliceAccount->getUsername();
     auto bobUri = bobAccount->getUsername();
-    auto convId = aliceAccount->startConversation();
+    auto convId = DRing::startConversation(aliceId);
     std::mutex mtx;
     std::unique_lock<std::mutex> lk {mtx};
     std::condition_variable cv;
@@ -1766,27 +1778,27 @@ ConversationTest::testBannedMemberCannotSendMessage()
             cv.notify_one();
         }));
     DRing::registerSignalHandlers(confHandlers);
-    aliceAccount->addConversationMember(convId, bobUri);
+    DRing::addConversationMember(aliceId, convId, bobUri);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() {
         return requestReceived && memberMessageGenerated;
     }));
     memberMessageGenerated = false;
-    bobAccount->acceptConversationRequest(convId);
+    DRing::acceptConversationRequest(bobId, convId);
     CPPUNIT_ASSERT(
         cv.wait_for(lk, std::chrono::seconds(30), [&]() { return memberMessageGenerated; }));
 
     memberMessageGenerated = false;
     voteMessageGenerated = false;
-    aliceAccount->removeConversationMember(convId, bobUri);
+    DRing::removeConversationMember(aliceId, convId, bobUri);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() {
         return memberMessageGenerated && voteMessageGenerated;
     }));
-    auto members = aliceAccount->getConversationMembers(convId);
+    auto members = DRing::getConversationMembers(aliceId, convId);
     CPPUNIT_ASSERT(members.size() == 1);
 
     // Now check that alice doesn't receive a message from Bob
     aliceMessageReceived = false;
-    bobAccount->sendMessage(convId, "hi"s);
+    DRing::sendMessage(bobId, convId, "hi"s, "");
     CPPUNIT_ASSERT(
         !cv.wait_for(lk, std::chrono::seconds(30), [&]() { return aliceMessageReceived; }));
     DRing::unregisterSignalHandlers();
@@ -1799,7 +1811,7 @@ ConversationTest::testAddBannedMember()
     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
     auto aliceUri = aliceAccount->getUsername();
     auto bobUri = bobAccount->getUsername();
-    auto convId = aliceAccount->startConversation();
+    auto convId = DRing::startConversation(aliceId);
     std::mutex mtx;
     std::unique_lock<std::mutex> lk {mtx};
     std::condition_variable cv;
@@ -1835,26 +1847,26 @@ ConversationTest::testAddBannedMember()
             }
         }));
     DRing::registerSignalHandlers(confHandlers);
-    aliceAccount->addConversationMember(convId, bobUri);
+    DRing::addConversationMember(aliceId, convId, bobUri);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() {
         return requestReceived && memberMessageGenerated;
     }));
     memberMessageGenerated = false;
-    bobAccount->acceptConversationRequest(convId);
+    DRing::acceptConversationRequest(bobId, convId);
     CPPUNIT_ASSERT(
         cv.wait_for(lk, std::chrono::seconds(30), [&]() { return memberMessageGenerated; }));
 
     // Now check that alice, has the only admin, can remove bob
     memberMessageGenerated = false;
     voteMessageGenerated = false;
-    aliceAccount->removeConversationMember(convId, bobUri);
+    DRing::removeConversationMember(aliceId, convId, bobUri);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() {
         return memberMessageGenerated && voteMessageGenerated;
     }));
 
     // Then check that bobUri cannot be re-added
     memberMessageGenerated = false;
-    aliceAccount->addConversationMember(convId, bobUri);
+    DRing::addConversationMember(aliceId, convId, bobUri);
     CPPUNIT_ASSERT(
         !cv.wait_for(lk, std::chrono::seconds(30), [&]() { return memberMessageGenerated; }));
     DRing::unregisterSignalHandlers();
@@ -1929,7 +1941,10 @@ ConversationTest::simulateRemoval(std::shared_ptr<JamiAccount> account,
     wbuilder["indentation"] = "";
     cr.commitMessage(Json::writeString(wbuilder, json));
 
-    account->sendMessage(convId, "trigger the fake history to be pulled"s);
+    DRing::sendMessage(account->getAccountID(),
+                       convId,
+                       "trigger the fake history to be pulled"s,
+                       "");
 }
 
 void
@@ -1972,7 +1987,10 @@ ConversationTest::generateFakeInvite(std::shared_ptr<JamiAccount> account,
     wbuilder["indentation"] = "";
     cr.commitMessage(Json::writeString(wbuilder, json));
 
-    account->sendMessage(convId, "trigger the fake history to be pulled"s);
+    DRing::sendMessage(account->getAccountID(),
+                       convId,
+                       "trigger the fake history to be pulled"s,
+                       "");
 }
 
 void
@@ -2267,7 +2285,7 @@ ConversationTest::createFakeConversation(std::shared_ptr<JamiAccount> account)
     test.emplace_back(ConvInfoTest {commit_str, std::time(nullptr), 0, 0});
     msgpack::pack(file, test);
 
-    account->loadConversations(); // necessary to load fake conv
+    account->convModule()->loadConversations(); // necessary to load fake conv
 
     return commit_str;
 }
@@ -2312,7 +2330,7 @@ ConversationTest::testMemberCannotBanOther()
     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
     auto aliceUri = aliceAccount->getUsername();
     auto bobUri = bobAccount->getUsername();
-    auto convId = aliceAccount->startConversation();
+    auto convId = DRing::startConversation(aliceId);
     auto carlaAccount = Manager::instance().getAccount<JamiAccount>(carlaId);
     auto carlaUri = carlaAccount->getUsername();
     aliceAccount->trackBuddyPresence(carlaUri, true);
@@ -2373,23 +2391,23 @@ ConversationTest::testMemberCannotBanOther()
     DRing::registerSignalHandlers(confHandlers);
     Manager::instance().sendRegister(carlaId, true);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(60), [&] { return carlaConnected; }));
-    aliceAccount->addConversationMember(convId, bobUri);
+    DRing::addConversationMember(aliceId, convId, bobUri);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() {
         return requestReceived && memberMessageGenerated;
     }));
     memberMessageGenerated = false;
-    bobAccount->acceptConversationRequest(convId);
+    DRing::acceptConversationRequest(bobId, convId);
     CPPUNIT_ASSERT(
         cv.wait_for(lk, std::chrono::seconds(30), [&]() { return memberMessageGenerated; }));
     requestReceived = false;
     memberMessageGenerated = false;
-    aliceAccount->addConversationMember(convId, carlaUri);
+    DRing::addConversationMember(aliceId, convId, carlaUri);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() {
         return requestReceived && memberMessageGenerated;
     }));
     memberMessageGenerated = false;
     messageBobReceived = false;
-    carlaAccount->acceptConversationRequest(convId);
+    DRing::acceptConversationRequest(carlaId, convId);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() {
         return memberMessageGenerated && messageBobReceived;
     }));
@@ -2401,7 +2419,7 @@ ConversationTest::testMemberCannotBanOther()
     simulateRemoval(carlaAccount, convId, bobUri);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return errorDetected; }));
 
-    aliceAccount->sendMessage(convId, "hi"s);
+    DRing::sendMessage(aliceId, convId, "hi"s, "");
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return messageBobReceived; }));
 }
 
@@ -2412,7 +2430,7 @@ ConversationTest::testCheckAdminFakeAVoteIsDetected()
     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
     auto aliceUri = aliceAccount->getUsername();
     auto bobUri = bobAccount->getUsername();
-    auto convId = aliceAccount->startConversation();
+    auto convId = DRing::startConversation(aliceId);
     auto carlaAccount = Manager::instance().getAccount<JamiAccount>(carlaId);
     auto carlaUri = carlaAccount->getUsername();
     aliceAccount->trackBuddyPresence(carlaUri, true);
@@ -2475,22 +2493,22 @@ ConversationTest::testCheckAdminFakeAVoteIsDetected()
     DRing::registerSignalHandlers(confHandlers);
     Manager::instance().sendRegister(carlaId, true);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(60), [&] { return carlaConnected; }));
-    aliceAccount->addConversationMember(convId, bobUri);
+    DRing::addConversationMember(aliceId, convId, bobUri);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() {
         return requestReceived && memberMessageGenerated;
     }));
     memberMessageGenerated = false;
-    bobAccount->acceptConversationRequest(convId);
+    DRing::acceptConversationRequest(bobId, convId);
     CPPUNIT_ASSERT(
         cv.wait_for(lk, std::chrono::seconds(30), [&]() { return memberMessageGenerated; }));
     requestReceived = false;
-    aliceAccount->addConversationMember(convId, carlaUri);
+    DRing::addConversationMember(aliceId, convId, carlaUri);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() {
         return requestReceived && memberMessageGenerated;
     }));
     memberMessageGenerated = false;
     messageBobReceived = false;
-    carlaAccount->acceptConversationRequest(convId);
+    DRing::acceptConversationRequest(carlaId, convId);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() {
         return memberMessageGenerated && messageBobReceived;
     }));
@@ -2508,7 +2526,7 @@ ConversationTest::testVoteNonEmpty()
     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
     auto aliceUri = aliceAccount->getUsername();
     auto bobUri = bobAccount->getUsername();
-    auto convId = aliceAccount->startConversation();
+    auto convId = DRing::startConversation(aliceId);
     auto carlaAccount = Manager::instance().getAccount<JamiAccount>(carlaId);
     auto carlaUri = carlaAccount->getUsername();
     aliceAccount->trackBuddyPresence(carlaUri, true);
@@ -2571,22 +2589,22 @@ ConversationTest::testVoteNonEmpty()
     DRing::registerSignalHandlers(confHandlers);
     Manager::instance().sendRegister(carlaId, true);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(60), [&] { return carlaConnected; }));
-    aliceAccount->addConversationMember(convId, bobUri);
+    DRing::addConversationMember(aliceId, convId, bobUri);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() {
         return requestReceived && memberMessageGenerated;
     }));
     memberMessageGenerated = false;
-    bobAccount->acceptConversationRequest(convId);
+    DRing::acceptConversationRequest(bobId, convId);
     CPPUNIT_ASSERT(
         cv.wait_for(lk, std::chrono::seconds(30), [&]() { return memberMessageGenerated; }));
     requestReceived = false;
-    aliceAccount->addConversationMember(convId, carlaUri);
+    DRing::addConversationMember(aliceId, convId, carlaUri);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() {
         return requestReceived && memberMessageGenerated;
     }));
     memberMessageGenerated = false;
     messageBobReceived = false;
-    carlaAccount->acceptConversationRequest(convId);
+    DRing::acceptConversationRequest(carlaId, convId);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() {
         return memberMessageGenerated && messageBobReceived;
     }));
@@ -2651,12 +2669,12 @@ ConversationTest::testCommitUnauthorizedUser()
         }));
     DRing::registerSignalHandlers(confHandlers);
 
-    auto convId = aliceAccount->startConversation();
+    auto convId = DRing::startConversation(aliceId);
 
-    aliceAccount->addConversationMember(convId, bobUri);
+    DRing::addConversationMember(aliceId, convId, bobUri);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return requestReceived; }));
 
-    bobAccount->acceptConversationRequest(convId);
+    DRing::acceptConversationRequest(bobId, convId);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return conversationReady; }));
 
     // Assert that repository exists
@@ -2665,7 +2683,7 @@ ConversationTest::testCommitUnauthorizedUser()
     CPPUNIT_ASSERT(fileutils::isDirectory(repoPath));
     // Wait that alice sees Bob
     CPPUNIT_ASSERT(
-        cv.wait_for(lk, std::chrono::seconds(30), [&]() { return messageAliceReceived == 1; }));
+        cv.wait_for(lk, std::chrono::seconds(30), [&]() { return messageAliceReceived == 2; }));
 
     // Add commit from invalid user
     Json::Value root;
@@ -2678,7 +2696,7 @@ ConversationTest::testCommitUnauthorizedUser()
     commitInRepo(repoPath, carlaAccount, message);
 
     errorDetected = false;
-    bobAccount->sendMessage(convId, "hi"s);
+    DRing::sendMessage(bobId, convId, "hi"s, "");
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return errorDetected; }));
     DRing::unregisterSignalHandlers();
 }
@@ -2688,7 +2706,7 @@ ConversationTest::testAdminCannotKickTheirself()
 {
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
     auto aliceUri = aliceAccount->getUsername();
-    auto convId = aliceAccount->startConversation();
+    auto convId = DRing::startConversation(aliceId);
     std::mutex mtx;
     std::unique_lock<std::mutex> lk {mtx};
     std::condition_variable cv;
@@ -2726,10 +2744,10 @@ ConversationTest::testAdminCannotKickTheirself()
             cv.notify_one();
         }));
     DRing::registerSignalHandlers(confHandlers);
-    auto members = aliceAccount->getConversationMembers(convId);
+    auto members = DRing::getConversationMembers(aliceId, convId);
     CPPUNIT_ASSERT(members.size() == 1);
-    aliceAccount->removeConversationMember(convId, aliceUri);
-    members = aliceAccount->getConversationMembers(convId);
+    DRing::removeConversationMember(aliceId, convId, aliceUri);
+    members = DRing::getConversationMembers(aliceId, convId);
     CPPUNIT_ASSERT(members.size() == 1);
 }
 
@@ -2793,10 +2811,10 @@ ConversationTest::testNoBadFileInInitialCommit()
 
     Manager::instance().sendRegister(carlaId, true);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&] { return carlaConnected; }));
-    carlaAccount->addConversationMember(convId, aliceUri);
+    DRing::addConversationMember(carlaId, convId, aliceUri);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return requestReceived; }));
 
-    aliceAccount->acceptConversationRequest(convId);
+    DRing::acceptConversationRequest(aliceId, convId);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return errorDetected; }));
 }
 
@@ -2816,7 +2834,7 @@ ConversationTest::testPlainTextNoBadFile()
     bool requestReceived = false;
     bool conversationReady = false;
     bool errorDetected = false;
-    std::string convId = aliceAccount->startConversation();
+    std::string convId = DRing::startConversation(aliceId);
     confHandlers.insert(DRing::exportable_callback<DRing::ConversationSignal::MessageReceived>(
         [&](const std::string& accountId,
             const std::string& conversationId,
@@ -2855,13 +2873,13 @@ ConversationTest::testPlainTextNoBadFile()
         }));
     DRing::registerSignalHandlers(confHandlers);
 
-    aliceAccount->addConversationMember(convId, bobUri);
+    DRing::addConversationMember(aliceId, convId, bobUri);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() {
         return requestReceived && memberMessageGenerated;
     }));
     memberMessageGenerated = false;
 
-    bobAccount->acceptConversationRequest(convId);
+    DRing::acceptConversationRequest(bobId, convId);
     cv.wait_for(lk, std::chrono::seconds(30), [&] {
         return conversationReady && memberMessageGenerated;
     });
@@ -2872,7 +2890,7 @@ ConversationTest::testPlainTextNoBadFile()
     root["body"] = "hi";
     commit(aliceAccount, convId, root);
     errorDetected = false;
-    aliceAccount->sendMessage(convId, "hi"s);
+    DRing::sendMessage(aliceId, convId, "hi"s, "");
     // Check not received due to the unwanted file
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return errorDetected; }));
     DRing::unregisterSignalHandlers();
@@ -2885,7 +2903,7 @@ ConversationTest::testVoteNoBadFile()
     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
     auto aliceUri = aliceAccount->getUsername();
     auto bobUri = bobAccount->getUsername();
-    auto convId = aliceAccount->startConversation();
+    auto convId = DRing::startConversation(aliceId);
     auto carlaAccount = Manager::instance().getAccount<JamiAccount>(carlaId);
     auto carlaUri = carlaAccount->getUsername();
     aliceAccount->trackBuddyPresence(carlaUri, true);
@@ -2948,22 +2966,22 @@ ConversationTest::testVoteNoBadFile()
     Manager::instance().sendRegister(carlaId, true);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return carlaConnected; }));
 
-    aliceAccount->addConversationMember(convId, bobUri);
+    DRing::addConversationMember(aliceId, convId, bobUri);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() {
         return requestReceived && memberMessageGenerated;
     }));
     memberMessageGenerated = false;
-    bobAccount->acceptConversationRequest(convId);
+    DRing::acceptConversationRequest(bobId, convId);
     CPPUNIT_ASSERT(
         cv.wait_for(lk, std::chrono::seconds(30), [&]() { return memberMessageGenerated; }));
     requestReceived = false;
-    aliceAccount->addConversationMember(convId, carlaUri);
+    DRing::addConversationMember(aliceId, convId, carlaUri);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() {
         return requestReceived && memberMessageGenerated;
     }));
     memberMessageGenerated = false;
     messageBobReceived = false;
-    carlaAccount->acceptConversationRequest(convId);
+    DRing::acceptConversationRequest(carlaId, convId);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() {
         return memberMessageGenerated && messageBobReceived;
     }));
@@ -2971,13 +2989,13 @@ ConversationTest::testVoteNoBadFile()
     // Now Alice remove Carla without a vote. Bob will not receive the message
     messageBobReceived = false;
     addFile(aliceAccount, convId, "BADFILE");
-    aliceAccount->removeConversationMember(convId, bobUri);
+    DRing::removeConversationMember(aliceId, convId, bobUri);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() {
         return memberMessageGenerated && voteMessageGenerated;
     }));
 
     messageCarlaReceived = false;
-    bobAccount->sendMessage(convId, "final"s);
+    DRing::sendMessage(bobId, convId, "final"s, "");
     CPPUNIT_ASSERT(
         cv.wait_for(lk, std::chrono::seconds(30), [&]() { return messageCarlaReceived; }));
 }
@@ -3034,7 +3052,7 @@ ConversationTest::testETooBigClone()
         }));
     DRing::registerSignalHandlers(confHandlers);
 
-    auto convId = aliceAccount->startConversation();
+    auto convId = DRing::startConversation(aliceId);
 
     // Assert that repository exists
     auto repoPath = fileutils::get_data_dir() + DIR_SEPARATOR_STR + aliceAccount->getAccountID()
@@ -3047,11 +3065,11 @@ ConversationTest::testETooBigClone()
 
     addAll(aliceAccount, convId);
 
-    aliceAccount->addConversationMember(convId, bobUri);
+    DRing::addConversationMember(aliceId, convId, bobUri);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return requestReceived; }));
 
     errorDetected = false;
-    bobAccount->acceptConversationRequest(convId);
+    DRing::acceptConversationRequest(bobId, convId);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return errorDetected; }));
     DRing::unregisterSignalHandlers();
 }
@@ -3108,16 +3126,16 @@ ConversationTest::testETooBigFetch()
         }));
     DRing::registerSignalHandlers(confHandlers);
 
-    auto convId = aliceAccount->startConversation();
+    auto convId = DRing::startConversation(aliceId);
 
-    aliceAccount->addConversationMember(convId, bobUri);
+    DRing::addConversationMember(aliceId, convId, bobUri);
     cv.wait_for(lk, std::chrono::seconds(30), [&]() { return requestReceived; });
 
-    bobAccount->acceptConversationRequest(convId);
+    DRing::acceptConversationRequest(bobId, convId);
     cv.wait_for(lk, std::chrono::seconds(30), [&]() { return conversationReady; });
 
     // Wait that alice sees Bob
-    cv.wait_for(lk, std::chrono::seconds(30), [&]() { return messageAliceReceived == 1; });
+    cv.wait_for(lk, std::chrono::seconds(30), [&]() { return messageAliceReceived == 2; });
 
     auto repoPath = fileutils::get_data_dir() + DIR_SEPARATOR_STR + aliceAccount->getAccountID()
                     + DIR_SEPARATOR_STR + "conversations" + DIR_SEPARATOR_STR + convId;
@@ -3134,7 +3152,7 @@ ConversationTest::testETooBigFetch()
     json["type"] = "text/plain";
     commit(aliceAccount, convId, json);
 
-    aliceAccount->sendMessage(convId, "hi"s);
+    DRing::sendMessage(aliceId, convId, "hi"s, "");
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return errorDetected; }));
     DRing::unregisterSignalHandlers();
 }
@@ -3146,7 +3164,7 @@ ConversationTest::testMemberJoinsNoBadFile()
     auto carlaAccount = Manager::instance().getAccount<JamiAccount>(carlaId);
     auto carlaUri = carlaAccount->getUsername();
     aliceAccount->trackBuddyPresence(carlaUri, true);
-    auto convId = aliceAccount->startConversation();
+    auto convId = DRing::startConversation(aliceId);
 
     std::mutex mtx;
     std::unique_lock<std::mutex> lk {mtx};
@@ -3191,7 +3209,7 @@ ConversationTest::testMemberJoinsNoBadFile()
         }));
     DRing::registerSignalHandlers(confHandlers);
 
-    aliceAccount->addConversationMember(convId, carlaUri, false);
+    aliceAccount->convModule()->addConversationMember(convId, carlaUri, false);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(5), [&] { return memberMessageGenerated; }));
 
     // Cp conversations & convInfo
@@ -3212,12 +3230,12 @@ ConversationTest::testMemberJoinsNoBadFile()
     ConversationRepository repo(carlaAccount, convId);
 
     // Start Carla, should merge and all messages should be there
-    carlaAccount->loadConversations(); // Because of the copy
+    carlaAccount->convModule()->loadConversations(); // Because of the copy
     Manager::instance().sendRegister(carlaId, true);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&] { return carlaConnected; }));
 
     errorDetected = false;
-    carlaAccount->sendMessage(convId, "hi"s);
+    DRing::sendMessage(carlaId, convId, "hi"s, "");
 
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(60), [&] { return errorDetected; }));
     DRing::unregisterSignalHandlers();
@@ -3230,7 +3248,7 @@ ConversationTest::testMemberAddedNoCertificate()
     auto carlaAccount = Manager::instance().getAccount<JamiAccount>(carlaId);
     auto carlaUri = carlaAccount->getUsername();
     aliceAccount->trackBuddyPresence(carlaUri, true);
-    auto convId = aliceAccount->startConversation();
+    auto convId = DRing::startConversation(aliceId);
 
     std::mutex mtx;
     std::unique_lock<std::mutex> lk {mtx};
@@ -3275,7 +3293,7 @@ ConversationTest::testMemberAddedNoCertificate()
         }));
     DRing::registerSignalHandlers(confHandlers);
 
-    aliceAccount->addConversationMember(convId, carlaUri, false);
+    aliceAccount->convModule()->addConversationMember(convId, carlaUri, false);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(5), [&] { return memberMessageGenerated; }));
 
     // Cp conversations & convInfo
@@ -3306,11 +3324,11 @@ ConversationTest::testMemberAddedNoCertificate()
     cr.commitMessage(Json::writeString(wbuilder, json));
 
     // Start Carla, should merge and all messages should be there
-    carlaAccount->loadConversations(); // Because of the copy
+    carlaAccount->convModule()->loadConversations(); // Because of the copy
     Manager::instance().sendRegister(carlaId, true);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&] { return carlaConnected; }));
 
-    carlaAccount->sendMessage(convId, "hi"s);
+    DRing::sendMessage(carlaId, convId, "hi"s, "");
     errorDetected = false;
 
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(60), [&] { return errorDetected; }));
@@ -3324,7 +3342,7 @@ ConversationTest::testMemberJoinsInviteRemoved()
     auto carlaAccount = Manager::instance().getAccount<JamiAccount>(carlaId);
     auto carlaUri = carlaAccount->getUsername();
     aliceAccount->trackBuddyPresence(carlaUri, true);
-    auto convId = aliceAccount->startConversation();
+    auto convId = DRing::startConversation(aliceId);
 
     std::mutex mtx;
     std::unique_lock<std::mutex> lk {mtx};
@@ -3369,7 +3387,7 @@ ConversationTest::testMemberJoinsInviteRemoved()
         }));
     DRing::registerSignalHandlers(confHandlers);
 
-    aliceAccount->addConversationMember(convId, carlaUri, false);
+    aliceAccount->convModule()->addConversationMember(convId, carlaUri, false);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(5), [&] { return memberMessageGenerated; }));
 
     // Cp conversations & convInfo
@@ -3405,11 +3423,11 @@ ConversationTest::testMemberJoinsInviteRemoved()
     commit(carlaAccount, convId, json);
 
     // Start Carla, should merge and all messages should be there
-    carlaAccount->loadConversations(); // Because of the copy
+    carlaAccount->convModule()->loadConversations(); // Because of the copy
     Manager::instance().sendRegister(carlaId, true);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&] { return carlaConnected; }));
 
-    carlaAccount->sendMessage(convId, "hi"s);
+    DRing::sendMessage(carlaId, convId, "hi"s, "");
     errorDetected = false;
 
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(60), [&] { return errorDetected; }));
@@ -3597,7 +3615,7 @@ ConversationTest::testFailAddMemberInOneToOne()
     aliceAccount->sendTrustRequest(bobUri, {});
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(5), [&]() { return !convId.empty(); }));
     memberMessageGenerated = false;
-    aliceAccount->addConversationMember(convId, carlaUri);
+    DRing::addConversationMember(aliceId, convId, carlaUri);
     CPPUNIT_ASSERT(
         !cv.wait_for(lk, std::chrono::seconds(5), [&]() { return memberMessageGenerated; }));
 }
@@ -3608,7 +3626,7 @@ ConversationTest::testUnknownModeDetected()
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
     auto bobUri = bobAccount->getUsername();
-    auto convId = aliceAccount->startConversation();
+    auto convId = DRing::startConversation(aliceId);
     ConversationRepository repo(aliceAccount, convId);
     Json::Value json;
     json["mode"] = 1412;
@@ -3657,10 +3675,10 @@ ConversationTest::testUnknownModeDetected()
             cv.notify_one();
         }));
     DRing::registerSignalHandlers(confHandlers);
-    aliceAccount->addConversationMember(convId, bobUri);
+    DRing::addConversationMember(aliceId, convId, bobUri);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return requestReceived; }));
     errorDetected = false;
-    bobAccount->acceptConversationRequest(convId);
+    DRing::acceptConversationRequest(bobId, convId);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return errorDetected; }));
 }
 
@@ -3724,7 +3742,7 @@ ConversationTest::testRemoveContact()
         cv.wait_for(lk, std::chrono::seconds(30), [&]() { return memberMessageGenerated; }));
 
     // Check that getConversationMembers return "role":"left"
-    auto members = aliceAccount->getConversationMembers(convId);
+    auto members = DRing::getConversationMembers(aliceId, convId);
     CPPUNIT_ASSERT(std::find_if(members.begin(),
                                 members.end(),
                                 [&](auto member) {
@@ -3885,7 +3903,7 @@ ConversationTest::testConversationMemberEvent()
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
     auto bobUri = bobAccount->getUsername();
-    auto convId = aliceAccount->startConversation();
+    auto convId = DRing::startConversation(aliceId);
     std::mutex mtx;
     std::unique_lock<std::mutex> lk {mtx};
     std::condition_variable cv;
@@ -3919,7 +3937,7 @@ ConversationTest::testConversationMemberEvent()
                 cv.notify_one();
             }));
     DRing::registerSignalHandlers(confHandlers);
-    aliceAccount->addConversationMember(convId, bobUri);
+    DRing::addConversationMember(aliceId, convId, bobUri);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return memberAddGenerated; }));
     // Assert that repository exists
     auto repoPath = fileutils::get_data_dir() + DIR_SEPARATOR_STR + aliceAccount->getAccountID()
@@ -3929,7 +3947,7 @@ ConversationTest::testConversationMemberEvent()
     auto bobInvited = repoPath + DIR_SEPARATOR_STR + "invited" + DIR_SEPARATOR_STR + bobUri;
     CPPUNIT_ASSERT(fileutils::isFile(bobInvited));
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return requestReceived; }));
-    bobAccount->acceptConversationRequest(convId);
+    DRing::acceptConversationRequest(bobId, convId);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return conversationReady; }));
     auto clonedPath = fileutils::get_data_dir() + DIR_SEPARATOR_STR + bobAccount->getAccountID()
                       + DIR_SEPARATOR_STR + "conversations" + DIR_SEPARATOR_STR + convId;
@@ -4113,23 +4131,23 @@ ConversationTest::testUpdateProfile()
         }));
     DRing::registerSignalHandlers(confHandlers);
 
-    auto convId = aliceAccount->startConversation();
+    auto convId = DRing::startConversation(aliceId);
 
-    aliceAccount->addConversationMember(convId, bobUri);
+    DRing::addConversationMember(aliceId, convId, bobUri);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return requestReceived; }));
 
     messageAliceReceived = 0;
-    bobAccount->acceptConversationRequest(convId);
+    DRing::acceptConversationRequest(bobId, convId);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() {
         return conversationReady && messageAliceReceived == 1;
     }));
 
     messageBobReceived = 0;
-    aliceAccount->updateConversationInfos(convId, {{"title", "My awesome swarm"}});
+    aliceAccount->convModule()->updateConversationInfos(convId, {{"title", "My awesome swarm"}});
     CPPUNIT_ASSERT(
         cv.wait_for(lk, std::chrono::seconds(30), [&]() { return messageBobReceived == 1; }));
 
-    auto infos = bobAccount->conversationInfos(convId);
+    auto infos = DRing::conversationInfos(bobId, convId);
     CPPUNIT_ASSERT(infos["title"] == "My awesome swarm");
     CPPUNIT_ASSERT(infos["description"].empty());
 
@@ -4178,12 +4196,12 @@ ConversationTest::testCheckProfileInConversationRequest()
         }));
     DRing::registerSignalHandlers(confHandlers);
 
-    auto convId = aliceAccount->startConversation();
-    aliceAccount->updateConversationInfos(convId, {{"title", "My awesome swarm"}});
+    auto convId = DRing::startConversation(aliceId);
+    aliceAccount->convModule()->updateConversationInfos(convId, {{"title", "My awesome swarm"}});
 
-    aliceAccount->addConversationMember(convId, bobUri);
+    DRing::addConversationMember(aliceId, convId, bobUri);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return requestReceived; }));
-    auto requests = bobAccount->getConversationRequests();
+    auto requests = DRing::getConversationRequests(bobId);
     CPPUNIT_ASSERT(requests.size() == 1);
     CPPUNIT_ASSERT(requests.front()["title"] == "My awesome swarm");
 
@@ -4254,7 +4272,7 @@ ConversationTest::testMemberCannotUpdateProfile()
     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
     auto bobUri = bobAccount->getUsername();
 
-    auto convId = aliceAccount->startConversation();
+    auto convId = DRing::startConversation(aliceId);
 
     std::mutex mtx;
     std::unique_lock<std::mutex> lk {mtx};
@@ -4301,17 +4319,17 @@ ConversationTest::testMemberCannotUpdateProfile()
         }));
     DRing::registerSignalHandlers(confHandlers);
 
-    aliceAccount->addConversationMember(convId, bobUri);
+    DRing::addConversationMember(aliceId, convId, bobUri);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return requestReceived; }));
 
     messageAliceReceived = 0;
-    bobAccount->acceptConversationRequest(convId);
+    DRing::acceptConversationRequest(bobId, convId);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() {
         return conversationReady && messageAliceReceived == 1;
     }));
 
     messageBobReceived = 0;
-    bobAccount->updateConversationInfos(convId, {{"title", "My awesome swarm"}});
+    bobAccount->convModule()->updateConversationInfos(convId, {{"title", "My awesome swarm"}});
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(5), [&]() { return errorDetected; }));
 
     DRing::unregisterSignalHandlers();
@@ -4323,7 +4341,7 @@ ConversationTest::testUpdateProfileWithBadFile()
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
     auto bobUri = bobAccount->getUsername();
-    auto convId = aliceAccount->startConversation();
+    auto convId = DRing::startConversation(aliceId);
 
     std::mutex mtx;
     std::unique_lock<std::mutex> lk {mtx};
@@ -4370,11 +4388,11 @@ ConversationTest::testUpdateProfileWithBadFile()
         }));
     DRing::registerSignalHandlers(confHandlers);
 
-    aliceAccount->addConversationMember(convId, bobUri);
+    DRing::addConversationMember(aliceId, convId, bobUri);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return requestReceived; }));
 
     messageAliceReceived = 0;
-    bobAccount->acceptConversationRequest(convId);
+    DRing::acceptConversationRequest(bobId, convId);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() {
         return conversationReady && messageAliceReceived == 1;
     }));
@@ -4391,7 +4409,7 @@ END:VCARD";
     root["type"] = "application/update-profile";
     commit(aliceAccount, convId, root);
     errorDetected = false;
-    aliceAccount->sendMessage(convId, "hi"s);
+    DRing::sendMessage(aliceId, convId, "hi"s, "");
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return errorDetected; }));
 
     DRing::unregisterSignalHandlers();
@@ -4403,7 +4421,7 @@ ConversationTest::testFetchProfileUnauthorized()
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
     auto bobUri = bobAccount->getUsername();
-    auto convId = aliceAccount->startConversation();
+    auto convId = DRing::startConversation(aliceId);
 
     std::mutex mtx;
     std::unique_lock<std::mutex> lk {mtx};
@@ -4450,11 +4468,11 @@ ConversationTest::testFetchProfileUnauthorized()
         }));
     DRing::registerSignalHandlers(confHandlers);
 
-    aliceAccount->addConversationMember(convId, bobUri);
+    DRing::addConversationMember(aliceId, convId, bobUri);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return requestReceived; }));
 
     messageAliceReceived = 0;
-    bobAccount->acceptConversationRequest(convId);
+    DRing::acceptConversationRequest(bobId, convId);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() {
         return conversationReady && messageAliceReceived == 1;
     }));
@@ -4470,7 +4488,7 @@ END:VCARD";
     root["type"] = "application/update-profile";
     commit(bobAccount, convId, root);
     errorDetected = false;
-    bobAccount->sendMessage(convId, "hi"s);
+    DRing::sendMessage(bobId, convId, "hi"s, "");
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return errorDetected; }));
 
     DRing::unregisterSignalHandlers();
@@ -4481,9 +4499,9 @@ ConversationTest::testDoNotLoadIncorrectConversation()
 {
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
     auto uri = aliceAccount->getUsername();
-    auto convId = aliceAccount->startConversation();
+    auto convId = DRing::startConversation(aliceId);
 
-    auto convInfos = aliceAccount->conversationInfos(convId);
+    auto convInfos = DRing::conversationInfos(aliceId, convId);
     CPPUNIT_ASSERT(convInfos.find("mode") != convInfos.end());
     CPPUNIT_ASSERT(convInfos.find("syncing") == convInfos.end());
 
@@ -4493,10 +4511,11 @@ ConversationTest::testDoNotLoadIncorrectConversation()
                        + DIR_SEPARATOR_STR + ".git";
     fileutils::removeAll(repoGitPath, true); // This make the repository not usable
 
-    aliceAccount->loadConversations(); // Refresh. This should detect the incorrect conversations.
+    aliceAccount->convModule()
+        ->loadConversations(); // Refresh. This should detect the incorrect conversations.
 
     // the conv should be detected as invalid and added as syncing
-    convInfos = aliceAccount->conversationInfos(convId);
+    convInfos = DRing::conversationInfos(aliceId, convId);
     CPPUNIT_ASSERT(convInfos.find("mode") == convInfos.end());
     CPPUNIT_ASSERT(convInfos["syncing"] == "true");
 }
@@ -4541,13 +4560,13 @@ ConversationTest::testSyncingWhileAccepting()
     Manager::instance().sendRegister(aliceId, false); // This avoid to sync immediately
     CPPUNIT_ASSERT(bobAccount->acceptTrustRequest(aliceUri));
 
-    auto convInfos = bobAccount->conversationInfos(convId);
+    auto convInfos = DRing::conversationInfos(bobId, convId);
     CPPUNIT_ASSERT(convInfos["syncing"] == "true");
 
     Manager::instance().sendRegister(aliceId, true); // This avoid to sync immediately
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return conversationReady; }));
 
-    convInfos = bobAccount->conversationInfos(convId);
+    convInfos = DRing::conversationInfos(bobId, convId);
     CPPUNIT_ASSERT(convInfos.find("syncing") == convInfos.end());
 }
 
@@ -4591,7 +4610,7 @@ ConversationTest::testGetConversationsMembersWhileSyncing()
     Manager::instance().sendRegister(aliceId, false); // This avoid to sync immediately
     CPPUNIT_ASSERT(bobAccount->acceptTrustRequest(aliceUri));
 
-    auto members = bobAccount->getConversationMembers(convId);
+    auto members = DRing::getConversationMembers(bobId, convId);
     CPPUNIT_ASSERT(std::find_if(members.begin(),
                                 members.end(),
                                 [&](auto memberInfo) { return memberInfo["uri"] == aliceUri; })
@@ -4642,36 +4661,39 @@ ConversationTest::testRemoveContactRemoveSyncing()
     Manager::instance().sendRegister(aliceId, false); // This avoid to sync immediately
     CPPUNIT_ASSERT(bobAccount->acceptTrustRequest(aliceUri));
 
-    CPPUNIT_ASSERT(bobAccount->getConversations().size() == 1);
+    CPPUNIT_ASSERT(DRing::getConversations(bobId).size() == 1);
     bobAccount->removeContact(aliceUri, false);
 
-    CPPUNIT_ASSERT(bobAccount->getConversations().size() == 0);
+    CPPUNIT_ASSERT(DRing::getConversations(bobId).size() == 0);
 }
 
 void
 ConversationTest::testCountInteractions()
 {
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
-    auto convId = aliceAccount->startConversation();
+    auto convId = DRing::startConversation(aliceId);
     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();
-    });
+    aliceAccount->convModule()
+        ->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();
-    });
+    aliceAccount->convModule()
+        ->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();
-    });
+    aliceAccount->convModule()
+        ->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 */);
@@ -4701,7 +4723,7 @@ ConversationTest::testGetConversationMembersWithSelfOneOne()
     aliceAccount->addContact(aliceUri);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(5), [&]() { return !convId.empty(); }));
 
-    auto members = aliceAccount->getConversationMembers(convId);
+    auto members = DRing::getConversationMembers(aliceId, convId);
     CPPUNIT_ASSERT(members.size() == 1);
     CPPUNIT_ASSERT(members[0]["uri"] == aliceUri);
 }
diff --git a/test/unitTest/fileTransfer/fileTransfer.cpp b/test/unitTest/fileTransfer/fileTransfer.cpp
index 658e0a527708ca69951733eee0b1e25f1227f086..a441a53a5523ffacb9eff1c95daf4a7edc687ae0 100644
--- a/test/unitTest/fileTransfer/fileTransfer.cpp
+++ b/test/unitTest/fileTransfer/fileTransfer.cpp
@@ -481,14 +481,14 @@ FileTransferTest::testConversationFileTransfer()
         }));
     DRing::registerSignalHandlers(confHandlers);
 
-    auto convId = aliceAccount->startConversation();
+    auto convId = DRing::startConversation(aliceId);
 
-    aliceAccount->addConversationMember(convId, bobUri);
-    aliceAccount->addConversationMember(convId, carlaUri);
+    DRing::addConversationMember(aliceId, convId, bobUri);
+    DRing::addConversationMember(aliceId, convId, carlaUri);
     cv.wait_for(lk, std::chrono::seconds(60), [&]() { return requestReceived == 2; });
 
-    bobAccount->acceptConversationRequest(convId);
-    carlaAccount->acceptConversationRequest(convId);
+    DRing::acceptConversationRequest(bobId, convId);
+    DRing::acceptConversationRequest(carlaId, convId);
     cv.wait_for(lk, std::chrono::seconds(30), [&]() {
         return conversationReady == 3 && memberJoined == 2;
     });
@@ -529,7 +529,7 @@ FileTransferTest::testFileTransferInConversation()
     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
     auto bobUri = bobAccount->getUsername();
     auto aliceUri = aliceAccount->getUsername();
-    auto convId = aliceAccount->startConversation();
+    auto convId = DRing::startConversation(aliceId);
 
     std::mutex mtx;
     std::unique_lock<std::mutex> lk {mtx};
@@ -588,10 +588,10 @@ FileTransferTest::testFileTransferInConversation()
         }));
     DRing::registerSignalHandlers(confHandlers);
 
-    aliceAccount->addConversationMember(convId, bobUri);
+    DRing::addConversationMember(aliceId, convId, bobUri);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return requestReceived; }));
 
-    bobAccount->acceptConversationRequest(convId);
+    DRing::acceptConversationRequest(bobId, convId);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() {
         return conversationReady && bobJoined;
     }));
@@ -630,7 +630,7 @@ FileTransferTest::testBadSha3sumOut()
     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
     auto bobUri = bobAccount->getUsername();
     auto aliceUri = aliceAccount->getUsername();
-    auto convId = aliceAccount->startConversation();
+    auto convId = DRing::startConversation(aliceId);
 
     std::mutex mtx;
     std::unique_lock<std::mutex> lk {mtx};
@@ -698,10 +698,10 @@ FileTransferTest::testBadSha3sumOut()
             }));
     DRing::registerSignalHandlers(confHandlers);
 
-    aliceAccount->addConversationMember(convId, bobUri);
+    DRing::addConversationMember(aliceId, convId, bobUri);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return requestReceived; }));
 
-    bobAccount->acceptConversationRequest(convId);
+    DRing::acceptConversationRequest(bobId, convId);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() {
         return conversationReady && memberJoin;
     }));
@@ -746,7 +746,7 @@ FileTransferTest::testBadSha3sumIn()
     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
     auto bobUri = bobAccount->getUsername();
     auto aliceUri = aliceAccount->getUsername();
-    auto convId = aliceAccount->startConversation();
+    auto convId = DRing::startConversation(aliceId);
 
     std::mutex mtx;
     std::unique_lock<std::mutex> lk {mtx};
@@ -814,10 +814,10 @@ FileTransferTest::testBadSha3sumIn()
             }));
     DRing::registerSignalHandlers(confHandlers);
 
-    aliceAccount->addConversationMember(convId, bobUri);
+    DRing::addConversationMember(aliceId, convId, bobUri);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return requestReceived; }));
 
-    bobAccount->acceptConversationRequest(convId);
+    DRing::acceptConversationRequest(bobId, convId);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(60), [&]() {
         return conversationReady && memberJoin;
     }));
@@ -864,7 +864,7 @@ FileTransferTest::testAskToMultipleParticipants()
     auto aliceUri = aliceAccount->getUsername();
     auto bobUri = bobAccount->getUsername();
     auto carlaUri = carlaAccount->getUsername();
-    auto convId = aliceAccount->startConversation();
+    auto convId = DRing::startConversation(aliceId);
 
     std::mutex mtx;
     std::unique_lock<std::mutex> lk {mtx};
@@ -935,10 +935,10 @@ FileTransferTest::testAskToMultipleParticipants()
             }));
     DRing::registerSignalHandlers(confHandlers);
 
-    aliceAccount->addConversationMember(convId, bobUri);
+    DRing::addConversationMember(aliceId, convId, bobUri);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return requestReceived; }));
 
-    bobAccount->acceptConversationRequest(convId);
+    DRing::acceptConversationRequest(bobId, convId);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(60), [&]() {
         return conversationReady && memberJoin;
     }));
@@ -947,10 +947,10 @@ FileTransferTest::testAskToMultipleParticipants()
     conversationReady = false;
     memberJoin = false;
 
-    aliceAccount->addConversationMember(convId, carlaUri);
+    DRing::addConversationMember(aliceId, convId, carlaUri);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return requestReceived; }));
 
-    carlaAccount->acceptConversationRequest(convId);
+    DRing::acceptConversationRequest(carlaId, convId);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(60), [&]() {
         return conversationReady && memberJoin;
     }));
@@ -994,7 +994,7 @@ FileTransferTest::testCancelInTransfer()
     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
     auto bobUri = bobAccount->getUsername();
     auto aliceUri = aliceAccount->getUsername();
-    auto convId = aliceAccount->startConversation();
+    auto convId = DRing::startConversation(aliceId);
 
     std::mutex mtx;
     std::unique_lock<std::mutex> lk {mtx};
@@ -1055,10 +1055,10 @@ FileTransferTest::testCancelInTransfer()
         }));
     DRing::registerSignalHandlers(confHandlers);
 
-    aliceAccount->addConversationMember(convId, bobUri);
+    DRing::addConversationMember(aliceId, convId, bobUri);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return requestReceived; }));
 
-    bobAccount->acceptConversationRequest(convId);
+    DRing::acceptConversationRequest(bobId, convId);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() {
         return conversationReady && bobJoined;
     }));
@@ -1096,7 +1096,7 @@ FileTransferTest::testTransferInfo()
     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
     auto bobUri = bobAccount->getUsername();
     auto aliceUri = aliceAccount->getUsername();
-    auto convId = aliceAccount->startConversation();
+    auto convId = DRing::startConversation(aliceId);
 
     std::mutex mtx;
     std::unique_lock<std::mutex> lk {mtx};
@@ -1155,10 +1155,10 @@ FileTransferTest::testTransferInfo()
         }));
     DRing::registerSignalHandlers(confHandlers);
 
-    aliceAccount->addConversationMember(convId, bobUri);
+    DRing::addConversationMember(aliceId, convId, bobUri);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return requestReceived; }));
 
-    bobAccount->acceptConversationRequest(convId);
+    DRing::acceptConversationRequest(bobId, convId);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() {
         return conversationReady && bobJoined;
     }));
diff --git a/test/unitTest/syncHistory/syncHistory.cpp b/test/unitTest/syncHistory/syncHistory.cpp
index 7608518ab1fc111af3f672b14bfd2677077a4bd2..2cb28faa55009b64f325cb32d4de3ecc19cad1fa 100644
--- a/test/unitTest/syncHistory/syncHistory.cpp
+++ b/test/unitTest/syncHistory/syncHistory.cpp
@@ -111,7 +111,7 @@ SyncHistoryTest::testCreateConversationThenSync()
 {
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
     // Start conversation
-    auto convId = aliceAccount->startConversation();
+    auto convId = DRing::startConversation(aliceId);
 
     // Now create alice2
     auto aliceArchive = std::filesystem::current_path().string() + "/alice.gz";
@@ -181,7 +181,7 @@ SyncHistoryTest::testCreateConversationWithOnlineDevice()
     std::map<std::string, std::shared_ptr<DRing::CallbackWrapperBase>> confHandlers;
 
     // Start conversation now
-    auto convId = aliceAccount->startConversation();
+    auto convId = DRing::startConversation(aliceId);
     auto conversationReady = false, alice2Ready = false;
     confHandlers.insert(DRing::exportable_callback<DRing::ConversationSignal::ConversationReady>(
         [&](const std::string& accountId, const std::string& conversationId) {
@@ -213,7 +213,7 @@ void
 SyncHistoryTest::testCreateConversationWithMessagesThenAddDevice()
 {
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
-    auto convId = aliceAccount->startConversation();
+    auto convId = DRing::startConversation(aliceId);
 
     std::mutex mtx;
     std::unique_lock<std::mutex> lk {mtx};
@@ -222,7 +222,7 @@ SyncHistoryTest::testCreateConversationWithMessagesThenAddDevice()
     auto conversationReady = false;
     auto messageReceived = false;
     confHandlers.insert(DRing::exportable_callback<DRing::ConversationSignal::MessageReceived>(
-        [&](const std::string& accountId,
+        [&](const std::string& /* accountId */,
             const std::string& /* conversationId */,
             std::map<std::string, std::string> /*message*/) {
             messageReceived = true;
@@ -240,11 +240,11 @@ SyncHistoryTest::testCreateConversationWithMessagesThenAddDevice()
 
     // Start conversation
     messageReceived = false;
-    aliceAccount->sendMessage(convId, std::string("Message 1"));
+    DRing::sendMessage(aliceId, convId, std::string("Message 1"), "");
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(10), [&] { return messageReceived; }));
-    aliceAccount->sendMessage(convId, std::string("Message 2"));
+    DRing::sendMessage(aliceId, convId, std::string("Message 2"), "");
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(10), [&] { return messageReceived; }));
-    aliceAccount->sendMessage(convId, std::string("Message 3"));
+    DRing::sendMessage(aliceId, convId, std::string("Message 3"), "");
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(10), [&] { return messageReceived; }));
 
     // Now create alice2
@@ -263,7 +263,6 @@ SyncHistoryTest::testCreateConversationWithMessagesThenAddDevice()
 
     // Check if conversation is ready
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(60), [&]() { return conversationReady; }));
-    auto alice2Account = Manager::instance().getAccount<JamiAccount>(alice2Id);
     std::vector<std::map<std::string, std::string>> messages;
     confHandlers.insert(DRing::exportable_callback<DRing::ConversationSignal::ConversationLoaded>(
         [&](uint32_t,
@@ -276,7 +275,7 @@ SyncHistoryTest::testCreateConversationWithMessagesThenAddDevice()
             }
         }));
     DRing::registerSignalHandlers(confHandlers);
-    alice2Account->loadConversationMessages(convId);
+    DRing::loadConversationMessages(alice2Id, convId, "", 0);
     cv.wait_for(lk, std::chrono::seconds(30));
     DRing::unregisterSignalHandlers();
     confHandlers.clear();
@@ -294,25 +293,25 @@ SyncHistoryTest::testCreateMultipleConversationThenAddDevice()
 {
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
     // Start conversation
-    auto convId = aliceAccount->startConversation();
-    aliceAccount->sendMessage(convId, std::string("Message 1"));
-    aliceAccount->sendMessage(convId, std::string("Message 2"));
-    aliceAccount->sendMessage(convId, std::string("Message 3"));
+    auto convId = DRing::startConversation(aliceId);
+    DRing::sendMessage(aliceId, convId, std::string("Message 1"), "");
+    DRing::sendMessage(aliceId, convId, std::string("Message 2"), "");
+    DRing::sendMessage(aliceId, convId, std::string("Message 3"), "");
     std::this_thread::sleep_for(std::chrono::seconds(1));
-    auto convId2 = aliceAccount->startConversation();
-    aliceAccount->sendMessage(convId2, std::string("Message 1"));
-    aliceAccount->sendMessage(convId2, std::string("Message 2"));
-    aliceAccount->sendMessage(convId2, std::string("Message 3"));
+    auto convId2 = DRing::startConversation(aliceId);
+    DRing::sendMessage(aliceId, convId2, std::string("Message 1"), "");
+    DRing::sendMessage(aliceId, convId2, std::string("Message 2"), "");
+    DRing::sendMessage(aliceId, convId2, std::string("Message 3"), "");
     std::this_thread::sleep_for(std::chrono::seconds(1));
-    auto convId3 = aliceAccount->startConversation();
-    aliceAccount->sendMessage(convId3, std::string("Message 1"));
-    aliceAccount->sendMessage(convId3, std::string("Message 2"));
-    aliceAccount->sendMessage(convId3, std::string("Message 3"));
+    auto convId3 = DRing::startConversation(aliceId);
+    DRing::sendMessage(aliceId, convId3, std::string("Message 1"), "");
+    DRing::sendMessage(aliceId, convId3, std::string("Message 2"), "");
+    DRing::sendMessage(aliceId, convId3, std::string("Message 3"), "");
     std::this_thread::sleep_for(std::chrono::seconds(1));
-    auto convId4 = aliceAccount->startConversation();
-    aliceAccount->sendMessage(convId4, std::string("Message 1"));
-    aliceAccount->sendMessage(convId4, std::string("Message 2"));
-    aliceAccount->sendMessage(convId4, std::string("Message 3"));
+    auto convId4 = DRing::startConversation(aliceId);
+    DRing::sendMessage(aliceId, convId4, std::string("Message 1"), "");
+    DRing::sendMessage(aliceId, convId4, std::string("Message 2"), "");
+    DRing::sendMessage(aliceId, convId4, std::string("Message 3"), "");
 
     // Now create alice2
     auto aliceArchive = std::filesystem::current_path().string() + "/alice.gz";
@@ -364,7 +363,7 @@ SyncHistoryTest::testReceivesInviteThenAddDevice()
     auto uri = aliceAccount->getUsername();
 
     // Start conversation for Alice
-    auto convId = bobAccount->startConversation();
+    auto convId = DRing::startConversation(bobId);
 
     // Check that alice receives the request
     std::mutex mtx;
@@ -394,7 +393,7 @@ SyncHistoryTest::testReceivesInviteThenAddDevice()
     DRing::registerSignalHandlers(confHandlers);
 
     memberEvent = false;
-    bobAccount->addConversationMember(convId, uri);
+    DRing::addConversationMember(bobId, convId, uri);
     CPPUNIT_ASSERT(
         cv.wait_for(lk, std::chrono::seconds(30), [&] { return memberEvent && requestReceived; }));
     DRing::unregisterSignalHandlers();
@@ -453,7 +452,7 @@ SyncHistoryTest::testRemoveConversationOnAllDevices()
     std::map<std::string, std::shared_ptr<DRing::CallbackWrapperBase>> confHandlers;
 
     // Start conversation now
-    auto convId = aliceAccount->startConversation();
+    auto convId = DRing::startConversation(aliceId);
     bool alice2Ready = false;
     auto conversationReady = false, conversationRemoved = false;
     confHandlers.insert(DRing::exportable_callback<DRing::ConversationSignal::ConversationReady>(
@@ -486,7 +485,7 @@ SyncHistoryTest::testRemoveConversationOnAllDevices()
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(60), [&] {
         return alice2Ready && conversationReady;
     }));
-    aliceAccount->removeConversation(convId);
+    DRing::removeConversation(aliceId, convId);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&] { return conversationRemoved; }));
 
     DRing::unregisterSignalHandlers();
@@ -507,7 +506,7 @@ SyncHistoryTest::testSyncCreateAccountExportDeleteReimportOldBackup()
     aliceAccount->exportArchive(aliceArchive);
 
     // Start conversation
-    auto convId = aliceAccount->startConversation();
+    auto convId = DRing::startConversation(aliceId);
 
     std::mutex mtx;
     std::unique_lock<std::mutex> lk {mtx};
@@ -560,10 +559,10 @@ SyncHistoryTest::testSyncCreateAccountExportDeleteReimportOldBackup()
             }));
     DRing::registerSignalHandlers(confHandlers);
 
-    aliceAccount->addConversationMember(convId, bobUri);
+    DRing::addConversationMember(aliceId, convId, bobUri);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return requestReceived; }));
 
-    bobAccount->acceptConversationRequest(convId);
+    DRing::acceptConversationRequest(bobId, convId);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return conversationReady; }));
 
     // Wait that alice sees Bob
@@ -585,17 +584,16 @@ SyncHistoryTest::testSyncCreateAccountExportDeleteReimportOldBackup()
     conversationReady = false;
     alice2Id = Manager::instance().addAccount(details);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&] { return alice2Ready; }));
-    aliceAccount = Manager::instance().getAccount<JamiAccount>(alice2Id);
 
     // This will trigger a conversation request. Cause alice2 can't know first conversation
-    bobAccount->sendMessage(convId, std::string("hi"));
+    DRing::sendMessage(bobId, convId, std::string("hi"), "");
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&] { return requestReceived; }));
 
-    aliceAccount->acceptConversationRequest(convId);
+    DRing::acceptConversationRequest(alice2Id, convId);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&] { return conversationReady; }));
 
     messageBobReceived = 0;
-    aliceAccount->sendMessage(convId, std::string("hi"));
+    DRing::sendMessage(alice2Id, convId, std::string("hi"), "");
     cv.wait_for(lk, std::chrono::seconds(30), [&]() { return messageBobReceived == 1; });
     std::remove(aliceArchive.c_str());
     DRing::unregisterSignalHandlers();
@@ -611,7 +609,7 @@ SyncHistoryTest::testSyncCreateAccountExportDeleteReimportWithConvId()
     auto bobUri = bobAccount->getUsername();
 
     // Start conversation
-    auto convId = aliceAccount->startConversation();
+    auto convId = DRing::startConversation(aliceId);
 
     std::mutex mtx;
     std::unique_lock<std::mutex> lk {mtx};
@@ -671,10 +669,10 @@ SyncHistoryTest::testSyncCreateAccountExportDeleteReimportWithConvId()
             }));
     DRing::registerSignalHandlers(confHandlers);
 
-    aliceAccount->addConversationMember(convId, bobUri);
+    DRing::addConversationMember(aliceId, convId, bobUri);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return requestReceived; }));
 
-    bobAccount->acceptConversationRequest(convId);
+    DRing::acceptConversationRequest(bobId, convId);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return conversationReady; }));
 
     // We need to track presence to know when to sync
@@ -708,8 +706,7 @@ SyncHistoryTest::testSyncCreateAccountExportDeleteReimportWithConvId()
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&] { return conversationReady; }));
 
     messageBobReceived = 0;
-    aliceAccount = Manager::instance().getAccount<JamiAccount>(alice2Id);
-    aliceAccount->sendMessage(convId, std::string("hi"));
+    DRing::sendMessage(alice2Id, convId, std::string("hi"), "");
     cv.wait_for(lk, std::chrono::seconds(30), [&]() { return messageBobReceived == 1; });
     std::remove(aliceArchive.c_str());
     DRing::unregisterSignalHandlers();
@@ -725,7 +722,7 @@ SyncHistoryTest::testSyncCreateAccountExportDeleteReimportWithConvReq()
     auto aliceUri = aliceAccount->getUsername();
 
     // Start conversation
-    auto convId = bobAccount->startConversation();
+    auto convId = DRing::startConversation(bobId);
 
     std::mutex mtx;
     std::unique_lock<std::mutex> lk {mtx};
@@ -778,7 +775,7 @@ SyncHistoryTest::testSyncCreateAccountExportDeleteReimportWithConvReq()
             }));
     DRing::registerSignalHandlers(confHandlers);
 
-    bobAccount->addConversationMember(convId, aliceUri);
+    DRing::addConversationMember(bobId, convId, aliceUri);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return requestReceived; }));
 
     // Backup alice after startConversation with member
@@ -801,10 +798,9 @@ SyncHistoryTest::testSyncCreateAccountExportDeleteReimportWithConvReq()
     conversationReady = false;
     alice2Id = Manager::instance().addAccount(details);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&] { return alice2Ready; }));
-    aliceAccount = Manager::instance().getAccount<JamiAccount>(alice2Id);
 
     // Should get the same request as before.
-    aliceAccount->acceptConversationRequest(convId);
+    DRing::acceptConversationRequest(alice2Id, convId);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() {
         return conversationReady && messageBobReceived == 1;
     }));
@@ -883,7 +879,7 @@ SyncHistoryTest::testConversationRequestRemoved()
     auto uri = aliceAccount->getUsername();
 
     // Start conversation for Alice
-    auto convId = bobAccount->startConversation();
+    auto convId = DRing::startConversation(bobId);
 
     // Check that alice receives the request
     std::mutex mtx;
@@ -903,7 +899,7 @@ SyncHistoryTest::testConversationRequestRemoved()
             }));
     DRing::registerSignalHandlers(confHandlers);
 
-    bobAccount->addConversationMember(convId, uri);
+    DRing::addConversationMember(bobId, convId, uri);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&] { return requestReceived; }));
     DRing::unregisterSignalHandlers();
     confHandlers.clear();
@@ -946,7 +942,7 @@ SyncHistoryTest::testConversationRequestRemoved()
 
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&] { return requestReceived; }));
     // Now decline trust request, this should trigger ConversationRequestDeclined both sides for Alice
-    aliceAccount->declineConversationRequest(convId);
+    DRing::declineConversationRequest(aliceId, convId);
 
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&] {
         return requestDeclined && requestDeclined2;