diff --git a/bin/dbus/cx.ring.Ring.ConfigurationManager.xml b/bin/dbus/cx.ring.Ring.ConfigurationManager.xml
index 88543d88d4c5337d23b1196c15a55a89f58a700a..a54d9b27bbe6656e05d4d6f4cc6a7dbb3bf661be 100644
--- a/bin/dbus/cx.ring.Ring.ConfigurationManager.xml
+++ b/bin/dbus/cx.ring.Ring.ConfigurationManager.xml
@@ -610,6 +610,40 @@
            </arg>
        </signal>
 
+       <method name="setIsComposing" tp:name-for-bindings="setIsComposing">
+           <tp:added version="7.9.0"/>
+           <tp:docstring>
+             <p>Sends and update composing indication for a given contact/conversation</p>
+           </tp:docstring>
+           <arg type="s" name="accountId" direction="in">
+             <tp:docstring>
+               The account ID
+             </tp:docstring>
+           </arg>
+           <arg type="s" name="contactId" direction="in">
+             <tp:docstring>
+               The contact ID
+             </tp:docstring>
+           </arg>
+           <arg type="b" name="isComposing" direction="in">
+             <tp:docstring>
+               True is the user is composing a message, false otherwise
+             </tp:docstring>
+           </arg>
+       </method>
+
+       <signal name="composingStatusChanged" tp:name-for-bindings="composingStatusChanged">
+           <tp:added version="7.9.0"/>
+           <tp:docstring>
+               Notify clients that a message composition status changed
+           </tp:docstring>
+           <arg type="s" name="accountId"/>
+           <arg type="s" name="contactId"/>
+           <arg type="i" name="status">
+              <tp:docstring>The new status of the message, 0 for Idle, 1 for Active</tp:docstring>
+           </arg>
+       </signal>
+
        <method name="setVolume" tp:name-for-bindings="setVolume">
            <tp:docstring>
              <p>Sets the volume using a linear scale [0,100].</p>
diff --git a/bin/dbus/dbusclient.cpp b/bin/dbus/dbusclient.cpp
index f4970075e38d35705d8534d08d5eee73ad7aebbf..a56f974941c570c62343d5de680d84e301c797b8 100644
--- a/bin/dbus/dbusclient.cpp
+++ b/bin/dbus/dbusclient.cpp
@@ -177,6 +177,7 @@ DBusClient::initLibrary(int flags)
         exportable_callback<ConfigurationSignal::Error>(bind(&DBusConfigurationManager::errorAlert, confM, _1)),
         exportable_callback<ConfigurationSignal::IncomingAccountMessage>(bind(&DBusConfigurationManager::incomingAccountMessage, confM, _1, _2, _3 )),
         exportable_callback<ConfigurationSignal::AccountMessageStatusChanged>(bind(&DBusConfigurationManager::accountMessageStatusChanged, confM, _1, _2, _3, _4 )),
+        exportable_callback<ConfigurationSignal::ComposingStatusChanged>(bind(&DBusConfigurationManager::composingStatusChanged, confM, _1, _2, _3 )),
         exportable_callback<ConfigurationSignal::IncomingTrustRequest>(bind(&DBusConfigurationManager::incomingTrustRequest, confM, _1, _2, _3, _4 )),
         exportable_callback<ConfigurationSignal::ContactAdded>(bind(&DBusConfigurationManager::contactAdded, confM, _1, _2, _3 )),
         exportable_callback<ConfigurationSignal::ContactRemoved>(bind(&DBusConfigurationManager::contactRemoved, confM, _1, _2, _3 )),
diff --git a/bin/dbus/dbusconfigurationmanager.cpp b/bin/dbus/dbusconfigurationmanager.cpp
index b71da1af5a1b174b32368ec6d5ad81aa030bd608..068fcfa21cf3df7270e123ecfbfb3800401496b4 100644
--- a/bin/dbus/dbusconfigurationmanager.cpp
+++ b/bin/dbus/dbusconfigurationmanager.cpp
@@ -192,6 +192,12 @@ DBusConfigurationManager::cancelMessage(const std::string& accountID, const uint
     return DRing::cancelMessage(accountID, id);
 }
 
