diff --git a/.ci/script.sh b/.ci/script.sh
index 63e7f135f2b72441bdd7e472c1828e26d0e49a45..f43a6efb4a75d882522e644065202632ce3fb4ec 100755
--- a/.ci/script.sh
+++ b/.ci/script.sh
@@ -71,7 +71,6 @@ cmake -GNinja -H. -Bbuild \
     -DHUNTER_ROOT=".hunter" \
     -DHUNTER_ENABLED=ON -DBUILD_SHARED_LIBS=OFF \
     -DCMAKE_BUILD_TYPE=RelWithDebInfo -DHUNTER_CONFIGURATION_TYPES=RelWithDebInfo \
-    -DUSE_BUNDLED_OPENSSL=OFF \
     -DCI_BUILD=ON
 fi
 cmake --build build
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 5b47b0af82f43123d5c64129267ee396baeb4105..b2b8da3b805e3d2ce1260bb3ec82ef305928e0a0 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -4,6 +4,7 @@ option(APPVEYOR_BUILD "Build on appveyor" OFF)
 option(CI_BUILD "Set when building in CI. Enables -Werror where possible" OFF)
 option(ASAN "Compile with address sanitizers" OFF)
 option(QML_DEBUGGING "Enable qml debugging" OFF)
+option(COMPILE_QML "Compile Qml. It will make Nheko faster, but you will need to recompile it, when you update Qt." OFF)
 
 set(
 	CMAKE_TOOLCHAIN_FILE "${CMAKE_CURRENT_LIST_DIR}/toolchain.cmake"
@@ -17,10 +18,9 @@ set(CMAKE_POSITION_INDEPENDENT_CODE ON CACHE BOOL "compile as PIC by default")
 option(HUNTER_ENABLED "Enable Hunter package manager" OFF)
 include("cmake/HunterGate.cmake")
 HunterGate(
-	URL "https://github.com/cpp-pm/hunter/archive/v0.23.244.tar.gz"
-	SHA1 "2c0f491fd0b80f7b09e3d21adb97237161ef9835"
-	LOCAL
-	)
+    URL "https://github.com/cpp-pm/hunter/archive/v0.23.260.tar.gz"
+    SHA1 "13775235910a3fa85644568d1c5be8271de72e1c"
+)
 
 option(USE_BUNDLED_BOOST "Use the bundled version of Boost." ${HUNTER_ENABLED})
 option(USE_BUNDLED_SPDLOG "Use the bundled version of spdlog."
@@ -35,8 +35,6 @@ option(USE_BUNDLED_JSON "Use the bundled version of nlohmann json."
 option(USE_BUNDLED_OPENSSL "Use the bundled version of OpenSSL."
 	${HUNTER_ENABLED})
 option(USE_BUNDLED_MTXCLIENT "Use the bundled version of the Matrix Client library." ${HUNTER_ENABLED})
-option(USE_BUNDLED_SODIUM "Use the bundled version of libsodium."
-	${HUNTER_ENABLED})
 option(USE_BUNDLED_LMDB "Use the bundled version of lmdb."
 	${HUNTER_ENABLED})
 option(USE_BUNDLED_LMDBXX "Use the bundled version of lmdb++."
@@ -334,7 +332,7 @@ find_package(Boost 1.70 REQUIRED
 if(USE_BUNDLED_OPENSSL)
 	hunter_add_package(OpenSSL)
 endif()
-find_package(OpenSSL REQUIRED)
+find_package(OpenSSL 1.1.0 REQUIRED)
 if(USE_BUNDLED_MTXCLIENT)
 	include(FetchContent)
 	set(BUILD_LIB_EXAMPLES OFF CACHE INTERNAL "")
@@ -342,7 +340,7 @@ if(USE_BUNDLED_MTXCLIENT)
 	FetchContent_Declare(
 		MatrixClient
 		GIT_REPOSITORY https://github.com/Nheko-Reborn/mtxclient.git
-		GIT_TAG        v0.3.1
+		GIT_TAG        eddd95a896fad0c51fc800741d82bbc43fc6d41e
 		)
 	FetchContent_MakeAvailable(MatrixClient)
 else()
diff --git a/README.md b/README.md
index cb5685d51de8e15e2f50f373ddd8dc3ff2d7fa40..20340a46a06d404d4f9f2ec0b36855bdb6921476 100644
--- a/README.md
+++ b/README.md
@@ -114,7 +114,6 @@ brew cask install nheko
 - [cmark](https://github.com/commonmark/cmark) 0.29 or greater.
 - Boost 1.70 or greater.
 - [libolm](https://gitlab.matrix.org/matrix-org/olm)
-- [libsodium](https://github.com/jedisct1/libsodium)
 - [spdlog](https://github.com/gabime/spdlog)
 - A compiler that supports C++ 17:
     - Clang 6 (tested on Travis CI)
@@ -136,8 +135,6 @@ The bundle flags are currently:
 - USE_BUNDLED_JSON
 - USE_BUNDLED_OPENSSL
 - USE_BUNDLED_MTXCLIENT
-- USE_BUNDLED_SODIUM
-- USE_BUNDLED_ZLIB
 - USE_BUNDLED_LMDB
 - USE_BUNDLED_LMDBXX
 - USE_BUNDLED_TWEENY
@@ -162,8 +159,7 @@ sudo pacman -S qt5-base \
     fontconfig \
     lmdb \
     cmark \
-    boost \
-    libsodium
+    boost
 ```
 
 ##### Gentoo Linux
@@ -176,7 +172,7 @@ sudo emerge -a ">=dev-qt/qtgui-5.9.0" media-libs/fontconfig
 
 ```bash
 # Build requirements + qml modules needed at runtime (you may not need all of them, but the following seem to work according to reports):
-sudo apt install g++ cmake zlib1g-dev libssl-dev qt{base,declarative,tools,multimedia,quickcontrols2-}5-dev libqt5svg5-dev libboost-system-dev libboost-thread-dev libboost-iostreams-dev libolm-dev libsodium-dev liblmdb++-dev libcmark-dev nlohmann-json3-dev libspdlog-dev libgtest-dev qml-module-qt{gstreamer,multimedia,quick-extras,-labs-settings,graphicaleffects,quick-controls2}
+sudo apt install g++ cmake zlib1g-dev libssl-dev qt{base,declarative,tools,multimedia,quickcontrols2-}5-dev libqt5svg5-dev libboost-system-dev libboost-thread-dev libboost-iostreams-dev libolm-dev liblmdb++-dev libcmark-dev nlohmann-json3-dev libspdlog-dev libgtest-dev qml-module-qt{gstreamer,multimedia,quick-extras,-labs-settings,graphicaleffects,quick-controls2}
 ```
 This will install all dependencies, except for tweeny (use bundled tweeny)
 and mtxclient (needs to be build separately).
@@ -186,7 +182,7 @@ and mtxclient (needs to be build separately).
 (User report, not sure if all of those are needed)
 
 ```bash
-sudo apt install cmake gcc make automake liblmdb-dev libsodium-dev \
+sudo apt install cmake gcc make automake liblmdb-dev \
     qt5-default libssl-dev libqt5multimedia5-plugins libqt5multimediagsttools5 libqt5multimediaquick5 libqt5svg5-dev \
     qml-module-qtgstreamer qtmultimedia5-dev qtquickcontrols2-5-dev qttools5-dev qttools5-dev-tools \
     qml-module-qtgraphicaleffects qml-module-qtmultimedia qml-module-qtquick-controls2 qml-module-qtquick-layouts
@@ -203,7 +199,7 @@ guix environment nheko
 
 ```bash
 brew update
-brew install qt5 lmdb cmake llvm libsodium spdlog boost cmark libolm
+brew install qt5 lmdb cmake llvm spdlog boost cmark libolm
 ```
 
 ##### Windows
diff --git a/cmake/Hunter/config.cmake b/cmake/Hunter/config.cmake
index d2f8777487b47a60ecbf5880d5a50c3f68791b11..7c53e0ea57096a3372bf3779dfdf86e8958e114e 100644
--- a/cmake/Hunter/config.cmake
+++ b/cmake/Hunter/config.cmake
@@ -1,5 +1,5 @@
 hunter_config(
     Boost
-    VERSION  "1.70.0-p0"
+    VERSION  "1.70.0-p1"
     CMAKE_ARGS IOSTREAMS_NO_BZIP2=1
 )
diff --git a/cmake/HunterGate.cmake b/cmake/HunterGate.cmake
index e78d3e8912a0dcce1a2f22763690e574d5c00e74..6d9cc24019af5cbd47c1d9388ee3776671107059 100644
--- a/cmake/HunterGate.cmake
+++ b/cmake/HunterGate.cmake
@@ -133,10 +133,14 @@ function(hunter_gate_self root version sha1 result)
 
   string(SUBSTRING "${sha1}" 0 7 archive_id)
 
-  set(
-      hunter_self
-      "${root}/_Base/Download/Hunter/${version}/${archive_id}/Unpacked"
-  )
+  if(EXISTS "${root}/cmake/Hunter")
+    set(hunter_self "${root}")
+  else()
+    set(
+        hunter_self
+        "${root}/_Base/Download/Hunter/${version}/${archive_id}/Unpacked"
+    )
+  endif()
 
   set("${result}" "${hunter_self}" PARENT_SCOPE)
 endfunction()
@@ -490,37 +494,44 @@ macro(HunterGate)
     )
 
     set(_master_location "${_hunter_self}/cmake/Hunter")
-    get_filename_component(_archive_id_location "${_hunter_self}/.." ABSOLUTE)
-    set(_done_location "${_archive_id_location}/DONE")
-    set(_sha1_location "${_archive_id_location}/SHA1")
-
-    # Check Hunter already downloaded by HunterGate
-    if(NOT EXISTS "${_done_location}")
-      hunter_gate_download("${_archive_id_location}")
-    endif()
+    if(EXISTS "${HUNTER_GATE_ROOT}/cmake/Hunter")
+      # Hunter downloaded manually (e.g. by 'git clone')
+      set(_unused "xxxxxxxxxx")
+      set(HUNTER_GATE_SHA1 "${_unused}")
+      set(HUNTER_GATE_VERSION "${_unused}")
+    else()
+      get_filename_component(_archive_id_location "${_hunter_self}/.." ABSOLUTE)
+      set(_done_location "${_archive_id_location}/DONE")
+      set(_sha1_location "${_archive_id_location}/SHA1")
+
+      # Check Hunter already downloaded by HunterGate
+      if(NOT EXISTS "${_done_location}")
+        hunter_gate_download("${_archive_id_location}")
+      endif()
 
-    if(NOT EXISTS "${_done_location}")
-      hunter_gate_internal_error("hunter_gate_download failed")
-    endif()
+      if(NOT EXISTS "${_done_location}")
+        hunter_gate_internal_error("hunter_gate_download failed")
+      endif()
 
-    if(NOT EXISTS "${_sha1_location}")
-      hunter_gate_internal_error("${_sha1_location} not found")
-    endif()
-    file(READ "${_sha1_location}" _sha1_value)
-    string(COMPARE EQUAL "${_sha1_value}" "${HUNTER_GATE_SHA1}" _is_equal)
-    if(NOT _is_equal)
-      hunter_gate_internal_error(
-          "Short SHA1 collision:"
-          "  ${_sha1_value} (from ${_sha1_location})"
-          "  ${HUNTER_GATE_SHA1} (HunterGate)"
-      )
-    endif()
-    if(NOT EXISTS "${_master_location}")
-      hunter_gate_user_error(
-          "Master file not found:"
-          "  ${_master_location}"
-          "try to update Hunter/HunterGate"
-      )
+      if(NOT EXISTS "${_sha1_location}")
+        hunter_gate_internal_error("${_sha1_location} not found")
+      endif()
+      file(READ "${_sha1_location}" _sha1_value)
+      string(COMPARE EQUAL "${_sha1_value}" "${HUNTER_GATE_SHA1}" _is_equal)
+      if(NOT _is_equal)
+        hunter_gate_internal_error(
+            "Short SHA1 collision:"
+            "  ${_sha1_value} (from ${_sha1_location})"
+            "  ${HUNTER_GATE_SHA1} (HunterGate)"
+        )
+      endif()
+      if(NOT EXISTS "${_master_location}")
+        hunter_gate_user_error(
+            "Master file not found:"
+            "  ${_master_location}"
+            "try to update Hunter/HunterGate"
+        )
+      endif()
     endif()
     include("${_master_location}")
     set_property(GLOBAL PROPERTY HUNTER_GATE_DONE YES)
diff --git a/cmake/Translations.cmake b/cmake/Translations.cmake
index 16120219eed4b6998eb0db0924de3b9cdd2a1e87..887697a8fb1d7177af118f9e07e7a9420622e78e 100644
--- a/cmake/Translations.cmake
+++ b/cmake/Translations.cmake
@@ -21,7 +21,7 @@ if(NOT EXISTS ${_qrc})
 endif()
 
 qt5_add_resources(LANG_QRC ${_qrc})
-if(Qt5QuickCompiler_FOUND)
+if(Qt5QuickCompiler_FOUND AND COMPILE_QML)
 	qtquick_compiler_add_resources(QRC resources/res.qrc)
 else()
 	qt5_add_resources(QRC resources/res.qrc)
diff --git a/io.github.NhekoReborn.Nheko.json b/io.github.NhekoReborn.Nheko.json
index 33acf34b3078aa9b3efcc5a14dab17d2626dda2f..8e4dbbe6dd29f5d8b9e008cdf3b47b79189eb06d 100644
--- a/io.github.NhekoReborn.Nheko.json
+++ b/io.github.NhekoReborn.Nheko.json
@@ -146,9 +146,9 @@
       "name": "mtxclient",
       "sources": [
         {
-          "sha256": "e4899cc4ce87397de2aef865e94ea2cdb8d9cb86253727e7d90532b925ecc770",
+          "sha256": "6334bb71821a0fde54fe24f02ad393cdb6836633557ffdd239b29c5d5108daaf",
           "type": "archive",
-          "url": "https://github.com/Nheko-Reborn/mtxclient/archive/v0.3.1.tar.gz"
+          "url": "https://github.com/Nheko-Reborn/mtxclient/archive/eddd95a896fad0c51fc800741d82bbc43fc6d41e.tar.gz"
         }
       ]
     },
@@ -171,7 +171,8 @@
     {
       "config-opts": [
         "-DCMAKE_BUILD_TYPE=Release",
-        "-DLMDBXX_INCLUDE_DIR=.deps/lmdbxx"
+        "-DLMDBXX_INCLUDE_DIR=.deps/lmdbxx",
+        "-DCOMPILE_QML=ON"
       ],
       "buildsystem": "cmake-ninja",
       "name": "nheko",
diff --git a/resources/qml/MatrixText.qml b/resources/qml/MatrixText.qml
index 9a4f73488c0576c15f8a2758503c3d6c526f62f7..d56143dd0143e612ebbf2134cc62ca6e6a9e30d3 100644
--- a/resources/qml/MatrixText.qml
+++ b/resources/qml/MatrixText.qml
@@ -16,7 +16,7 @@ TextEdit {
 			timelineManager.setHistoryView(match[1])
 			chat.positionViewAtIndex(chat.model.idToIndex(match[2]), ListView.Contain)
 		}
-		else Qt.openUrlExternally(link)
+		else timelineManager.openLink(link)
 	}
 	MouseArea
 	{
diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp
index 1bea856466c706554012bc58fdb563f0deaa93af..c83ce350e16285e376943bd1d75360d2fa3b89b5 100644
--- a/src/ChatPage.cpp
+++ b/src/ChatPage.cpp
@@ -589,8 +589,12 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
                                   emit notificationsRetrieved(std::move(res));
                           });
         });
-        connect(this, &ChatPage::syncRoomlist, room_list_, &RoomList::sync);
-        connect(this, &ChatPage::syncTags, communitiesList_, &CommunitiesList::syncTags);
+        connect(this, &ChatPage::syncRoomlist, room_list_, &RoomList::sync, Qt::QueuedConnection);
+        connect(this,
+                &ChatPage::syncTags,
+                communitiesList_,
+                &CommunitiesList::syncTags,
+                Qt::QueuedConnection);
         connect(
           this, &ChatPage::syncTopBar, this, [this](const std::map<QString, RoomInfo> &updates) {
                   if (updates.find(currentRoom()) != updates.end())
@@ -605,11 +609,15 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
                 user_info_widget_->setDisplayName(name);
         });
 
-        connect(this, &ChatPage::tryInitialSyncCb, this, &ChatPage::tryInitialSync);
-        connect(this, &ChatPage::trySyncCb, this, &ChatPage::trySync);
-        connect(this, &ChatPage::tryDelayedSyncCb, this, [this]() {
-                QTimer::singleShot(RETRY_TIMEOUT, this, &ChatPage::trySync);
-        });
+        connect(
+          this, &ChatPage::tryInitialSyncCb, this, &ChatPage::tryInitialSync, Qt::QueuedConnection);
+        connect(this, &ChatPage::trySyncCb, this, &ChatPage::trySync, Qt::QueuedConnection);
+        connect(
+          this,
+          &ChatPage::tryDelayedSyncCb,
+          this,
+          [this]() { QTimer::singleShot(RETRY_TIMEOUT, this, &ChatPage::trySync); },
+          Qt::QueuedConnection);
 
         connect(this, &ChatPage::dropToLoginPageCb, this, &ChatPage::dropToLoginPage);
 
diff --git a/src/Config.h b/src/Config.h
index f99cf36b65ba6aeaac49b03002a16b1d579b833c..c06247090b161854fd6cd0af0ab520adae6f6599 100644
--- a/src/Config.h
+++ b/src/Config.h
@@ -53,9 +53,9 @@ namespace strings {
 const QString url_html = "<a href=\"\\1\">\\1</a>";
 const QRegularExpression url_regex(
   // match an URL, that is not quoted, i.e.
-  // vvvvvv match quote via negative lookahead/lookbehind                    vv
-  //       vvvv atomic match url -> fail if there is a " before or after          vvv
-  R"((?<!")(?>((www\.(?!\.)|[a-z][a-z0-9+.-]*://)[^\s<>'"]+[^!,\.\s<>'"\]\)\:]))(?!"))");
+  // vvvvvv match quote via negative lookahead/lookbehind                              vv
+  //          vvvv atomic match url -> fail if there is a " before or after        vvv
+  R"((?<!["'])(?>((www\.(?!\.)|[a-z][a-z0-9+.-]*://)[^\s<>'"]+[^!,\.\s<>'"\]\)\:]))(?!["']))");
 }
 
 // Window geometry.
diff --git a/src/EventAccessors.cpp b/src/EventAccessors.cpp
index a2d8adbb2dea87d55b7e041bcfb61fd2e8333caf..7071819b0c64ebd9ac0500f170bbab6c0a52767d 100644
--- a/src/EventAccessors.cpp
+++ b/src/EventAccessors.cpp
@@ -85,8 +85,10 @@ struct EventFormattedBody
         template<class T>
         std::string operator()(const mtx::events::RoomEvent<T> &e)
         {
-                if constexpr (is_detected<formatted_body_t, T>::value)
-                        return e.content.formatted_body;
+                if constexpr (is_detected<formatted_body_t, T>::value) {
+                        if (e.content.format == "org.matrix.custom.html")
+                                return e.content.formatted_body;
+                }
                 return "";
         }
 };
diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp
index cdbd36c5b5f8f22de0e010c8b1c780cbc7a1e083..aaaf7d4ae2ca0ec8c689df03e6f3c5193e31fe2e 100644
--- a/src/timeline/TimelineModel.cpp
+++ b/src/timeline/TimelineModel.cpp
@@ -159,72 +159,96 @@ TimelineModel::TimelineModel(TimelineViewManager *manager, QString room_id, QObj
   , room_id_(room_id)
   , manager_(manager)
 {
+        connect(this,
+                &TimelineModel::oldMessagesRetrieved,
+                this,
+                &TimelineModel::addBackwardsEvents,
+                Qt::QueuedConnection);
         connect(
-          this, &TimelineModel::oldMessagesRetrieved, this, &TimelineModel::addBackwardsEvents);
-        connect(this, &TimelineModel::messageFailed, this, [this](QString txn_id) {
-                nhlog::ui()->error("Failed to send {}, retrying", txn_id.toStdString());
-
-                QTimer::singleShot(5000, this, [this]() { emit nextPendingMessage(); });
-        });
-        connect(this, &TimelineModel::messageSent, this, [this](QString txn_id, QString event_id) {
-                pending.removeOne(txn_id);
+          this,
+          &TimelineModel::messageFailed,
+          this,
+          [this](QString txn_id) {
+                  nhlog::ui()->error("Failed to send {}, retrying", txn_id.toStdString());
 
-                auto ev = events.value(txn_id);
-
-                if (auto reaction =
-                      std::get_if<mtx::events::RoomEvent<mtx::events::msg::Reaction>>(&ev)) {
-                        QString reactedTo =
-                          QString::fromStdString(reaction->content.relates_to.event_id);
-                        auto &rModel = reactions[reactedTo];
-                        rModel.removeReaction(*reaction);
-                        auto rCopy     = *reaction;
-                        rCopy.event_id = event_id.toStdString();
-                        rModel.addReaction(room_id_.toStdString(), rCopy);
-                }
-
-                int idx = idToIndex(txn_id);
-                if (idx < 0) {
-                        // transaction already received via sync
-                        return;
-                }
-                eventOrder[idx] = event_id;
-                ev              = std::visit(
-                  [event_id](const auto &e) -> mtx::events::collections::TimelineEvents {
-                          auto eventCopy     = e;
-                          eventCopy.event_id = event_id.toStdString();
-                          return eventCopy;
-                  },
-                  ev);
+                  QTimer::singleShot(5000, this, [this]() { emit nextPendingMessage(); });
+          },
+          Qt::QueuedConnection);
+        connect(
+          this,
+          &TimelineModel::messageSent,
+          this,
+          [this](QString txn_id, QString event_id) {
+                  pending.removeOne(txn_id);
+
+                  auto ev = events.value(txn_id);
+
+                  if (auto reaction =
+                        std::get_if<mtx::events::RoomEvent<mtx::events::msg::Reaction>>(&ev)) {
+                          QString reactedTo =
+                            QString::fromStdString(reaction->content.relates_to.event_id);
+                          auto &rModel = reactions[reactedTo];
+                          rModel.removeReaction(*reaction);
+                          auto rCopy     = *reaction;
+                          rCopy.event_id = event_id.toStdString();
+                          rModel.addReaction(room_id_.toStdString(), rCopy);
+                  }
 
-                events.remove(txn_id);
-                events.insert(event_id, ev);
+                  int idx = idToIndex(txn_id);
+                  if (idx < 0) {
+                          // transaction already received via sync
+                          return;
+                  }
+                  eventOrder[idx] = event_id;
+                  ev              = std::visit(
+                    [event_id](const auto &e) -> mtx::events::collections::TimelineEvents {
+                            auto eventCopy     = e;
+                            eventCopy.event_id = event_id.toStdString();
+                            return eventCopy;
+                    },
+                    ev);
 
-                // mark our messages as read
-                readEvent(event_id.toStdString());
+                  events.remove(txn_id);
+                  events.insert(event_id, ev);
 
-                emit dataChanged(index(idx, 0), index(idx, 0));
+                  // mark our messages as read
+                  readEvent(event_id.toStdString());
 
-                if (pending.size() > 0)
-                        emit nextPendingMessage();
-        });
-        connect(this, &TimelineModel::redactionFailed, this, [](const QString &msg) {
-                emit ChatPage::instance()->showNotification(msg);
-        });
+                  emit dataChanged(index(idx, 0), index(idx, 0));
 
+                  if (pending.size() > 0)
+                          emit nextPendingMessage();
+          },
+          Qt::QueuedConnection);
         connect(
-          this, &TimelineModel::nextPendingMessage, this, &TimelineModel::processOnePendingMessage);
-        connect(this, &TimelineModel::newMessageToSend, this, &TimelineModel::addPendingMessage);
+          this,
+          &TimelineModel::redactionFailed,
+          this,
+          [](const QString &msg) { emit ChatPage::instance()->showNotification(msg); },
+          Qt::QueuedConnection);
 
         connect(this,
-                &TimelineModel::eventFetched,
+                &TimelineModel::nextPendingMessage,
                 this,
-                [this](QString requestingEvent, mtx::events::collections::TimelineEvents event) {
-                        events.insert(QString::fromStdString(mtx::accessors::event_id(event)),
-                                      event);
-                        auto idx = idToIndex(requestingEvent);
-                        if (idx >= 0)
-                                emit dataChanged(index(idx, 0), index(idx, 0));
-                });
+                &TimelineModel::processOnePendingMessage,
+                Qt::QueuedConnection);
+        connect(this,
+                &TimelineModel::newMessageToSend,
+                this,
+                &TimelineModel::addPendingMessage,
+                Qt::QueuedConnection);
+
+        connect(
+          this,
+          &TimelineModel::eventFetched,
+          this,
+          [this](QString requestingEvent, mtx::events::collections::TimelineEvents event) {
+                  events.insert(QString::fromStdString(mtx::accessors::event_id(event)), event);
+                  auto idx = idToIndex(requestingEvent);
+                  if (idx >= 0)
+                          emit dataChanged(index(idx, 0), index(idx, 0));
+          },
+          Qt::QueuedConnection);
 }
 
 QHash<int, QByteArray>
diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp
index b652b78e7512f79fafc5cd80cb2c350e9e7ece7e..84be895f281c2f8e8b076ce45b284a881e1663b6 100644
--- a/src/timeline/TimelineViewManager.cpp
+++ b/src/timeline/TimelineViewManager.cpp
@@ -1,5 +1,6 @@
 #include "TimelineViewManager.h"
 
+#include <QDesktopServices>
 #include <QMetaType>
 #include <QPalette>
 #include <QQmlContext>
@@ -112,7 +113,10 @@ TimelineViewManager::TimelineViewManager(QSharedPointer<UserSettings> userSettin
         container = view;
         view->setResizeMode(QQuickWidget::SizeRootObjectToView);
         container->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
+
+#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
         view->quickWindow()->setTextRenderType(QQuickWindow::NativeTextRendering);
+#endif
 
         connect(view, &QQuickWidget::statusChanged, this, [](QQuickWidget::Status status) {
                 nhlog::ui()->debug("Status changed to {}", status);
@@ -231,6 +235,12 @@ TimelineViewManager::openImageOverlay(QString mxcUrl, QString eventId) const
         });
 }
 
+void
+TimelineViewManager::openLink(QString link) const
+{
+        QDesktopServices::openUrl(link);
+}
+
 void
 TimelineViewManager::updateReadReceipts(const QString &room_id,
                                         const std::vector<QString> &event_ids)
