diff --git a/src/account.h b/src/account.h
index 95df7ecab3f56a925aba058336c635d2fc1233c7..6911100af93dffe4cae0d72163de106f42110251 100644
--- a/src/account.h
+++ b/src/account.h
@@ -163,7 +163,8 @@ public:
      * @return a token to query the message status
      */
     virtual uint64_t sendTextMessage(const std::string& /*to*/,
-                                     const std::map<std::string, std::string>& /*payloads*/)
+                                     const std::map<std::string, std::string>& /*payloads*/,
+                                     uint64_t /*refreshToken*/ = 0)
     {
         return 0;
     }
diff --git a/src/im/message_engine.cpp b/src/im/message_engine.cpp
index 2b7bf384e6255eb93398c189da16711185ccef60..a7340fe8786dc828729be907f761e4818b3be9cb 100644
--- a/src/im/message_engine.cpp
+++ b/src/im/message_engine.cpp
@@ -43,7 +43,9 @@ MessageEngine::MessageEngine(SIPAccountBase& acc, const std::string& path)
 }
 
 MessageToken
-MessageEngine::sendMessage(const std::string& to, const std::map<std::string, std::string>& payloads)
+MessageEngine::sendMessage(const std::string& to,
+                           const std::map<std::string, std::string>& payloads,
+                           uint64_t refreshToken)
 {
     if (payloads.empty() or to.empty())
         return 0;
@@ -51,12 +53,21 @@ 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, JAMI_ID_MAX_VAL}(account_.rand);
-        } while (peerMessages.find(token) != peerMessages.end());
-        auto m = peerMessages.emplace(token, Message {});
-        m.first->second.to = to;
-        m.first->second.payloads = payloads;
+        auto previousIt = peerMessages.find(refreshToken);
+        if (previousIt != peerMessages.end() && previousIt->second.status != MessageStatus::SENT) {
+            JAMI_DBG("[message %ld] Replace content", refreshToken);
+            token = refreshToken;
+            previousIt->second.to = to;
+            previousIt->second.payloads = payloads;
+        } else {
+            do {
+                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;
+            m.first->second.payloads = payloads;
+        }
         save_();
     }
     runOnMainThread([this, to]() { retrySend(to); });
@@ -108,7 +119,7 @@ MessageEngine::retrySend(const std::string& peer, bool retryOnTimeout)
                 p.to,
                 std::to_string(p.token),
                 (int) DRing::Account::MessageStates::SENDING);
-        account_.sendTextMessage(p.to, p.payloads, p.token, retryOnTimeout);
+        account_.sendMessage(p.to, p.payloads, p.token, retryOnTimeout);
     }
 }
 
diff --git a/src/im/message_engine.h b/src/im/message_engine.h
index 962eaaa5bd42ea16ecc55160c231ea6aef3d9bd7..2057db07532b801f48f036d1d7c707b23837fed4 100644
--- a/src/im/message_engine.h
+++ b/src/im/message_engine.h
@@ -42,7 +42,8 @@ public:
     MessageEngine(SIPAccountBase&, const std::string& path);
 
     MessageToken sendMessage(const std::string& to,
-                             const std::map<std::string, std::string>& payloads);
+                             const std::map<std::string, std::string>& payloads,
+                             uint64_t refreshToken);
 
     MessageStatus getStatus(MessageToken t) const;
 
diff --git a/src/jamidht/conversation_module.cpp b/src/jamidht/conversation_module.cpp
index bf347d10c5a49f6174387a0e468e497edda2a217..2d13bdde217693b0f932d7063b72e38e29182aa7 100644
--- a/src/jamidht/conversation_module.cpp
+++ b/src/jamidht/conversation_module.cpp
@@ -268,6 +268,7 @@ public:
     // Replay conversations (after erasing/re-adding)
     std::mutex replayMtx_;
     std::map<std::string, std::vector<std::map<std::string, std::string>>> replay_;
+    std::map<std::string, uint64_t> refreshMessage;
 };
 
 ConversationModule::Impl::Impl(std::weak_ptr<JamiAccount>&& account,
@@ -467,7 +468,8 @@ ConversationModule::Impl::fetchNewCommits(const std::string& peer,
                   accountId_.c_str(),
                   conversationId.c_str());
         sendMsgCb_(peer,
-                   std::map<std::string, std::string> {{"application/invite", conversationId}});
+                   std::map<std::string, std::string> {{"application/invite", conversationId}},
+                   0);
     }
 }
 
@@ -769,8 +771,10 @@ ConversationModule::Impl::sendMessageNotification(const Conversation& conversati
     const auto text = Json::writeString(builder, message);
     for (const auto& member : conversation.memberUris(sync ? "" : username_)) {
         // Announce to all members that a new message is sent
-        sendMsgCb_(member,
-                   std::map<std::string, std::string> {{"application/im-gitmessage-id", text}});
+        refreshMessage[member] = sendMsgCb_(member,
+                                            std::map<std::string, std::string> {
+                                                {"application/im-gitmessage-id", text}},
+                                            refreshMessage[member]);
     }
 }
 
