diff --git a/deps/CMakeLists.txt b/deps/CMakeLists.txt
index 9eb1d8407fb36f1880f893445b6b147fcfd5d473..1a45112e9acfaa686a29b775fb4e4f5b03c445fc 100644
--- a/deps/CMakeLists.txt
+++ b/deps/CMakeLists.txt
@@ -46,10 +46,10 @@ set(BOOST_SHA256
 
 set(
   MTXCLIENT_URL
-  https://github.com/Nheko-Reborn/mtxclient/archive/1c31a8072f09a454b9239e11740473c8a7dbee39.tar.gz
+  https://github.com/Nheko-Reborn/mtxclient/archive/8c6e9ba8fc18ed9dd69d014eebd1ebff08701d6d.tar.gz
   )
 set(MTXCLIENT_HASH
-  5cdcd7d6feaefa8df8966bd053ea2d1181eb7e6c0f3b548b2f98f00400e2760e)
+  b31ec18b9d7d74db1a17b930bfa570fa1cede56cc49b43948b7d86c396f2f3d3)
 set(
   TWEENY_URL
   https://github.com/mobius3/tweeny/archive/b94ce07cfb02a0eb8ac8aaf66137dabdaea857cf.tar.gz
diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp
index dd23fb80907df602949df4cd9906f1f21f6432d0..5b8382595a7614410176b3ddb101e81bb4fd8e3b 100644
--- a/src/ChatPage.cpp
+++ b/src/ChatPage.cpp
@@ -264,6 +264,11 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
                 view_manager_,
                 SLOT(queueTextMessage(const QString &)));
 
+        connect(text_input_,
+                SIGNAL(sendReplyMessage(const QString &, const QString &)),
+                view_manager_,
+                SLOT(queueReplyMessage(const QString &, const QString &)));
+
         connect(text_input_,
                 SIGNAL(sendEmoteMessage(const QString &)),
                 view_manager_,
diff --git a/src/ChatPage.h b/src/ChatPage.h
index 7d3b3273cda1204399c22450ca3e3a013cd043f2..09e7a2c6e7fb674c93374d6ec3352a4dc58b133f 100644
--- a/src/ChatPage.h
+++ b/src/ChatPage.h
@@ -83,7 +83,7 @@ signals:
         void connectionLost();
         void connectionRestored();
 
-        void messageReply(const QString &username, const QString &msg);
+        void messageReply(const QString &username, const QString &msg, const QString &related_event);
 
         void notificationsRetrieved(const mtx::responses::Notifications &);
 
diff --git a/src/TextInputWidget.cpp b/src/TextInputWidget.cpp
index 934f2b2cac62550a7d2ce2dc59abb75c880fea49..340c6f7c8a87954c20891a3ef7d147736824e0e1 100644
--- a/src/TextInputWidget.cpp
+++ b/src/TextInputWidget.cpp
@@ -398,14 +398,24 @@ FilteredTextEdit::submit()
                 auto name = text.mid(1, command_end - 1);
                 auto args = text.mid(command_end + 1);
                 if (name.isEmpty() || name == "/") {
-                        message(args);
+                        if (!related_event_.isEmpty()) {
+                                reply(args, related_event_);
+                        } else {
+                                message(args);
+                        }
                 } else {
                         command(name, args);
                 }
         } else {
-                message(std::move(text));
+                if (!related_event_.isEmpty()) {
+                        reply(std::move(text), std::move(related_event_));
+                } else {
+                        message(std::move(text));
+                }
         }
 
+        related_event_ = "";
+
         clear();
 }
 
@@ -536,6 +546,7 @@ TextInputWidget::TextInputWidget(QWidget *parent)
         connect(sendMessageBtn_, &FlatButton::clicked, input_, &FilteredTextEdit::submit);
         connect(sendFileBtn_, SIGNAL(clicked()), this, SLOT(openFileSelection()));
         connect(input_, &FilteredTextEdit::message, this, &TextInputWidget::sendTextMessage);
