diff --git a/src/Cache.cpp b/src/Cache.cpp
index 0d75ac51c735978bb43050d3ceb9deab678b5d16..2178bbfbb991a6cce57062244589e5a2a92f5ac8 100644
--- a/src/Cache.cpp
+++ b/src/Cache.cpp
@@ -1681,6 +1681,27 @@ Cache::storeEvent(const std::string &room_id,
         txn.commit();
 }
 
+void
+Cache::replaceEvent(const std::string &room_id,
+                    const std::string &event_id,
+                    const mtx::events::collections::TimelineEvent &event)
+{
+        auto txn         = lmdb::txn::begin(env_);
+        auto eventsDb    = getEventsDb(txn, room_id);
+        auto relationsDb = getRelationsDb(txn, room_id);
+        auto event_json  = mtx::accessors::serialize_event(event.data).dump();
+
+        {
+                eventsDb.del(txn, event_id);
+                eventsDb.put(txn, event_id, event_json);
+                for (auto relation : mtx::accessors::relations(event.data).relations) {
+                        relationsDb.put(txn, relation.event_id, event_id);
+                }
+        }
+
+        txn.commit();
+}
+
 std::vector<std::string>
 Cache::relatedEvents(const std::string &room_id, const std::string &event_id)
 {
diff --git a/src/Cache_p.h b/src/Cache_p.h
index f29116220c250462cc328e628cce11e394f2d5be..669f1895212150e537cc70c486d51446aa617d5b 100644
--- a/src/Cache_p.h
+++ b/src/Cache_p.h
@@ -184,6 +184,9 @@ public:
         void storeEvent(const std::string &room_id,
                         const std::string &event_id,
                         const mtx::events::collections::TimelineEvent &event);
+        void replaceEvent(const std::string &room_id,
+                          const std::string &event_id,
+                          const mtx::events::collections::TimelineEvent &event);
         std::vector<std::string> relatedEvents(const std::string &room_id,
                                                const std::string &event_id);
 
diff --git a/src/timeline/EventStore.cpp b/src/timeline/EventStore.cpp
index 4a9f0fffac62956048a04c6b820708c466a6fef3..04f7ef7685b2f3daedc60c229d3320a37488c626 100644
--- a/src/timeline/EventStore.cpp
+++ b/src/timeline/EventStore.cpp
@@ -185,6 +185,48 @@ EventStore::EventStore(std::string room_id, QObject *)
           [this](std::string txn_id, std::string event_id) {
                   nhlog::ui()->debug("sent {}", txn_id);
 
+                  // Replace the event_id in pending edits/replies/redactions with the actual
+                  // event_id of this event. This allows one to edit and reply to events that are
+                  // currently pending.
+
+                  // FIXME (introduced by balsoft): this doesn't work for encrypted events, but
+                  // allegedly it's hard to fix so I'll leave my first contribution at that
+                  for (auto related_event_id : cache::client()->relatedEvents(room_id_, txn_id)) {
+                          if (cache::client()->getEvent(room_id_, related_event_id)) {
+                                  auto related_event =
+                                    cache::client()->getEvent(room_id_, related_event_id).value();
+                                  auto relations = mtx::accessors::relations(related_event.data);
+
+                                  // Replace the blockquote in fallback reply
+                                  auto related_text =
+                                    std::get_if<mtx::events::RoomEvent<mtx::events::msg::Text>>(
+                                      &related_event.data);
+                                  if (related_text && relations.reply_to() == txn_id) {
+                                          size_t index =
+                                            related_text->content.formatted_body.find(txn_id);
+                                          if (index != std::string::npos) {
+                                                  related_text->content.formatted_body.replace(
+                                                    index, event_id.length(), event_id);
+                                          }
+                                  }
+
+                                  for (mtx::common::Relation &rel : relations.relations) {
+                                          if (rel.event_id == txn_id)
+                                                  rel.event_id = event_id;
+                                  }
+
+                                  mtx::accessors::set_relations(related_event.data, relations);
+
+                                  cache::client()->replaceEvent(
+                                    room_id_, related_event_id, related_event);
+
+                                  auto idx = idToIndex(related_event_id);
+
+                                  events_by_id_.remove({room_id_, related_event_id});
+                                  events_.remove({room_id_, toInternalIdx(*idx)});
+                          }
+                  }
+
                   http::client()->read_event(
                     room_id_, event_id, [this, event_id](mtx::http::RequestErr err) {
                             if (err) {
@@ -193,6 +235,11 @@ EventStore::EventStore(std::string room_id, QObject *)
                             }
                     });
 
+                  auto idx = idToIndex(event_id);
+
+                  if (idx)
+                          emit dataChanged(*idx, *idx);
+
                   cache::client()->removePendingStatus(room_id_, txn_id);
                   this->current_txn             = "";
                   this->current_txn_error_count = 0;
diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp
index f29f929e1c446636f4e2f74cac61b5d9878daeb6..99547b15b496acb69d6b333e7f1e2603472dd58b 100644
--- a/src/timeline/TimelineModel.cpp
+++ b/src/timeline/TimelineModel.cpp
@@ -375,6 +375,21 @@ TimelineModel::TimelineModel(TimelineViewManager *manager, QString room_id, QObj
         connect(&events, &EventStore::updateFlowEventId, this, [this](std::string event_id) {
                 this->updateFlowEventId(event_id);
         });
+        // When a message is sent, check if the current edit/reply relates to that message,
+        // and update the event_id so that it points to the sent message and not the pending one.
+        connect(&events,
+                &EventStore::messageSent,
+                this,
+                [this](std::string txn_id, std::string event_id) {
+                        if (edit_.toStdString() == txn_id) {
+                                edit_ = QString::fromStdString(event_id);
+                                emit editChanged(edit_);
+                        }
+                        if (reply_.toStdString() == txn_id) {
+                                reply_ = QString::fromStdString(event_id);
+                                emit replyChanged(reply_);
+                        }
+                });
 
         showEventTimer.callOnTimeout(this, &TimelineModel::scrollTimerEvent);
 }
@@ -568,10 +583,8 @@ TimelineModel::data(const mtx::events::collections::TimelineEvents &event, int r
         case IsEdited:
                 return QVariant(relations(event).replaces().has_value());
         case IsEditable:
-                return QVariant(!is_state_event(event) &&
-                                mtx::accessors::sender(event) ==
-                                  http::client()->user_id().to_string() &&
-                                !event_id(event).empty() && event_id(event).front() == '$');
+                return QVariant(!is_state_event(event) && mtx::accessors::sender(event) ==
+                                                            http::client()->user_id().to_string());
         case IsEncrypted: {
                 auto id              = event_id(event);
                 auto encrypted_event = events.get(id, "", false);
@@ -1796,9 +1809,6 @@ TimelineModel::formatMemberEvent(QString id)
 void
 TimelineModel::setEdit(QString newEdit)
 {
-        if (edit_.startsWith('m'))
-                return;
-
         if (newEdit.isEmpty()) {
                 resetEdit();
                 return;