@@ -1075,7 +1079,7 @@ ConversationModule::onNeedConversationRequest(const std::string& from,
         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));
+        pimpl_->sendMsgCb_(from, std::move(invite), 0);
     }
 }
 
@@ -1499,7 +1503,8 @@ ConversationModule::onNewCommit(const std::string& peer,
                   conversationId.c_str());
         pimpl_->sendMsgCb_(peer,
                            std::map<std::string, std::string> {
-                               {"application/invite", conversationId}});
+                               {"application/invite", conversationId}},
+                           0);
         return;
     }
     JAMI_DBG("[Account %s] on new commit notification from %s, for %s, commit %s",
@@ -1532,7 +1537,7 @@ ConversationModule::addConversationMember(const std::string& conversationId,
         // we should not forbid new invites
         auto invite = it->second->generateInvitation();
         lk.unlock();
-        pimpl_->sendMsgCb_(contactUri, std::move(invite));
+        pimpl_->sendMsgCb_(contactUri, std::move(invite), 0);
         return;
     }
 
@@ -1550,7 +1555,7 @@ ConversationModule::addConversationMember(const std::string& conversationId,
                                 if (sendRequest) {
                                     auto invite = it->second->generateInvitation();
                                     lk.unlock();
-                                    pimpl_->sendMsgCb_(contactUri, std::move(invite));
+                                    pimpl_->sendMsgCb_(contactUri, std::move(invite), 0);
                                 }
                             }
                         }
diff --git a/src/jamidht/conversation_module.h b/src/jamidht/conversation_module.h
index ac5d947a5a42f45460796e7d9b99fc3e95f4de4b..a4e4d4ae4fce3a824e593edaf160d843615260e1 100644
--- a/src/jamidht/conversation_module.h
+++ b/src/jamidht/conversation_module.h
@@ -40,7 +40,8 @@ struct SyncMsg
 
 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 SengMsgCb
+    = std::function<uint64_t(const std::string&, std::map<std::string, std::string>&&, uint64_t)>;
 using NeedsSyncingCb = std::function<void()>;
 using UpdateConvReq = std::function<void(const std::string&, const std::string&, bool)>;
 
diff --git a/src/jamidht/jamiaccount.cpp b/src/jamidht/jamiaccount.cpp
index 4e1c11ffc2ce5c155b386a165cca9f2d6f83c18e..293593e4e69b0d09a9658ce13452e6bf62f63b08 100644
--- a/src/jamidht/jamiaccount.cpp
+++ b/src/jamidht/jamiaccount.cpp
@@ -2350,11 +2350,11 @@ JamiAccount::convModule()
                         shared->syncModule()->syncWithConnected();
                 });
             },
