diff --git a/include/Cache.h b/include/Cache.h
index 97133b0cfdb7bab872db01953208cb32d920eaa8..76266ebd471277b766db16350433363205c470c2 100644
--- a/include/Cache.h
+++ b/include/Cache.h
@@ -355,7 +355,7 @@ public:
         //
         // Outbound Megolm Sessions
         //
-        void saveOutboundMegolmSession(const MegolmSessionIndex &index,
+        void saveOutboundMegolmSession(const std::string &room_id,
                                        const OutboundGroupSessionData &data,
                                        mtx::crypto::OutboundGroupSessionPtr session);
         OutboundGroupSessionDataRef getOutboundMegolmSession(const MegolmSessionIndex &index);
diff --git a/include/timeline/TimelineView.h b/include/timeline/TimelineView.h
index 88857222d40f9a1832fecb7bceb404d50be3e5f5..2c369d5f74382d3aed944621c95eebd51d2ef0ed 100644
--- a/include/timeline/TimelineView.h
+++ b/include/timeline/TimelineView.h
@@ -48,8 +48,37 @@ struct PendingMessage
         uint64_t media_size;
         QString event_id;
         TimelineItem *widget;
+        bool is_encrypted = false;
 };
 
+template<class MessageT>
+MessageT
+toRoomMessage(const PendingMessage &) = delete;
+
+template<>
+mtx::events::msg::Audio
+toRoomMessage<mtx::events::msg::Audio>(const PendingMessage &m);
+
+template<>
+mtx::events::msg::Emote
+toRoomMessage<mtx::events::msg::Emote>(const PendingMessage &m);
+
+template<>
+mtx::events::msg::File
+toRoomMessage<mtx::events::msg::File>(const PendingMessage &);
+
+template<>
+mtx::events::msg::Image
+toRoomMessage<mtx::events::msg::Image>(const PendingMessage &m);
+
+template<>
+mtx::events::msg::Text
+toRoomMessage<mtx::events::msg::Text>(const PendingMessage &);
+
+template<>
+mtx::events::msg::Video
+toRoomMessage<mtx::events::msg::Video>(const PendingMessage &m);
+
 // In which place new TimelineItems should be inserted.
 enum class TimelineDirection
 {
@@ -318,7 +347,7 @@ TimelineView::addUserMessage(const QString &url,
 
         PendingMessage message;
         message.ty         = MsgType;
-        message.txn_id     = mtx::client::utils::random_token();
+        message.txn_id     = http::v2::client()->generate_txn_id();
         message.body       = url;
         message.filename   = trimmed;
         message.mime       = mime;
diff --git a/src/Cache.cc b/src/Cache.cc
index abc6fae4523589e11bd9fd5cd09dce67dd7a12ac..35ad8f9d761c3fd8a7ee4b56a67a25dba5c2e494 100644
--- a/src/Cache.cc
+++ b/src/Cache.cc
@@ -251,12 +251,11 @@ Cache::inboundMegolmSessionExists(const MegolmSessionIndex &index) noexcept
 }
 
 void
-Cache::saveOutboundMegolmSession(const MegolmSessionIndex &index,
+Cache::saveOutboundMegolmSession(const std::string &room_id,
                                  const OutboundGroupSessionData &data,
                                  mtx::crypto::OutboundGroupSessionPtr session)
 {
         using namespace mtx::crypto;
-        const auto key     = index.to_hash();
         const auto pickled = pickle<OutboundSessionObject>(session.get(), SECRET);
 
         json j;
@@ -264,13 +263,13 @@ Cache::saveOutboundMegolmSession(const MegolmSessionIndex &index,
         j["session"] = pickled;
 
         auto txn = lmdb::txn::begin(env_);
-        lmdb::dbi_put(txn, outboundMegolmSessionDb_, lmdb::val(key), lmdb::val(j.dump()));
+        lmdb::dbi_put(txn, outboundMegolmSessionDb_, lmdb::val(room_id), lmdb::val(j.dump()));
         txn.commit();
 
         {
                 std::unique_lock<std::mutex> lock(session_storage.group_outbound_mtx);
-                session_storage.group_outbound_session_data[key] = data;
-                session_storage.group_outbound_sessions[key]     = std::move(session);
+                session_storage.group_outbound_session_data[room_id] = data;
+                session_storage.group_outbound_sessions[room_id]     = std::move(session);
         }
 }
 
@@ -302,7 +301,7 @@ Cache::saveOutboundOlmSession(const std::string &curve25519, mtx::crypto::OlmSes
         const auto pickled = pickle<SessionObject>(session.get(), SECRET);
 
         auto txn = lmdb::txn::begin(env_);
-        lmdb::dbi_put(txn, outboundMegolmSessionDb_, lmdb::val(curve25519), lmdb::val(pickled));
+        lmdb::dbi_put(txn, outboundOlmSessionDb_, lmdb::val(curve25519), lmdb::val(pickled));
         txn.commit();
 
         {
@@ -372,8 +371,8 @@ Cache::restoreSessions()
                                   unpickle<OutboundSessionObject>(obj.at("session"), SECRET);
                                 session_storage.group_outbound_sessions[key] = std::move(session);
                         } catch (const nlohmann::json::exception &e) {
-                                log::db()->warn("failed to parse outbound megolm session data: {}",
-                                                e.what());
+                                log::db()->critical(
+                                  "failed to parse outbound megolm session data: {}", e.what());
                         }
                 }
                 cursor.close();
diff --git a/src/timeline/TimelineView.cc b/src/timeline/TimelineView.cc
index d004a5430c4538524f5b5b925efc170e04b5b3c3..67da5dc63348292b5022b10b39798c880e4e3316 100644
--- a/src/timeline/TimelineView.cc
+++ b/src/timeline/TimelineView.cc
@@ -285,7 +285,12 @@ TimelineView::parseMessageEvent(const mtx::events::collections::TimelineEvents &
                   parseEncryptedEvent(mpark::get<EncryptedEvent<msg::Encrypted>>(event));
                 return parseMessageEvent(decrypted, direction);
         } else if (mpark::holds_alternative<StateEvent<state::Encryption>>(event)) {
-                cache::client()->setEncryptedRoom(room_id_.toStdString());
+                try {
+                        cache::client()->setEncryptedRoom(room_id_.toStdString());
+                } catch (const lmdb::error &e) {
+                        log::db()->critical("failed to save room {} as encrypted",
+                                            room_id_.toStdString());
+                }
         }
 
         return nullptr;
@@ -588,6 +593,7 @@ TimelineView::addTimelineItem(TimelineItem *item, TimelineDirection direction)
 void
 TimelineView::updatePendingMessage(const std::string &txn_id, const QString &event_id)
 {
+        log::main()->info("[{}] message was received by the server", txn_id);
         if (!pending_msgs_.isEmpty() &&
             pending_msgs_.head().txn_id == txn_id) { // We haven't received it yet
                 auto msg     = pending_msgs_.dequeue();
@@ -613,19 +619,28 @@ TimelineView::addUserMessage(mtx::events::MessageType ty, const QString &body)
         TimelineItem *view_item =
           new TimelineItem(ty, local_user_, body, with_sender, room_id_, scroll_widget_);
 
+        PendingMessage message;
+        message.ty     = ty;
+        message.txn_id = http::v2::client()->generate_txn_id();
+        message.body   = body;
+        message.widget = view_item;
+
+        try {
+                message.is_encrypted = cache::client()->isRoomEncrypted(room_id_.toStdString());
+        } catch (const lmdb::error &e) {
+                log::db()->critical("failed to check encryption status of room {}", e.what());
+                view_item->deleteLater();
+
+                // TODO: Send a notification to the user.
+
+                return;
+        }
+
         addTimelineItem(view_item);
 
         lastMessageDirection_ = TimelineDirection::Bottom;
 
-        QApplication::processEvents();
-
         saveLastMessageInfo(local_user_, QDateTime::currentDateTime());
-
-        PendingMessage message;
-        message.ty     = ty;
-        message.txn_id = mtx::client::utils::random_token();
-        message.body   = body;
-        message.widget = view_item;
         handleNewUserMessage(message);
 }
 
@@ -646,18 +661,21 @@ TimelineView::sendNextPendingMessage()
         using namespace mtx::events;
 
         PendingMessage &m = pending_msgs_.head();
+
+        log::main()->info("[{}] sending next queued message", m.txn_id);
+
+        if (m.is_encrypted) {
+                // sendEncryptedMessage(m);
+                log::main()->info("[{}] sending encrypted event", m.txn_id);
+                return;
+        }
+
         switch (m.ty) {
         case mtx::events::MessageType::Audio: {
-                msg::Audio audio;
-                audio.info.mimetype = m.mime.toStdString();
-                audio.info.size     = m.media_size;
-                audio.body          = m.filename.toStdString();
-                audio.url           = m.body.toStdString();
-
                 http::v2::client()->send_room_message<msg::Audio, EventType::RoomMessage>(
                   room_id_.toStdString(),
                   m.txn_id,
-                  audio,
+                  toRoomMessage<msg::Audio>(m),
                   std::bind(&TimelineView::sendRoomMessageHandler,
                             this,
                             m.txn_id,
@@ -667,16 +685,10 @@ TimelineView::sendNextPendingMessage()
                 break;
         }
         case mtx::events::MessageType::Image: {
-                msg::Image image;
-                image.info.mimetype = m.mime.toStdString();
-                image.info.size     = m.media_size;
-                image.body          = m.filename.toStdString();
-                image.url           = m.body.toStdString();
-
                 http::v2::client()->send_room_message<msg::Image, EventType::RoomMessage>(
                   room_id_.toStdString(),
                   m.txn_id,
-                  image,
+                  toRoomMessage<msg::Image>(m),
                   std::bind(&TimelineView::sendRoomMessageHandler,
                             this,
                             m.txn_id,
@@ -686,16 +698,10 @@ TimelineView::sendNextPendingMessage()
                 break;
         }
         case mtx::events::MessageType::Video: {
-                msg::Video video;
-                video.info.mimetype = m.mime.toStdString();
-                video.info.size     = m.media_size;
-                video.body          = m.filename.toStdString();
-                video.url           = m.body.toStdString();
-
                 http::v2::client()->send_room_message<msg::Video, EventType::RoomMessage>(
                   room_id_.toStdString(),
                   m.txn_id,
-                  video,
+                  toRoomMessage<msg::Video>(m),
                   std::bind(&TimelineView::sendRoomMessageHandler,
                             this,
                             m.txn_id,
@@ -705,16 +711,10 @@ TimelineView::sendNextPendingMessage()
                 break;
         }
         case mtx::events::MessageType::File: {
-                msg::File file;
-                file.info.mimetype = m.mime.toStdString();
-                file.info.size     = m.media_size;
-                file.body          = m.filename.toStdString();
-                file.url           = m.body.toStdString();
-
                 http::v2::client()->send_room_message<msg::File, EventType::RoomMessage>(
                   room_id_.toStdString(),
                   m.txn_id,
-                  file,
+                  toRoomMessage<msg::File>(m),
                   std::bind(&TimelineView::sendRoomMessageHandler,
                             this,
                             m.txn_id,
@@ -724,13 +724,10 @@ TimelineView::sendNextPendingMessage()
                 break;
         }
         case mtx::events::MessageType::Text: {
-                msg::Text text;
-                text.body = m.body.toStdString();
-
                 http::v2::client()->send_room_message<msg::Text, EventType::RoomMessage>(
                   room_id_.toStdString(),
                   m.txn_id,
-                  text,
+                  toRoomMessage<msg::Text>(m),
                   std::bind(&TimelineView::sendRoomMessageHandler,
                             this,
                             m.txn_id,
@@ -740,13 +737,10 @@ TimelineView::sendNextPendingMessage()
                 break;
         }
         case mtx::events::MessageType::Emote: {
-                msg::Emote emote;
-                emote.body = m.body.toStdString();
-
                 http::v2::client()->send_room_message<msg::Emote, EventType::RoomMessage>(
                   room_id_.toStdString(),
                   m.txn_id,
-                  emote,
+                  toRoomMessage<msg::Emote>(m),
                   std::bind(&TimelineView::sendRoomMessageHandler,
                             this,
                             m.txn_id,
@@ -809,13 +803,15 @@ TimelineView::removePendingMessage(const std::string &txn_id)
                         if (pending_sent_msgs_.isEmpty())
                                 sendNextPendingMessage();
 
-                        return;
+                        log::main()->info("[{}] removed message with sync", txn_id);
                 }
         }
         for (auto it = pending_msgs_.begin(); it != pending_msgs_.end(); ++it) {
                 if (it->txn_id == txn_id) {
                         int index = std::distance(pending_msgs_.begin(), it);
                         pending_msgs_.removeAt(index);
+
+                        log::main()->info("[{}] removed message before sync", txn_id);
                         return;
                 }
         }
@@ -1051,9 +1047,80 @@ TimelineView::sendRoomMessageHandler(const std::string &txn_id,
                                      mtx::http::RequestErr err)
 {
         if (err) {
+                const int status_code = static_cast<int>(err->status_code);
+                log::net()->warn("[{}] failed to send message: {} {}",
+                                 txn_id,
+                                 err->matrix_error.error,
+                                 status_code);
                 emit messageFailed(txn_id);
                 return;
         }
 
         emit messageSent(txn_id, QString::fromStdString(res.event_id.to_string()));
 }
+
+template<>
+mtx::events::msg::Audio
+toRoomMessage<mtx::events::msg::Audio>(const PendingMessage &m)
+{
+        mtx::events::msg::Audio audio;
+        audio.info.mimetype = m.mime.toStdString();
+        audio.info.size     = m.media_size;
+        audio.body          = m.filename.toStdString();
+        audio.url           = m.body.toStdString();
+        return audio;
+}
+
+template<>
+mtx::events::msg::Image
+toRoomMessage<mtx::events::msg::Image>(const PendingMessage &m)
+{
+        mtx::events::msg::Image image;
+        image.info.mimetype = m.mime.toStdString();
+        image.info.size     = m.media_size;
+        image.body          = m.filename.toStdString();
+        image.url           = m.body.toStdString();
+        return image;
+}
+
+template<>
+mtx::events::msg::Video
+toRoomMessage<mtx::events::msg::Video>(const PendingMessage &m)
+{
+        mtx::events::msg::Video video;
+        video.info.mimetype = m.mime.toStdString();
+        video.info.size     = m.media_size;
+        video.body          = m.filename.toStdString();
+        video.url           = m.body.toStdString();
+        return video;
+}
+
+template<>
+mtx::events::msg::Emote
+toRoomMessage<mtx::events::msg::Emote>(const PendingMessage &m)
+{
+        mtx::events::msg::Emote emote;
+        emote.body = m.body.toStdString();
+        return emote;
+}
+
+template<>
+mtx::events::msg::File
+toRoomMessage<mtx::events::msg::File>(const PendingMessage &m)
+{
+        mtx::events::msg::File file;
+        file.info.mimetype = m.mime.toStdString();
+        file.info.size     = m.media_size;
+        file.body          = m.filename.toStdString();
+        file.url           = m.body.toStdString();
+        return file;
+}
+
+template<>
+mtx::events::msg::Text
+toRoomMessage<mtx::events::msg::Text>(const PendingMessage &m)
+{
+        mtx::events::msg::Text text;
+        text.body = m.body.toStdString();
+        return text;
+}
diff --git a/src/timeline/TimelineViewManager.cc b/src/timeline/TimelineViewManager.cc
index 9026463d17386142122471986f88d51c245f9a89..b6e7d50d48e337e05f3e2f65a370bf57a15cf929 100644
--- a/src/timeline/TimelineViewManager.cc
+++ b/src/timeline/TimelineViewManager.cc
@@ -22,8 +22,6 @@
 #include <QFileInfo>
 #include <QSettings>
 
-#include "MatrixClient.h"
-
 #include "timeline/TimelineView.h"
 #include "timeline/TimelineViewManager.h"
 #include "timeline/widgets/AudioItem.h"