+        connect(input_, &FilteredTextEdit::reply, this, &TextInputWidget::sendReplyMessage);
         connect(input_, &FilteredTextEdit::command, this, &TextInputWidget::command);
         connect(input_, &FilteredTextEdit::image, this, &TextInputWidget::uploadImage);
         connect(input_, &FilteredTextEdit::audio, this, &TextInputWidget::uploadAudio);
@@ -653,7 +664,7 @@ TextInputWidget::paintEvent(QPaintEvent *)
 }
 
 void
-TextInputWidget::addReply(const QString &username, const QString &msg)
+TextInputWidget::addReply(const QString &username, const QString &msg, const QString &replied_event)
 {
         input_->setText(QString("> %1: %2\n\n").arg(username).arg(msg));
         input_->setFocus();
@@ -661,4 +672,5 @@ TextInputWidget::addReply(const QString &username, const QString &msg)
         auto cursor = input_->textCursor();
         cursor.movePosition(QTextCursor::End);
         input_->setTextCursor(cursor);
+        input_->setRelatedEvent(replied_event);
 }
diff --git a/src/TextInputWidget.h b/src/TextInputWidget.h
index 8f634f6b5216226d52e361b616e5da4c9eb885d3..a12183d8dc11e1adbd4275934bd26d06a67ce2be 100644
--- a/src/TextInputWidget.h
+++ b/src/TextInputWidget.h
@@ -54,6 +54,7 @@ public:
         QSize minimumSizeHint() const override;
 
         void submit();
+        void setRelatedEvent(const QString &event) { related_event_ = event; }
 
 signals:
         void heightChanged(int height);
@@ -61,6 +62,7 @@ signals:
         void stoppedTyping();
         void startedUpload();
         void message(QString);
+        void reply(QString, QString);
         void command(QString name, QString args);
         void image(QSharedPointer<QIODevice> data, const QString &filename);
         void audio(QSharedPointer<QIODevice> data, const QString &filename);
@@ -94,6 +96,9 @@ private:
 
         SuggestionsPopup popup_;
 
+        // Used for replies
+        QString related_event_;
+
         enum class AnchorType
         {
                 Tab   = 0,
@@ -158,13 +163,14 @@ public slots:
         void openFileSelection();
         void hideUploadSpinner();
         void focusLineEdit() { input_->setFocus(); }
-        void addReply(const QString &username, const QString &msg);
+        void addReply(const QString &username, const QString &msg, const QString &related_event);
 
 private slots:
         void addSelectedEmoji(const QString &emoji);
 
 signals:
         void sendTextMessage(QString msg);
+        void sendReplyMessage(QString msg, QString event_id);
         void sendEmoteMessage(QString msg);
         void heightChanged(int height);
 
@@ -189,6 +195,9 @@ private:
         QHBoxLayout *topLayout_;
         FilteredTextEdit *input_;
 
+        // Used for replies
+        QString related_event_;
+
         LoadingIndicator *spinner_;
 
         FlatButton *sendFileBtn_;
diff --git a/src/timeline/TimelineItem.cpp b/src/timeline/TimelineItem.cpp
index faae1da37152e194091e2c5d7c747434423aeb00..dd09ec789b7d24e254f7e084a1db3e7b530feb2f 100644
--- a/src/timeline/TimelineItem.cpp
+++ b/src/timeline/TimelineItem.cpp
@@ -875,7 +875,7 @@ TimelineItem::replyAction()
                 return;
 
         emit ChatPage::instance()->messageReply(
-          Cache::displayName(room_id_, descriptionMsg_.userid), body_->toPlainText());
+          Cache::displayName(room_id_, descriptionMsg_.userid), body_->toPlainText(), eventId());
 }
 
 void
diff --git a/src/timeline/TimelineView.cpp b/src/timeline/TimelineView.cpp
index 2b4f979b296586f00aac5c9687e0418d36c7e1b4..ee7021d854fe3eaefad8a7899b56940760cfa462 100644
--- a/src/timeline/TimelineView.cpp
+++ b/src/timeline/TimelineView.cpp
@@ -690,7 +690,7 @@ TimelineView::updatePendingMessage(const std::string &txn_id, const QString &eve
 }
 
 void