-            [this](auto&& uri, auto&& msg) {
-                runOnMainThread([w = weak(), uri, msg] {
-                    if (auto shared = w.lock())
-                        shared->sendTextMessage(uri, msg);
-                });
+            [this](auto&& uri, auto&& msg, auto token = 0) {
+                // No need to retrigger, sendTextMessage will call
+                // messageEngine_.sendMessage, already retriggering on
+                // main thread.
+                return sendTextMessage(uri, msg, token);
             },
             [this](const auto& convId, const auto& deviceId, auto&& cb) {
                 runOnMainThread([w = weak(), convId, deviceId, cb = std::move(cb)] {
@@ -3147,7 +3147,8 @@ JamiAccount::forEachDevice(const dht::InfoHash& to,
 
 uint64_t
 JamiAccount::sendTextMessage(const std::string& to,
-                             const std::map<std::string, std::string>& payloads)
+                             const std::map<std::string, std::string>& payloads,
+                             uint64_t refreshToken)
 {
     Uri uri(to);
     if (uri.scheme() == Uri::Scheme::SWARM) {
@@ -3166,15 +3167,15 @@ JamiAccount::sendTextMessage(const std::string& to,
         JAMI_ERR("Multi-part im is not supported yet by JamiAccount");
         return 0;
     }
-    return SIPAccountBase::sendTextMessage(toUri, payloads);
+    return SIPAccountBase::sendTextMessage(toUri, payloads, refreshToken);
 }
 
 void
-JamiAccount::sendTextMessage(const std::string& to,
-                             const std::map<std::string, std::string>& payloads,
-                             uint64_t token,
-                             bool retryOnTimeout,
-                             bool onlyConnected)
+JamiAccount::sendMessage(const std::string& to,
+                         const std::map<std::string, std::string>& payloads,
+                         uint64_t token,
+                         bool retryOnTimeout,
+                         bool onlyConnected)
 {
     std::string toUri;
     try {
@@ -3640,7 +3641,7 @@ JamiAccount::sendInstantMessage(const std::string& convId,
         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);
+        sendMessage(uri, msg, token, false, true);
     }
 }
 
diff --git a/src/jamidht/jamiaccount.h b/src/jamidht/jamiaccount.h
index d47867f14bb6cce5580c2e90a88342ec0106d820..f9eb1f832a019b23564d5797a7ac748908ff344e 100644
--- a/src/jamidht/jamiaccount.h
+++ b/src/jamidht/jamiaccount.h
@@ -332,13 +332,14 @@ public:
     std::map<std::string, std::string> getContactDetails(const std::string& uri) const;
 
     void sendTrustRequest(const std::string& to, const std::vector<uint8_t>& payload);
-    void sendTextMessage(const std::string& to,
-                         const std::map<std::string, std::string>& payloads,
-                         uint64_t id,
-                         bool retryOnTimeout = true,
-                         bool onlyConnected = false) override;
+    void sendMessage(const std::string& to,
+                     const std::map<std::string, std::string>& payloads,
+                     uint64_t id,
+                     bool retryOnTimeout = true,
+                     bool onlyConnected = false) override;
     uint64_t sendTextMessage(const std::string& to,
-                             const std::map<std::string, std::string>& payloads) override;
+                             const std::map<std::string, std::string>& payloads,
+                             uint64_t refreshToken = 0) override;
     void sendInstantMessage(const std::string& convId,
                             const std::map<std::string, std::string>& msg);
     void onIsComposing(const std::string& conversationId, const std::string& peer, bool isWriting);
diff --git a/src/sip/sipaccount.cpp b/src/sip/sipaccount.cpp
index 7654fbe0ba8b167cac49d4f655c05c492c2a5886..08e55cb6cf0cba1cca7fa79e5e75bddb542013e7 100644
--- a/src/sip/sipaccount.cpp
+++ b/src/sip/sipaccount.cpp
@@ -2147,11 +2147,11 @@ static pjsip_accept_hdr* im_create_accept(pj_pool_t *pool)
 #endif
 
 void
-SIPAccount::sendTextMessage(const std::string& to,
-                            const std::map<std::string, std::string>& payloads,
-                            uint64_t id,
-                            bool,
-                            bool)
+SIPAccount::sendMessage(const std::string& to,
+                        const std::map<std::string, std::string>& payloads,
+                        uint64_t id,
+                        bool,
+                        bool)
 {
     if (to.empty() or payloads.empty()) {
         JAMI_WARN("No sender or payload");
diff --git a/src/sip/sipaccount.h b/src/sip/sipaccount.h
index 5cb2ad4df7cd04a8ee9368c6e6bdf4b6c30bbc73..525cda54d9731bf6b165c6b1887028a4e3052b0a 100644
--- a/src/sip/sipaccount.h
+++ b/src/sip/sipaccount.h
@@ -442,11 +442,11 @@ public:
 
     void onRegister(pjsip_regc_cbparam* param);
 
-    virtual void sendTextMessage(const std::string& to,
-                                 const std::map<std::string, std::string>& payloads,
-                                 uint64_t id,
-                                 bool retryOnTimeout = true,
-                                 bool onlyConnected = false) override;
+    virtual void sendMessage(const std::string& to,
+                             const std::map<std::string, std::string>& payloads,
+                             uint64_t id,
+                             bool retryOnTimeout = true,
+                             bool onlyConnected = false) override;
 
     void connectivityChanged() override;
 
diff --git a/src/sip/sipaccountbase.h b/src/sip/sipaccountbase.h
index b9b245fcd9281949f821dd80abf6533fc6d15fb7..0d9c4a459a5e6ac250f75860f2dc2a1d5d17842b 100644
--- a/src/sip/sipaccountbase.h
+++ b/src/sip/sipaccountbase.h
@@ -233,23 +233,30 @@ public:
      * file, that can be used directly by PJSIP to initialize
      * an alternate UDP transport.
      */
-    std::string getStunServer() const { return stunServer_; }
+    std::string getStunServer() const
+    {
+        return stunServer_;
+    }
 
-    void setStunServer(const std::string& srv) { stunServer_ = srv; }
+    void setStunServer(const std::string& srv)
+    {
+        stunServer_ = srv;
+    }
 
     IceTransportOptions getIceOptions() const noexcept;
 
-    virtual void sendTextMessage(const std::string& to,
-                                 const std::map<std::string, std::string>& payloads,
-                                 uint64_t id,
-                                 bool retryOnTimeout = true,
-                                 bool onlyConnected = false)
+    virtual void sendMessage(const std::string& to,
+                             const std::map<std::string, std::string>& payloads,
+                             uint64_t id,
+                             bool retryOnTimeout = true,
+                             bool onlyConnected = false)
         = 0;
 
     virtual uint64_t sendTextMessage(const std::string& to,
-                                     const std::map<std::string, std::string>& payloads) override
+                                     const std::map<std::string, std::string>& payloads,
+                                     uint64_t refreshToken = 0) override
     {
-        return messageEngine_.sendMessage(to, payloads);
+        return messageEngine_.sendMessage(to, payloads, refreshToken);
     }
 
     im::MessageStatus getMessageStatus(uint64_t id) const override
@@ -257,7 +264,10 @@ public:
         return messageEngine_.getStatus(id);
     }
 
-    bool cancelMessage(uint64_t id) override { return messageEngine_.cancel(id); }
+    bool cancelMessage(uint64_t id) override
+    {
+        return messageEngine_.cancel(id);
+    }
 
     virtual void onTextMessage(const std::string& id,
                                const std::string& from,