diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp
index 35de082ad62e3fbb7b0abef88034f3f5f56b897e..80038d9955a4ebe58f49ca09ee79c396b4da1a05 100644
--- a/src/timeline/TimelineModel.cpp
+++ b/src/timeline/TimelineModel.cpp
@@ -4,6 +4,7 @@
 #include <thread>
 #include <type_traits>
 
+#include <QCache>
 #include <QFileDialog>
 #include <QMimeDatabase>
 #include <QRegularExpression>
@@ -22,6 +23,14 @@
 
 Q_DECLARE_METATYPE(QModelIndex)
 
+namespace std {
+inline uint
+qHash(const std::string &key, uint seed = 0)
+{
+        return qHash(QByteArray::fromRawData(key.data(), key.length()), seed);
+}
+}
+
 namespace {
 struct RoomEventType
 {
@@ -705,6 +714,11 @@ TimelineModel::openUserProfile(QString userid) const
 DecryptionResult
 TimelineModel::decryptEvent(const mtx::events::EncryptedEvent<mtx::events::msg::Encrypted> &e) const
 {
+        static QCache<std::string, DecryptionResult> decryptedEvents{300};
+
+        if (auto cachedEvent = decryptedEvents.object(e.event_id))
+                return *cachedEvent;
+
         MegolmSessionIndex index;
         index.room_id    = room_id_.toStdString();
         index.session_id = e.content.session_id;
@@ -726,6 +740,8 @@ TimelineModel::decryptEvent(const mtx::events::EncryptedEvent<mtx::events::msg::
                                               index.session_id,
                                               e.sender);
                         // TODO: request megolm session_id & session_key from the sender.
+                        decryptedEvents.insert(
+                          dummy.event_id, new DecryptionResult{dummy, false}, 1);
                         return {dummy, false};
                 }
         } catch (const lmdb::error &e) {
@@ -734,6 +750,7 @@ TimelineModel::decryptEvent(const mtx::events::EncryptedEvent<mtx::events::msg::
                                         "Placeholder, when the message can't be decrypted, because "
                                         "the DB access failed when trying to lookup the session.")
                                        .toStdString();
+                decryptedEvents.insert(dummy.event_id, new DecryptionResult{dummy, false}, 1);
                 return {dummy, false};
         }
 
@@ -753,6 +770,7 @@ TimelineModel::decryptEvent(const mtx::events::EncryptedEvent<mtx::events::msg::
                      "Placeholder, when the message can't be decrypted, because the DB access "
                      "failed.")
                     .toStdString();
+                decryptedEvents.insert(dummy.event_id, new DecryptionResult{dummy, false}, 1);
                 return {dummy, false};
         } catch (const mtx::crypto::olm_exception &e) {
                 nhlog::crypto()->critical("failed to decrypt message with index ({}, {}, {}): {}",
@@ -766,6 +784,7 @@ TimelineModel::decryptEvent(const mtx::events::EncryptedEvent<mtx::events::msg::
                      "decrytion returned an error, which is passed ad %1.")
                     .arg(e.what())
                     .toStdString();
+                decryptedEvents.insert(dummy.event_id, new DecryptionResult{dummy, false}, 1);
                 return {dummy, false};
         }
 
@@ -786,14 +805,17 @@ TimelineModel::decryptEvent(const mtx::events::EncryptedEvent<mtx::events::msg::
         std::vector<mtx::events::collections::TimelineEvents> temp_events;
         mtx::responses::utils::parse_timeline_events(event_array, temp_events);
 
-        if (temp_events.size() == 1)
-                return {temp_events.at(0), true};
+        if (temp_events.size() == 1) {
+                decryptedEvents.insert(e.event_id, new DecryptionResult{temp_events[0], true}, 1);
+                return {temp_events[0], true};
+        }
 
         dummy.content.body =
           tr("-- Encrypted Event (Unknown event type) --",
              "Placeholder, when the message was decrypted, but we couldn't parse it, because "
              "Nheko/mtxclient don't support that event type yet.")
             .toStdString();
+        decryptedEvents.insert(dummy.event_id, new DecryptionResult{dummy, false}, 1);
         return {dummy, false};
 }