-TimelineView::addUserMessage(mtx::events::MessageType ty, const QString &body)
+TimelineView::addUserMessage(mtx::events::MessageType ty, const QString &body, const QString &related_event)
 {
         auto with_sender = (lastSender_ != local_user_) || isDateDifference(lastMsgTimestamp_);
 
@@ -702,6 +702,9 @@ TimelineView::addUserMessage(mtx::events::MessageType ty, const QString &body)
         message.txn_id = http::client()->generate_txn_id();
         message.body   = body;
         message.widget = view_item;
+        if (!related_event.isEmpty()) {
+                message.related_event = related_event.toStdString();
+        }
 
         try {
                 message.is_encrypted = cache::client()->isRoomEncrypted(room_id_.toStdString());
@@ -722,6 +725,12 @@ TimelineView::addUserMessage(mtx::events::MessageType ty, const QString &body)
         handleNewUserMessage(message);
 }
 
+void
+TimelineView::addUserMessage(mtx::events::MessageType ty, const QString &body)
+{
+        addUserMessage(ty, body, "");
+}
+
 void
 TimelineView::handleNewUserMessage(PendingMessage msg)
 {
@@ -1267,6 +1276,10 @@ toRoomMessage<mtx::events::msg::Text>(const PendingMessage &m)
         if (html != m.body.trimmed().toHtmlEscaped())
                 text.formatted_body = html.toStdString();
 
+        if (!m.related_event.empty()) {
+                text.relates_to.in_reply_to.event_id = m.related_event;
+        }
+
         return text;
 }
 
diff --git a/src/timeline/TimelineView.h b/src/timeline/TimelineView.h
index b0909b4424e1585b735c85061b9f2fc14a5483d0..450b5dfa17959367dc7745847d6a12eeae748c67 100644
--- a/src/timeline/TimelineView.h
+++ b/src/timeline/TimelineView.h
@@ -63,6 +63,7 @@ struct PendingMessage
 {
         mtx::events::MessageType ty;
         std::string txn_id;
+        std::string related_event;
         QString body;
         QString filename;
         QString mime;
@@ -120,6 +121,7 @@ public:
 
         // Add new events at the end of the timeline.
         void addEvents(const mtx::responses::Timeline &timeline);
+        void addUserMessage(mtx::events::MessageType ty, const QString &body, const QString &related_event);
         void addUserMessage(mtx::events::MessageType ty, const QString &msg);
 
         template<class Widget, mtx::events::MessageType MsgType>
diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp
index feab46a331ce35fe1c7bedae7a77d122f189bcb5..10c2d7479c1374913e155a3920ccb6ab07a60374 100644
--- a/src/timeline/TimelineViewManager.cpp
+++ b/src/timeline/TimelineViewManager.cpp
@@ -78,6 +78,19 @@ TimelineViewManager::queueEmoteMessage(const QString &msg)
         view->addUserMessage(mtx::events::MessageType::Emote, msg);
 }
 
+void
+TimelineViewManager::queueReplyMessage(const QString &reply,
+                                       const QString &related_event)
+{
+        if (active_room_.isEmpty())
+                return;
+
+        auto room_id = active_room_;
+        auto view    = views_[room_id];
+
+        view->addUserMessage(mtx::events::MessageType::Text, reply, related_event);
+}
+
 void
 TimelineViewManager::queueImageMessage(const QString &roomid,
                                        const QString &filename,
diff --git a/src/timeline/TimelineViewManager.h b/src/timeline/TimelineViewManager.h
index d23345d345d05e3995b058bc83ee033799f980a3..cc981d9ec77d98f553d774e5b755884a28bf3c1a 100644
--- a/src/timeline/TimelineViewManager.h
+++ b/src/timeline/TimelineViewManager.h
@@ -63,6 +63,8 @@ public slots:
 
         void setHistoryView(const QString &room_id);
         void queueTextMessage(const QString &msg);
+        void queueReplyMessage(const QString &reply,
+                               const QString &related_event);
         void queueEmoteMessage(const QString &msg);
         void queueImageMessage(const QString &roomid,
                                const QString &filename,