diff --git a/contrib/src/dhtnet/package.json b/contrib/src/dhtnet/package.json
index a1c2a86b0e823d2b0f07f485781e466dafba5222..989bd2d2accb2434e43c66b354763d625e6d790c 100644
--- a/contrib/src/dhtnet/package.json
+++ b/contrib/src/dhtnet/package.json
@@ -1,6 +1,6 @@
 {
     "name": "dhtnet",
-    "version": "738aedb0860d098f6ba7260a70a0d9dc8667d735",
+    "version": "5aec410067947f33089004d7ef639015822c4f16",
     "url": "https://review.jami.net/plugins/gitiles/dhtnet/+archive/__VERSION__.tar.gz",
     "deps": [
         "opendht",
diff --git a/contrib/src/dhtnet/rules.mak b/contrib/src/dhtnet/rules.mak
index 0d72ad0cee43d7458fee433a9275436c956bd731..35a2225d61311eab9d89fc2629f21df42a6a1fcd 100644
--- a/contrib/src/dhtnet/rules.mak
+++ b/contrib/src/dhtnet/rules.mak
@@ -1,5 +1,5 @@
 # DHTNET
-DHTNET_VERSION := 738aedb0860d098f6ba7260a70a0d9dc8667d735
+DHTNET_VERSION := 5aec410067947f33089004d7ef639015822c4f16
 DHTNET_URL := https://review.jami.net/plugins/gitiles/dhtnet/+archive/$(DHTNET_VERSION).tar.gz
 
 PKGS += dhtnet
diff --git a/src/jamidht/jamiaccount.cpp b/src/jamidht/jamiaccount.cpp
index 35f744bbfb192b0c611e605cd91441ab97851666..e413365074b4c7dc65491899fe5c3d981e9dfcd2 100644
--- a/src/jamidht/jamiaccount.cpp
+++ b/src/jamidht/jamiaccount.cpp
@@ -111,7 +111,7 @@ static constexpr const char MIME_TYPE_IM_COMPOSING[] {"application/im-iscomposin
 static constexpr const char MIME_TYPE_INVITE_JSON[] {"application/invite+json"};
 static constexpr const char DEVICE_ID_PATH[] {"ring_device"};
 static constexpr std::chrono::steady_clock::duration COMPOSING_TIMEOUT {std::chrono::seconds(12)};
-static constexpr auto TREATED_PATH = "treatedImMessages";
+static constexpr auto TREATED_PATH = "treatedImMessages"sv;
 
 struct PendingConfirmation
 {
@@ -274,6 +274,7 @@ JamiAccount::JamiAccount(const std::string& accountId)
     , dataPath_(cachePath_ / "values")
     , certStore_ {std::make_unique<dhtnet::tls::CertificateStore>(idPath_, Logger::dhtLogger())}
     , dht_(new dht::DhtRunner)
+    , treatedMessages_(cachePath_ / TREATED_PATH)
     , connectionManager_ {}
     , nonSwarmTransferManager_()
 {}
@@ -1812,7 +1813,6 @@ JamiAccount::doRegister_()
         if (not accountManager_ or not accountManager_->getInfo())
             throw std::runtime_error("No identity configured for this account.");
 
-        loadTreatedMessages();
         if (dht_->isRunning()) {
             JAMI_ERR("[Account %s] DHT already running (stopping it first).",
                      getAccountID().c_str());
@@ -2125,8 +2125,7 @@ JamiAccount::doRegister_()
         auto inboxDeviceKey = dht::InfoHash::get(
             "inbox:" + accountManager_->getInfo()->devicePk->getId().toString());
         dht_->listen<dht::ImMessage>(inboxDeviceKey, [this, inboxDeviceKey](dht::ImMessage&& v) {
-            auto msgId = to_hex_string(v.id);
-            if (isMessageTreated(msgId))
+            if (isMessageTreated(v.id))
                 return true;
             accountManager_->onPeerMessage(
                 *v.owner,
@@ -2134,7 +2133,7 @@ JamiAccount::doRegister_()
                 [this,
                  v,
                  inboxDeviceKey,
-                 msgId](const std::shared_ptr<dht::crypto::Certificate>& cert,
+                 msgId = to_hex_string(v.id)](const std::shared_ptr<dht::crypto::Certificate>& cert,
                         const dht::InfoHash& peer_account) {
                     auto now = clock::to_time_t(clock::now());
                     std::string datatype = utf8_make_valid(v.datatype);
@@ -2526,81 +2525,11 @@ JamiAccount::getCertificatesByStatus(dhtnet::tls::TrustStore::PermissionStatus s
     return {};
 }
 
-template<typename ID = dht::Value::Id>
-std::set<ID, std::less<>>
-loadIdList(const std::filesystem::path& path)
-{
-    std::set<ID, std::less<>> ids;
-    std::ifstream file(path);
-    if (!file.is_open()) {
-        JAMI_DBG("Could not load %s", path.c_str());
-        return ids;
-    }
-    std::string line;
-    while (std::getline(file, line)) {
-        if constexpr (std::is_same<ID, std::string>::value) {
-            ids.emplace(std::move(line));
-        } else if constexpr (std::is_integral<ID>::value) {
-            ID vid;
-            if (auto [p, ec] = std::from_chars(line.data(), line.data() + line.size(), vid, 16);
-                ec == std::errc()) {
-                ids.emplace(vid);
-            }
-        }
-    }
-    return ids;
-}
-
-template<typename List = std::set<dht::Value::Id>>
-void
-saveIdList(const std::filesystem::path& path, const List& ids)
-{
-    std::ofstream file(path, std::ios::trunc | std::ios::binary);
-    if (!file.is_open()) {
-        JAMI_ERR("Could not save to %s", path.c_str());
-        return;
-    }
-    for (auto& c : ids)
-        file << std::hex << c << "\n";
-}
-
-void
-JamiAccount::loadTreatedMessages()
-{
-    std::lock_guard lock(messageMutex_);
-    auto path = cachePath_ / TREATED_PATH;
-    treatedMessages_ = loadIdList<std::string>(path.string());
-    if (treatedMessages_.empty()) {
-        auto messages = loadIdList(path.string());
-        for (const auto& m : messages)
-            treatedMessages_.emplace(to_hex_string(m));
-    }
-}
-
-void
-JamiAccount::saveTreatedMessages() const
-{
-    dht::ThreadPool::io().run([w = weak()]() {
-        if (auto sthis = w.lock()) {
-            auto& this_ = *sthis;
-            std::lock_guard lock(this_.messageMutex_);
-            dhtnet::fileutils::check_dir(this_.cachePath_);
-            saveIdList<decltype(this_.treatedMessages_)>(this_.cachePath_ / TREATED_PATH,
-                                                         this_.treatedMessages_);
-        }
-    });
-}
-
 bool
-JamiAccount::isMessageTreated(std::string_view id)
+JamiAccount::isMessageTreated(dht::Value::Id id)
 {
     std::lock_guard lock(messageMutex_);
-    auto res = treatedMessages_.emplace(id);
-    if (res.second) {
-        saveTreatedMessages();
-        return false;
-    }
-    return true;
+    return treatedMessages_.add(id);
 }
 
 std::map<std::string, std::string>
@@ -2620,7 +2549,7 @@ JamiAccount::getKnownDevices() const
 
 void
 JamiAccount::loadCachedUrl(const std::string& url,
-                           const std::string& cachePath,
+                           const std::filesystem::path& cachePath,
                            const std::chrono::seconds& cacheDuration,
                            std::function<void(const dht::http::Response& response)> cb)
 {
@@ -2650,14 +2579,10 @@ JamiAccount::loadCachedUrl(const std::string& url,
                                                     (const uint8_t*) response.body.data(),
                                                     response.body.size(),
                                                     0600);
-                                JAMI_DBG("Cached result to '%.*s'",
-                                         (int) cachePath.size(),
-                                         cachePath.c_str());
+                                JAMI_LOG("Cached result to '{}'", cachePath);
                             } catch (const std::exception& ex) {
-                                JAMI_WARN("Failed to save result to %.*s: %s",
-                                          (int) cachePath.size(),
-                                          cachePath.c_str(),
-                                          ex.what());
+                                JAMI_WARNING("Failed to save result to '{}': {}",
+                                          cachePath, ex.what());
                             }
                         } else {
                             JAMI_WARN("Failed to download url");
@@ -2684,7 +2609,7 @@ JamiAccount::loadCachedProxyServer(std::function<void(const std::string& proxy)>
             cb(getDhtProxyServer(conf.proxyServer));
         } else {
             loadCachedUrl(conf.proxyListUrl,
-                          (cachePath_ / "dhtproxylist").string(),
+                          cachePath_ / "dhtproxylist",
                           std::chrono::hours(24 * 3),
                           [w = weak(), cb = std::move(cb)](const dht::http::Response& response) {
                               if (auto sthis = w.lock()) {
@@ -3294,11 +3219,9 @@ JamiAccount::sendMessage(const std::string& to,
                                    << "] Received text message reply";
 
                         // add treated message
-                        auto res = treatedMessages_.emplace(to_hex_string(msg.id));
-                        if (!res.second)
+                        if (!treatedMessages_.add(msg.id))
                             return true;
                     }
-                    saveTreatedMessages();
 
                     // report message as confirmed received
                     {
diff --git a/src/jamidht/jamiaccount.h b/src/jamidht/jamiaccount.h
index 37bb91b371daf61ec27fb7185f932330d9699813..24107994a0bdae8bc9ff99d52ea42b55a9bfe745 100644
--- a/src/jamidht/jamiaccount.h
+++ b/src/jamidht/jamiaccount.h
@@ -50,6 +50,7 @@
 #include <dhtnet/connectionmanager.h>
 #include <dhtnet/upnp/mapping.h>
 #include <dhtnet/ip_utils.h>
+#include <dhtnet/fileutils.h>
 
 #include <opendht/dhtrunner.h>
 #include <opendht/default_types.h>
@@ -377,7 +378,7 @@ public:
     /// \return true if the given DHT message identifier has been treated
     /// \note if message has not been treated yet this method store this id and returns true at
     /// further calls
-    bool isMessageTreated(std::string_view id);
+    bool isMessageTreated(dht::Value::Id id);
 
     std::shared_ptr<dht::DhtRunner> dht() { return dht_; }
 
@@ -672,9 +673,6 @@ private:
                                                             const std::filesystem::path& path,
                                                             const std::string& name);
 
-    void loadTreatedMessages();
-    void saveTreatedMessages() const;
-
     void replyToIncomingIceMsg(const std::shared_ptr<SIPCall>&,
                                const std::shared_ptr<IceTransport>&,
                                const std::shared_ptr<IceTransport>&,
@@ -683,7 +681,7 @@ private:
                                const dht::InfoHash& from);
 
     void loadCachedUrl(const std::string& url,
-                       const std::string& cachePath,
+                       const std::filesystem::path& cachePath,
                        const std::chrono::seconds& cacheDuration,
                        std::function<void(const dht::http::Response& response)>);
 
@@ -715,7 +713,7 @@ private:
 
     mutable std::mutex messageMutex_ {};
     std::map<dht::Value::Id, PendingMessage> sentMessages_;
-    std::set<std::string, std::less<>> treatedMessages_ {};
+    dhtnet::fileutils::IdList treatedMessages_;
 
     /* tracked buddies presence */
     mutable std::mutex buddyInfoMtx;
diff --git a/src/sip/sipvoiplink.cpp b/src/sip/sipvoiplink.cpp
index d6032f1f364c5fa53bb0b7cb64c87dc0e7bbc2a8..9ed6532ccdbbbd29d326c7383fba2edf320f999f 100644
--- a/src/sip/sipvoiplink.cpp
+++ b/src/sip/sipvoiplink.cpp
@@ -348,8 +348,9 @@ transaction_request_cb(pjsip_rx_data* rdata)
                 if (not id.empty()) {
                     try {
                         // Mark message as treated
+                        auto intid = from_hex_string(id);
                         auto acc = std::dynamic_pointer_cast<JamiAccount>(account);
-                        if (acc and acc->isMessageTreated(id))
+                        if (acc and acc->isMessageTreated(intid))
                             return PJ_FALSE;
                     } catch (...) {
                     }