From 6c0a0aaf6a9a3649bdba46efb815d707bbcbea5b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=A9bastien=20Blin?=
 <sebastien.blin@savoirfairelinux.com>
Date: Mon, 19 Jul 2021 15:36:04 -0400
Subject: [PATCH] jamiaccount: extract conversation's related code from class

This patches introduces two new concepts in order to reduce the code
of JamiAccount.

ChannelHandlers to manages protocols logic. The idea of this class
is to handle channels per protocol, accept and reject it.
AccountModule, to be able to separate logic between call managements
datatransfer, config and conversation but give an interface to
detect when some events should occurs.

Change-Id: I34ff07852c06d7266411f1ffb32b71a1834aba4f
GitLab: #603
---
 src/account.cpp                              |    9 -
 src/account.h                                |   29 +-
 src/call.cpp                                 |    4 +-
 src/call_factory.cpp                         |    2 +-
 src/client/conversation_interface.cpp        |   43 +-
 src/client/datatransfer.cpp                  |    3 +-
 src/data_transfer.cpp                        |    2 +-
 src/im/message_engine.cpp                    |    2 +-
 src/jamidht/CMakeLists.txt                   |    5 +
 src/jamidht/Makefile.am                      |    5 +
 src/jamidht/account_manager.cpp              |  115 +-
 src/jamidht/account_manager.h                |   14 -
 src/jamidht/archive_account_manager.cpp      |   12 +-
 src/jamidht/channel_handler.h                |   67 +
 src/jamidht/connectionmanager.cpp            |    2 +-
 src/jamidht/conversation_channel_handler.cpp |   71 +
 src/jamidht/conversation_channel_handler.h   |   66 +
 src/jamidht/conversation_module.cpp          | 1479 +++++++++++++
 src/jamidht/conversation_module.h            |  343 +++
 src/jamidht/jamiaccount.cpp                  | 1967 ++++--------------
 src/jamidht/jamiaccount.h                    |  194 +-
 src/jamidht/p2p.cpp                          |    9 +-
 src/plugin/chatservicesmanager.cpp           |    5 +-
 src/sip/sipaccountbase.cpp                   |  221 +-
 src/sip/sipaccountbase.h                     |    6 -
 src/uri.cpp                                  |    2 +
 src/uri.h                                    |    1 +
 test/unitTest/conversation/conversation.cpp  |  490 ++---
 test/unitTest/fileTransfer/fileTransfer.cpp  |   50 +-
 test/unitTest/syncHistory/syncHistory.cpp    |   92 +-
 30 files changed, 2917 insertions(+), 2393 deletions(-)
 create mode 100644 src/jamidht/channel_handler.h
 create mode 100644 src/jamidht/conversation_channel_handler.cpp
 create mode 100644 src/jamidht/conversation_channel_handler.h
 create mode 100644 src/jamidht/conversation_module.cpp
 create mode 100644 src/jamidht/conversation_module.h

diff --git a/src/account.cpp b/src/account.cpp
index 7f69976d31..429346e152 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 6b596c9334..f7b2e5be47 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 a151c0bff9..8d8ef5e175 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 a846e42336..f2e532823f 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 b420d703fe..a686d74a43 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 7383d6e718..a8e6b7934b 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 d41eabfc59..f0fa11f8b6 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 086797a34d..42585f3e48 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 67e9b4c3ea..47209bd934 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 48c6361703..b0ee274bd1 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 8796c1f3c4..cf3c1178a0 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 1fe1fc379f..a74847ab54 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 27cb4b8cae..6f44f57c9a 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 0000000000..a0c448f059
--- /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 24a10502c0..51b64b4aed 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 0000000000..4d7edbd081
--- /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 0000000000..455bc0790a
--- /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 0000000000..a863e3076f
--- /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 0000000000..ff8209520e
--- /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 2138895239..1dee510574 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 15675e2b65..0338817b4a 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 8edef02a9c..c3fddd403d 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 6d8b48ed43..f10bca7533 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 f5cee89154..3a8dc90784 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 0f6bca8ad6..e5d571fe71 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 decac06bab..145bfb9fdd 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 f62e579c43..9fd9d866b4 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 7114437fcd..a6214b6328 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 658e0a5277..a441a53a55 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 7608518ab1..2cb28faa55 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;
-- 
GitLab