+void
+DBusConfigurationManager::setIsComposing(const std::string& accountID, const std::string& to, const bool& isWriting)
+{
+    DRing::setIsComposing(accountID, to, isWriting);
+}
+
 auto
 DBusConfigurationManager::getTlsDefaultSettings() -> decltype(DRing::getTlsDefaultSettings())
 {
diff --git a/bin/dbus/dbusconfigurationmanager.h b/bin/dbus/dbusconfigurationmanager.h
index cad216eb05f979934b9af5bf0974d45c5f4b0a68..18b72829bd3160616f5959e3a2c6091a0703da18 100644
--- a/bin/dbus/dbusconfigurationmanager.h
+++ b/bin/dbus/dbusconfigurationmanager.h
@@ -88,6 +88,7 @@ class DRING_PUBLIC DBusConfigurationManager :
         int getMessageStatus(const uint64_t& id);
         int getMessageStatus(const std::string& accountID, const uint64_t& id);
         bool cancelMessage(const std::string& accountID, const uint64_t& messageId);
+        void setIsComposing(const std::string& accountID, const std::string& to, const bool& isWriting);
         std::map<std::string, std::string> getTlsDefaultSettings();
         std::vector<std::string> getSupportedCiphers(const std::string& accountID);
         std::vector<unsigned> getCodecList();
diff --git a/bin/jni/configurationmanager.i b/bin/jni/configurationmanager.i
index 03464636a9681ba512dd283b47ab2b7ea6d7d80e..2c61adb0224084ebfc9cb8ef5d01a0580cb6cb2f 100644
--- a/bin/jni/configurationmanager.i
+++ b/bin/jni/configurationmanager.i
@@ -35,6 +35,7 @@ public:
     virtual void volatileAccountDetailsChanged(const std::string& account_id, const std::map<std::string, std::string>& details){}
     virtual void incomingAccountMessage(const std::string& /*account_id*/, const std::string& /*from*/, const std::map<std::string, std::string>& /*payload*/){}
     virtual void accountMessageStatusChanged(const std::string& /*account_id*/, uint64_t /*message_id*/, const std::string& /*to*/, int /*state*/){}
+    virtual void composingStatusChanged(const std::string& /*account_id*/, const std::string& /*from*/, int /*state*/){}
     virtual void knownDevicesChanged(const std::string& /*account_id*/, const std::map<std::string, std::string>& /*devices*/){}
     virtual void exportOnRingEnded(const std::string& /*account_id*/, int /*state*/, const std::string& /*pin*/){}
 
@@ -92,6 +93,8 @@ std::vector<DRing::Message> getLastMessages(const std::string& accountID, uint64
 int getMessageStatus(uint64_t id);
 int getMessageStatus(const std::string& accountID, uint64_t id);
 bool cancelMessage(const std::string& accountID, uint64_t id);
+void setIsComposing(const std::string& accountID, const std::string& to, bool isWriting);
+
 bool changeAccountPassword(const std::string& accountID, const std::string& password_old, const std::string& password_new);
 
 bool lookupName(const std::string& account, const std::string& nameserver, const std::string& name);
@@ -241,6 +244,7 @@ public:
     virtual void volatileAccountDetailsChanged(const std::string& account_id, const std::map<std::string, std::string>& details){}
     virtual void incomingAccountMessage(const std::string& /*account_id*/, const std::string& /*from*/, const std::map<std::string, std::string>& /*payload*/){}
     virtual void accountMessageStatusChanged(const std::string& /*account_id*/, uint64_t /*message_id*/, const std::string& /*to*/, int /*state*/){}
+    virtual void composingStatusChanged(const std::string& /*account_id*/, const std::string& /*from*/, int /*state*/){}
     virtual void knownDevicesChanged(const std::string& /*account_id*/, const std::map<std::string, std::string>& /*devices*/){}
     virtual void exportOnRingEnded(const std::string& /*account_id*/, int /*state*/, const std::string& /*pin*/){}
 
diff --git a/bin/jni/jni_interface.i b/bin/jni/jni_interface.i
index 1a9201cdc47f9572ed346d283100bf175b0b58cf..304e208f132d65a3875411a80283667d3b6f3b03 100644
--- a/bin/jni/jni_interface.i
+++ b/bin/jni/jni_interface.i
@@ -263,6 +263,7 @@ void init(ConfigurationCallback* confM, Callback* callM, PresenceCallback* presM
         exportable_callback<ConfigurationSignal::Error>(bind(&ConfigurationCallback::errorAlert, confM, _1)),
         exportable_callback<ConfigurationSignal::IncomingAccountMessage>(bind(&ConfigurationCallback::incomingAccountMessage, confM, _1, _2, _3 )),
         exportable_callback<ConfigurationSignal::AccountMessageStatusChanged>(bind(&ConfigurationCallback::accountMessageStatusChanged, confM, _1, _2, _3, _4 )),
+        exportable_callback<ConfigurationSignal::ComposingStatusChanged>(bind(&ConfigurationCallback::composingStatusChanged, confM, _1, _2, _3 )),
         exportable_callback<ConfigurationSignal::IncomingTrustRequest>(bind(&ConfigurationCallback::incomingTrustRequest, confM, _1, _2, _3, _4 )),
         exportable_callback<ConfigurationSignal::ContactAdded>(bind(&ConfigurationCallback::contactAdded, confM, _1, _2, _3 )),
         exportable_callback<ConfigurationSignal::ContactRemoved>(bind(&ConfigurationCallback::contactRemoved, confM, _1, _2, _3 )),
diff --git a/configure.ac b/configure.ac
index 333267ea0092c485e1df34818fde66ad14a8d38e..69f68816281333e48a91b3d9f8212eac74b9b324 100644
--- a/configure.ac
+++ b/configure.ac
@@ -2,7 +2,7 @@ dnl Jami - configure.ac for automake 1.9 and autoconf 2.59
 
 dnl Process this file with autoconf to produce a configure script.
 AC_PREREQ([2.65])
-AC_INIT([Jami Daemon],[7.8.0],[ring@gnu.org],[jami])
+AC_INIT([Jami Daemon],[7.9.0],[ring@gnu.org],[jami])
 
 AC_COPYRIGHT([[Copyright (c) Savoir-faire Linux 2004-2019]])
 AC_REVISION([$Revision$])
diff --git a/src/account.cpp b/src/account.cpp
index 0c8565401dd92c401bf5b2556dd5ddf7ae2cf08f..86e92b4afee214ff2d18ecb9cbfbf9d72804fbc4 100644
--- a/src/account.cpp
+++ b/src/account.cpp
@@ -340,6 +340,12 @@ Account::getVolatileAccountDetails() const
     };
 }
 
+void
+Account::onIsComposing(const std::string& peer, bool isComposing)
+{
+    emitSignal<DRing::ConfigurationSignal::ComposingStatusChanged>(accountID_, peer, isComposing ? 1 : 0);
+}
+
 bool
 Account::hasActiveCodec(MediaType mediaType) const
 {
diff --git a/src/account.h b/src/account.h
index e5271bab75670707f34541ef5e662090de32d9cd..37ea96e97631249d2f9d92ac1e44a6eb536e86f4 100644
--- a/src/account.h
+++ b/src/account.h
@@ -153,8 +153,12 @@ class Account : public Serializable, public std::enable_shared_from_this<Account
          * If supported, send a text message from this account.
          * @return a token to query the message status
          */
-        virtual uint64_t sendTextMessage(const std::string& to UNUSED,
-                                     const std::map<std::string, std::string>& payloads UNUSED) { return 0; }
+        virtual uint64_t sendTextMessage(const std::string& /*to*/,
+                                     const std::map<std::string, std::string>& /*payloads*/) { return 0; }
+
+        virtual void setIsComposing(const std::string& /*to*/, bool /*isWriting*/) {};
+
+        virtual void onIsComposing(const std::string& /*peer*/, bool /*isWriting*/);
 
         virtual std::vector<DRing::Message> getLastMessages(const uint64_t& /*base_timestamp*/) {
             return {};
diff --git a/src/client/configurationmanager.cpp b/src/client/configurationmanager.cpp
index 14b6767eb7b2e03b317cd4c73b4d017ee9bcebaa..102756fb7e5a9345c821d96d2a86a5706cfd98ed 100644
--- a/src/client/configurationmanager.cpp
+++ b/src/client/configurationmanager.cpp
@@ -298,6 +298,13 @@ cancelMessage(const std::string& accountID, uint64_t messageId)
     return {};
 }
 
+void
+setIsComposing(const std::string& accountID, const std::string& to, bool isWriting)
+{
+    if (const auto acc = jami::Manager::instance().getAccount(accountID))
+        return acc->setIsComposing(to, isWriting);
+}
+
 bool
 exportOnRing(const std::string& accountID, const std::string& password)
 {
diff --git a/src/client/ring_signal.cpp b/src/client/ring_signal.cpp
index 2e42aed62dfbc4613852d57fecbd35f246b17cb8..f61f1f44c28ac10a7e052cc0779a641a9620122e 100644
--- a/src/client/ring_signal.cpp
+++ b/src/client/ring_signal.cpp
@@ -61,6 +61,7 @@ getSignalHandlers()
         exported_callback<DRing::ConfigurationSignal::CertificateExpired>(),
         exported_callback<DRing::ConfigurationSignal::CertificateStateChanged>(),
         exported_callback<DRing::ConfigurationSignal::IncomingAccountMessage>(),
+        exported_callback<DRing::ConfigurationSignal::ComposingStatusChanged>(),
         exported_callback<DRing::ConfigurationSignal::AccountMessageStatusChanged>(),
         exported_callback<DRing::ConfigurationSignal::IncomingTrustRequest>(),
         exported_callback<DRing::ConfigurationSignal::ContactAdded>(),
diff --git a/src/dring/configurationmanager_interface.h b/src/dring/configurationmanager_interface.h
index 18d694cacb52159d63daaaf62da20d8f6ff69285..6169cd0f9a1b8dfdeaac6946b95ec51a714c243b 100644
--- a/src/dring/configurationmanager_interface.h
+++ b/src/dring/configurationmanager_interface.h
@@ -81,6 +81,7 @@ DRING_PUBLIC std::vector<Message> getLastMessages(const std::string& accountID,
 DRING_PUBLIC std::map<std::string, std::string> getNearbyPeers(const std::string& accountID);
 DRING_PUBLIC int getMessageStatus(uint64_t id);
 DRING_PUBLIC int getMessageStatus(const std::string& accountID, uint64_t id);
+DRING_PUBLIC void setIsComposing(const std::string& accountID, const std::string& to, bool isWriting);
 
 DRING_PUBLIC std::map<std::string, std::string> getTlsDefaultSettings();
 
@@ -289,6 +290,10 @@ struct DRING_PUBLIC ConfigurationSignal {
                 constexpr static const char* name = "AccountMessageStatusChanged";
                 using cb_type = void(const std::string& /*account_id*/, uint64_t /*message_id*/, const std::string& /*to*/, int /*state*/);
         };
+        struct DRING_PUBLIC ComposingStatusChanged {
+                constexpr static const char* name = "ComposingStatusChanged";
+                using cb_type = void(const std::string& /*account_id*/, const std::string& /*from*/, int /*status*/);
+        };
         struct DRING_PUBLIC IncomingTrustRequest {
                 constexpr static const char* name = "IncomingTrustRequest";
                 using cb_type = void(const std::string& /*account_id*/, const std::string& /*from*/, const std::vector<uint8_t>& payload, time_t received);
diff --git a/src/im/instant_messaging.cpp b/src/im/instant_messaging.cpp
index c6aebda124405206ab56deaffdf7bb78051d4cc9..12e271e7a83e932dfb7dba5511fabf2a2e217257 100644
--- a/src/im/instant_messaging.cpp
+++ b/src/im/instant_messaging.cpp
@@ -238,7 +238,7 @@ im::parseSipMessage(const pjsip_msg* msg)
     }
 
     // check if its a multipart message
-    pj_str_t typeMultipart {const_cast<char*>("multipart"), 9};
+    constexpr pj_str_t typeMultipart {CONST_PJ_STR("multipart")};
 
     if (pj_strcmp(&typeMultipart, &msg->body->content_type.type) != 0) {
         // treat as single content type message
diff --git a/src/jamidht/jamiaccount.cpp b/src/jamidht/jamiaccount.cpp
index 004b062021994d0433456d982cf2fe29331c0307..81fb11e130540906b288e172f1462b9a18b406b5 100644
--- a/src/jamidht/jamiaccount.cpp
+++ b/src/jamidht/jamiaccount.cpp
@@ -2908,6 +2908,16 @@ JamiAccount::sendTextMessage(const std::string& to, const std::map<std::string,
     }, std::chrono::steady_clock::now() + std::chrono::minutes(1));
 }
 
+void
+JamiAccount::onIsComposing(const std::string& peer, bool isWriting)
+{
+    try {
+        Account::onIsComposing(parseJamiUri(peer), isWriting);
+    } catch (...) {
+        JAMI_ERR("[Account %s] Can't parse URI: %s", getAccountID().c_str(), peer.c_str());
+    }
+}
+
 void
 JamiAccount::registerDhtAddress(IceTransport& ice)
 {
diff --git a/src/jamidht/jamiaccount.h b/src/jamidht/jamiaccount.h
index 83287bfa329721537e867bda1dfe2b477d16e295..4e033f344c2636eec9aec1fb617e1d77451818c0 100644
--- a/src/jamidht/jamiaccount.h
+++ b/src/jamidht/jamiaccount.h
@@ -315,8 +315,9 @@ public:
 
     void sendTrustRequest(const std::string& to, const std::vector<uint8_t>& payload);
     void sendTrustRequestConfirm(const std::string& to);
-    virtual void sendTextMessage(const std::string& to, const std::map<std::string, std::string>& payloads, uint64_t id, bool retryOnTimeout=true) override;
-    virtual uint64_t sendTextMessage(const std::string& to, const std::map<std::string, std::string>& payloads) override;
+    void sendTextMessage(const std::string& to, const std::map<std::string, std::string>& payloads, uint64_t id, bool retryOnTimeout=true) override;
+    uint64_t sendTextMessage(const std::string& to, const std::map<std::string, std::string>& payloads) override;
+    void onIsComposing(const std::string& peer, bool isWriting) override;
 
     /* Devices */
     void addDevice(const std::string& password);
diff --git a/src/sip/sipaccountbase.cpp b/src/sip/sipaccountbase.cpp
index 9b9bf866780e1853884b55c324bba53485ebfe6a..e848211e13fec359dcfa58e0a16dda998d70ed33 100644
--- a/src/sip/sipaccountbase.cpp
+++ b/src/sip/sipaccountbase.cpp
@@ -32,22 +32,27 @@
 
 #include "config/yamlparser.h"
 
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
-#include <yaml-cpp/yaml.h>
-#pragma GCC diagnostic pop
-
 #include "client/ring_signal.h"
 #include "string_utils.h"
 #include "fileutils.h"
 #include "sip_utils.h"
 #include "utf8_utils.h"
 
-#include <ctime>
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+#include <yaml-cpp/yaml.h>
+#pragma GCC diagnostic pop
+
 #include <type_traits>
+#include <regex>
+
+#include <ctime>
 
 namespace jami {
 
+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, fileutils::get_cache_dir()+DIR_SEPARATOR_STR+getAccountID()+DIR_SEPARATOR_STR "messages"),
@@ -102,6 +107,53 @@ SIPAccountBase::flush()
     fileutils::remove(fileutils::get_cache_dir() + DIR_SEPARATOR_STR + getAccountID() + DIR_SEPARATOR_STR "messages");
 }
 
+std::string
+getIsComposing(bool isWriting)
+{
+    // implementing https://tools.ietf.org/rfc/rfc3994.txt
+    std::ostringstream ss;
+    ss << "<?xml version=\"1.0\" encoding=\"utf-8\" ?>" << std::endl
+       << "<isComposing><state>" << (isWriting ? "active" : "idle") << "</state></isComposing>";
+    return  ss.str();
+}
+
+void
+SIPAccountBase::setIsComposing(const std::string& to, bool isWriting)
+{
+    if (not isWriting and to != composingUri_)
+        return;
+
+    if (composingTimeout_) {
+        composingTimeout_->cancel();
+        composingTimeout_.reset();
+    }
+    if (isWriting)  {
+        if (not composingUri_.empty() and composingUri_ != to) {
+            sendTextMessage(composingUri_, {{MIME_TYPE_IM_COMPOSING, getIsComposing(false)}});
+            composingTime_  = std::chrono::steady_clock::time_point::min();
+        }
+        composingUri_.clear();
+        composingUri_.insert(composingUri_.end(), to.begin(), to.end());
+        auto now = std::chrono::steady_clock::now();
+        if (now >= composingTime_ + COMPOSING_TIMEOUT) {
+            sendTextMessage(composingUri_, {{MIME_TYPE_IM_COMPOSING, getIsComposing(true)}});
+            composingTime_ = now;
+        }
+        std::weak_ptr<SIPAccountBase> weak = std::static_pointer_cast<SIPAccountBase>(shared_from_this());
+        composingTimeout_ = Manager::instance().scheduleTask([weak, to](){
+            if (auto sthis = weak.lock())  {
+                sthis->sendTextMessage(to, {{MIME_TYPE_IM_COMPOSING, getIsComposing(false)}});
+                sthis->composingUri_.clear();
+                sthis->composingTime_  = std::chrono::steady_clock::time_point::min();
+            }
+        }, now + COMPOSING_TIMEOUT);
+    } else {
+        sendTextMessage(to, {{MIME_TYPE_IM_COMPOSING, getIsComposing(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)
@@ -413,6 +465,22 @@ SIPAccountBase::onTextMessage(const std::string& from,
             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";
+                }
+                onIsComposing(from, isComposing);
+                if (payloads.size() == 1)
+                    return;
+            } catch (const std::exception& e) {
+                JAMI_WARN("Error parsing composing state: %s", e.what());
+            }
+        }
     }
     emitSignal<DRing::ConfigurationSignal::IncomingAccountMessage>(accountID_, from, payloads);
     DRing::Message message;
diff --git a/src/sip/sipaccountbase.h b/src/sip/sipaccountbase.h
index 1ea26cca066dc89ab6818ee52dddbb167e7a8b91..79421c2eefea94806f087f4757f1807ee2ce8eec 100644
--- a/src/sip/sipaccountbase.h
+++ b/src/sip/sipaccountbase.h
@@ -31,8 +31,6 @@
 #include "noncopyable.h"
 #include "im/message_engine.h"
 
-#include <pjsip/sip_types.h>
-
 #include <array>
 #include <deque>
 #include <map>
@@ -40,6 +38,8 @@
 #include <mutex>
 #include <vector>
 
+extern "C" {
+#include <pjsip/sip_types.h>
 #ifdef _WIN32
 typedef uint16_t in_port_t;
 #else
@@ -49,10 +49,12 @@ typedef uint16_t in_port_t;
 struct pjsip_dialog;
 struct pjsip_inv_session;
 struct pjmedia_sdp_session;
+}
 
 namespace jami {
 
 class SipTransport;
+class Task;
 
 namespace Conf {
     // SIP specific configuration keys
@@ -267,6 +269,8 @@ public:
         return messageEngine_.sendMessage(to, payloads);
     }
 
+    void setIsComposing(const std::string& to, bool isWriting) override;
+
     virtual im::MessageStatus getMessageStatus(uint64_t id) const override {
         return messageEngine_.getStatus(id);
     }
@@ -462,6 +466,10 @@ protected:
     static constexpr size_t MAX_WAITING_MESSAGES_SIZE = 1000;
     std::deque<DRing::Message> lastMessages_;
 
+    std::string composingUri_;
+    std::chrono::steady_clock::time_point composingTime_ {std::chrono::steady_clock::time_point::min()};
+    std::shared_ptr<Task> composingTimeout_;
+
     mutable std::mutex cachedTurnMutex_ {};
     std::unique_ptr<IpAddr> cacheTurnV4_ {};
     std::unique_ptr<IpAddr> cacheTurnV6_ {};