diff --git a/src/timeline/TimelineViewManager.h b/src/timeline/TimelineViewManager.h
index 5224cd56786cde8a6bad00c4c77bf81407304de2..902dc047be1c7acc829f60c252c70f4903e47394 100644
--- a/src/timeline/TimelineViewManager.h
+++ b/src/timeline/TimelineViewManager.h
@@ -50,6 +50,8 @@ public:
         Q_INVOKABLE QString userPresence(QString id) const;
         Q_INVOKABLE QString userStatus(QString id) const;
 
+        Q_INVOKABLE void openLink(QString link) const;
+
 signals:
         void clearRoomMessageCount(QString roomid);
         void updateRoomsLastMessage(QString roomid, const DescInfo &info);
diff --git a/src/ui/SnackBar.cpp b/src/ui/SnackBar.cpp
index 5daa697ed3dc1f3f24a2aeeda7fd3fc4eeaabcab..51a0ff384579455a470125264b8a1fb8ad5c72bb 100644
--- a/src/ui/SnackBar.cpp
+++ b/src/ui/SnackBar.cpp
@@ -63,7 +63,7 @@ SnackBar::hideMessage()
                 // Moving on to the next message.
                 messages_.pop_front();
 
-        // Reseting the starting position of the widget.
+        // Resetting the starting position of the widget.
         offset_ = STARTING_OFFSET;
 
         if (!messages_.empty())