diff --git a/include/mtx/events/common.hpp b/include/mtx/events/common.hpp
index 9da0c615acef8653f0781a25fdc49c3f5b6a8244..b31c663936a1b9db3c87a50936307a6698376439 100644
--- a/include/mtx/events/common.hpp
+++ b/include/mtx/events/common.hpp
@@ -143,31 +143,18 @@ from_json(const nlohmann::json &obj, VideoInfo &info);
 void
 to_json(nlohmann::json &obj, const VideoInfo &info);
 
-//! In reply to data for rich replies (notice and text events)
-struct InReplyTo
-{
-        //! Event id being replied to
-        std::string event_id;
-};
-
-//! Deserialization method needed by @p nlohmann::json.
-void
-from_json(const nlohmann::json &obj, InReplyTo &in_reply_to);
-
-//! Serialization method needed by @p nlohmann::json.
-void
-to_json(nlohmann::json &obj, const InReplyTo &in_reply_to);
-
 //! Definition of rel_type for relations.
 enum class RelationType
 {
-        // m.annotation rel_type
+        //! m.annotation rel_type
         Annotation,
-        // m.reference rel_type
+        //! m.reference rel_type
         Reference,
-        // m.replace rel_type
+        //! m.replace rel_type
         Replace,
-        // not one of the supported types
+        //! im.nheko.relations.v1.in_reply_to rel_type
+        InReplyTo,
+        //! not one of the supported types
         Unsupported
 };
 
@@ -178,38 +165,51 @@ void
 to_json(nlohmann::json &obj, const RelationType &type);
 
 //! Relates to for reactions
-struct RelatesTo
+struct Relation
 {
-        // Type of relation
-        RelationType rel_type;
-        // event id being reacted to
-        std::string event_id;
-        // key is the reaction itself
-        std::optional<std::string> key;
+        //! Type of relation
+        RelationType rel_type = RelationType::Unsupported;
+        //! event id being reacted to
+        std::string event_id = "";
+        //! key is the reaction itself
+        std::optional<std::string> key = std::nullopt;
 };
-
-//! Deserialization method needed by @p nlohmann::json.
 void
-from_json(const nlohmann::json &obj, RelatesTo &relates_to);
-
-//! Serialization method needed by @p nlohmann::json.
+from_json(const nlohmann::json &obj, Relation &relation);
 void
-to_json(nlohmann::json &obj, const RelatesTo &relates_to);
+to_json(nlohmann::json &obj, const Relation &relation);
 
-//! Relates to data for rich replies (notice and text events)
-struct ReplyRelatesTo
+//! Multiple relations for a event
+struct Relations
 {
-        //! What the message is in reply to
-        InReplyTo in_reply_to;
+        //! All the relations for this event
+        std::vector<Relation> relations;
+        //! Flag, if we generated this from relates_to relations or used
+        //! im.nheko.relactions.v1.relations
+        bool synthesized = false;
+
+        std::optional<std::string> reply_to() const;
+        std::optional<std::string> replaces() const;
+        std::optional<std::string> references() const;
+        std::optional<Relation> annotates() const;
 };
 
-//! Deserialization method needed by @p nlohmann::json.
-void
-from_json(const nlohmann::json &obj, ReplyRelatesTo &relates_to);
+/// @brief Parses relations from a content object
+///
+/// @param obj The content object of an event.
+Relations
+parse_relations(const nlohmann::json &obj);
 
-//! Serialization method needed by @p nlohmann::json.
+/// @brief Serializes relations to a content object
+///
+/// @param obj The content object of an event.
 void
-to_json(nlohmann::json &obj, const ReplyRelatesTo &relates_to);
+add_relations(nlohmann::json &obj, const Relations &relations);
 
+/// @brief Applies also all the edit rules to the event in addition to adding the relations
+///
+/// @param obj The content object of an event.
+void
+apply_relations(nlohmann::json &obj, const Relations &relations);
 } // namespace common
 } // namespace mtx
diff --git a/include/mtx/events/encrypted.hpp b/include/mtx/events/encrypted.hpp
index 246416908ef78ac23490248ddb31c69d03eda0e8..7eeef31b11e807e94c679eac71956bdb446e15ff 100644
--- a/include/mtx/events/encrypted.hpp
+++ b/include/mtx/events/encrypted.hpp
@@ -81,7 +81,6 @@ from_json(const nlohmann::json &obj, OlmEncrypted &event);
 void
 to_json(nlohmann::json &obj, const OlmEncrypted &event);
 
-// !TODO Change the RelatesTo to handle ReplyRelatesTo type of event
 //! Content of the `m.room.encrypted` event.
 struct Encrypted
 {
@@ -95,10 +94,8 @@ struct Encrypted
         std::string sender_key;
         //! Outbound group session id.
         std::string session_id;
-        //! Relates to for rich replies
-        common::ReplyRelatesTo relates_to;
-        //! Relates to used for verification messages
-        common::RelatesTo r_relates_to;
+        //! Relations like rich replies
+        common::Relations relations;
 };
 
 void
@@ -271,7 +268,7 @@ struct KeyVerificationStart
         ///
         /// @note Will be used only for room-verification msgs where this is used in place of
         /// transaction_id.
-        std::optional<mtx::common::RelatesTo> relates_to;
+        common::Relations relations;
 };
 
 void
@@ -292,7 +289,7 @@ struct KeyVerificationReady
         //! this is used for relating this message with previously sent
         //! key.verification.request will be used only for room-verification msgs where this
         //! is used in place of txnid
-        std::optional<mtx::common::RelatesTo> relates_to;
+        common::Relations relations;
 };
 
 void
@@ -308,7 +305,7 @@ struct KeyVerificationDone
         std::optional<std::string> transaction_id;
         //! this is used for relating this message with previously sent key.verification.request
         //! will be used only for room-verification msgs where this is used in place of txnid
-        std::optional<mtx::common::RelatesTo> relates_to;
+        common::Relations relations;
 };
 
 void
@@ -344,7 +341,7 @@ struct KeyVerificationAccept
         std::string commitment;
         //! this is used for relating this message with previously sent key.verification.request
         //! will be used only for room-verification msgs where this is used in place of txnid
-        std::optional<mtx::common::RelatesTo> relates_to;
+        common::Relations relations;
 };
 
 void
@@ -389,7 +386,7 @@ struct KeyVerificationCancel
         std::string code;
         //! this is used for relating this message with previously sent key.verification.request
         //! will be used only for room-verification msgs where this is used in place of txnid
-        std::optional<mtx::common::RelatesTo> relates_to;
+        common::Relations relations;
 };
 
 void
@@ -407,7 +404,7 @@ struct KeyVerificationKey
         std::string key;
         //! this is used for relating this message with previously sent key.verification.request
         //! will be used only for room-verification msgs where this is used in place of txnid
-        std::optional<mtx::common::RelatesTo> relates_to;
+        common::Relations relations;
 };
 
 void
@@ -429,7 +426,7 @@ struct KeyVerificationMac
         std::string keys;
         //! this is used for relating this message with previously sent key.verification.request
         //! will be used only for room-verification msgs where this is used in place of txnid
-        std::optional<mtx::common::RelatesTo> relates_to;
+        common::Relations relations;
 };
 
 void
diff --git a/include/mtx/events/messages/audio.hpp b/include/mtx/events/messages/audio.hpp
index 0c7207cfbf424f89f879e6e68df28e2f39b740f1..4e2df4c90e0b64d88a149587fadf669d31f55d93 100644
--- a/include/mtx/events/messages/audio.hpp
+++ b/include/mtx/events/messages/audio.hpp
@@ -34,7 +34,7 @@ struct Audio
         //! Encryption members. If present, they replace url.
         std::optional<crypto::EncryptedFile> file;
         //! Relates to for rich replies
-        mtx::common::ReplyRelatesTo relates_to;
+        mtx::common::Relations relations;
 };
 
 void
diff --git a/include/mtx/events/messages/emote.hpp b/include/mtx/events/messages/emote.hpp
index 5ab5d2a5b527b2774ddafe9ff26348fa08778911..47e291c9915ce4b5186c1a5cd0d282e7eaa122ec 100644
--- a/include/mtx/events/messages/emote.hpp
+++ b/include/mtx/events/messages/emote.hpp
@@ -29,7 +29,7 @@ struct Emote
         //! HTML formatted message.
         std::string formatted_body;
         //! Relates to for rich replies
-        mtx::common::ReplyRelatesTo relates_to;
+        mtx::common::Relations relations;
 };
 
 void
diff --git a/include/mtx/events/messages/file.hpp b/include/mtx/events/messages/file.hpp
index baa794fccd05ddcd9f2c983995c46603160ad541..f5b23df07c77e6840a153927bc8ef1ca7c6eedfc 100644
--- a/include/mtx/events/messages/file.hpp
+++ b/include/mtx/events/messages/file.hpp
@@ -39,7 +39,7 @@ struct File
         //! Encryption members. If present, they replace url.
         std::optional<crypto::EncryptedFile> file;
         //! Relates to for rich replies
-        mtx::common::ReplyRelatesTo relates_to;
+        mtx::common::Relations relations;
 };
 
 void
diff --git a/include/mtx/events/messages/image.hpp b/include/mtx/events/messages/image.hpp
index 8307da28d87cdd41c02c1f0909393da1e1ffe8b3..f64f3338694603ba0572ae01595b5185a378d225 100644
--- a/include/mtx/events/messages/image.hpp
+++ b/include/mtx/events/messages/image.hpp
@@ -35,7 +35,7 @@ struct Image
         // Encryption members. If present, they replace url.
         std::optional<crypto::EncryptedFile> file;
         //! Relates to for rich replies
-        mtx::common::ReplyRelatesTo relates_to;
+        mtx::common::Relations relations;
 };
 
 //! Content of `m.sticker`.
@@ -52,7 +52,7 @@ struct StickerImage
         // Encryption members. If present, they replace url.
         std::optional<crypto::EncryptedFile> file;
         //! Relates to for rich replies
-        mtx::common::ReplyRelatesTo relates_to;
+        mtx::common::Relations relations;
 };
 
 void
diff --git a/include/mtx/events/messages/notice.hpp b/include/mtx/events/messages/notice.hpp
index b222f85e20ce444e0080af36fcdf52b635a3dead..c67024fac5132b0cb75a6c044e84a971e4c8f01a 100644
--- a/include/mtx/events/messages/notice.hpp
+++ b/include/mtx/events/messages/notice.hpp
@@ -29,7 +29,7 @@ struct Notice
         //! HTML formatted message.
         std::string formatted_body;
         //! Relates to for rich replies
-        mtx::common::ReplyRelatesTo relates_to;
+        mtx::common::Relations relations;
 };
 
 void
diff --git a/include/mtx/events/messages/text.hpp b/include/mtx/events/messages/text.hpp
index a64a749775710cb730718f2a8b67af3786a4152f..3b60e051309030618b580f0af2782fe2c2422f28 100644
--- a/include/mtx/events/messages/text.hpp
+++ b/include/mtx/events/messages/text.hpp
@@ -30,7 +30,7 @@ struct Text
         //! HTML formatted message.
         std::string formatted_body;
         //! Relates to for rich replies
-        mtx::common::ReplyRelatesTo relates_to;
+        mtx::common::Relations relations;
 };
 
 void
diff --git a/include/mtx/events/messages/video.hpp b/include/mtx/events/messages/video.hpp
index 1b33bb8d36363ba46ca8c868645eedbffcee9a87..80ec1f76fc65da34bd34caea74954f35fa3cc018 100644
--- a/include/mtx/events/messages/video.hpp
+++ b/include/mtx/events/messages/video.hpp
@@ -34,7 +34,7 @@ struct Video
         //! Encryption members. If present, they replace url.
         std::optional<crypto::EncryptedFile> file;
         //! Relates to for rich replies
-        mtx::common::ReplyRelatesTo relates_to;
+        mtx::common::Relations relations;
 };
 
 void
diff --git a/include/mtx/events/reaction.hpp b/include/mtx/events/reaction.hpp
index e8f8106f885f49a42e16586bffcdcc6533c3de4d..320354a8a590a91f7cf2c495755ab7e182fa5377 100644
--- a/include/mtx/events/reaction.hpp
+++ b/include/mtx/events/reaction.hpp
@@ -20,8 +20,8 @@ namespace msg {
 //! Content for the `m.reaction` event.
 struct Reaction
 {
-        //! The event being reacted to
-        mtx::common::RelatesTo relates_to;
+        //! Should be an annotation relation
+        common::Relations relations;
 };
 
 void
diff --git a/include/mtx/events_impl.hpp b/include/mtx/events_impl.hpp
index 9ba6bf0612cf71181749c752dcb12788b4e1d8ad..6ff565d9532980b92397ce3782df683d2a629939 100644
--- a/include/mtx/events_impl.hpp
+++ b/include/mtx/events_impl.hpp
@@ -5,6 +5,18 @@
 #include "mtx/events/unknown.hpp"
 
 namespace mtx::events {
+namespace detail {
+
+template<typename, typename = void>
+struct can_edit : std::false_type
+{};
+
+template<typename Content>
+struct can_edit<Content, std::void_t<decltype(Content::relations)>>
+  : std::is_same<decltype(Content::relations), mtx::common::Relations>
+{};
+}
+
 template<class Content>
 [[gnu::used, llvm::used]] void
 to_json(json &obj, const Event<Content> &event)
@@ -21,9 +33,20 @@ template<class Content>
 [[gnu::used, llvm::used]] void
 from_json(const json &obj, Event<Content> &event)
 {
-        event.content = obj.at("content").get<Content>();
-        event.type    = getEventType(obj.at("type").get<std::string>());
-        event.sender  = obj.value("sender", "");
+        if (obj.at("content").contains("m.new_content")) {
+                auto new_content = obj.at("content");
+                for (const auto &e : obj["content"]["m.new_content"].items()) {
+                        if (e.key() != "m.relates_to" &&
+                            e.key() != "im.nheko.relations.v1.relations")
+                                new_content[e.key()] = e.value();
+                }
+                event.content = new_content.get<Content>();
+        } else {
+                event.content = obj.at("content").get<Content>();
+        }
+
+        event.type   = getEventType(obj.at("type").get<std::string>());
+        event.sender = obj.value("sender", "");
 
         if constexpr (std::is_same_v<Unknown, Content>)
                 event.content.type = obj.at("type").get<std::string>();
diff --git a/lib/structs/events/common.cpp b/lib/structs/events/common.cpp
index 4551e1dbd6b86c4b6cb7b19634356674cba7c7c1..e8e080e04197e5e5133d3e31593546f2c98d80f3 100644
--- a/lib/structs/events/common.cpp
+++ b/lib/structs/events/common.cpp
@@ -169,19 +169,6 @@ to_json(json &obj, const VideoInfo &info)
                 obj["xyz.amorgan.blurhash"] = info.blurhash;
 }
 
-void
-from_json(const json &obj, InReplyTo &in_reply_to)
-{
-        if (obj.find("event_id") != obj.end())
-                in_reply_to.event_id = obj.at("event_id").get<std::string>();
-}
-
-void
-to_json(json &obj, const InReplyTo &in_reply_to)
-{
-        obj["event_id"] = in_reply_to.event_id;
-}
-
 void
 to_json(json &obj, const RelationType &type)
 {
@@ -195,6 +182,9 @@ to_json(json &obj, const RelationType &type)
         case RelationType::Replace:
                 obj = "m.replace";
                 break;
+        case RelationType::InReplyTo:
+                obj = "im.nheko.relations.v1.in_reply_to";
+                break;
         case RelationType::Unsupported:
         default:
                 obj = "unsupported";
@@ -211,12 +201,128 @@ from_json(const json &obj, RelationType &type)
                 type = RelationType::Reference;
         else if (obj.get<std::string>() == "m.replace")
                 type = RelationType::Replace;
+        else if (obj.get<std::string>() == "im.nheko.relations.v1.in_reply_to")
+                type = RelationType::InReplyTo;
         else
                 type = RelationType::Unsupported;
 }
 
+Relations
+parse_relations(const nlohmann::json &content)
+{
+        try {
+                if (content.contains("im.nheko.relations.v1.relations")) {
+                        Relations rels;
+                        rels.relations = content.at("im.nheko.relations.v1.relations")
+                                           .get<std::vector<mtx::common::Relation>>();
+                        rels.synthesized = false;
+                        return rels;
+                } else if (content.contains("m.relates_to")) {
+                        if (content.at("m.relates_to").contains("m.in_reply_to")) {
+                                Relation r;
+                                r.event_id = content.at("m.relates_to")
+                                               .at("m.in_reply_to")
+                                               .at("event_id")
+                                               .get<std::string>();
+                                r.rel_type = RelationType::InReplyTo;
+
+                                Relations rels;
+                                rels.relations.push_back(r);
+                                rels.synthesized = true;
+                                return rels;
+                        } else {
+                                Relation r =
+                                  content.at("m.relates_to").get<mtx::common::Relation>();
+                                Relations rels;
+                                rels.relations.push_back(r);
+                                rels.synthesized = true;
+
+                                if (r.rel_type == RelationType::Replace &&
+                                    content.contains("m.new_content") &&
+                                    content.at("m.new_content").contains("m.relates_to")) {
+                                        const auto secondRel =
+                                          content["m.new_content"]["m.relates_to"];
+                                        if (secondRel.contains("m.in_reply_to")) {
+                                                Relation r2{};
+                                                r.rel_type = RelationType::InReplyTo;
+                                                r.event_id = secondRel.at("m.in_reply_to")
+                                                               .at("event_id")
+                                                               .get<std::string>();
+                                                rels.relations.push_back(r2);
+                                        } else {
+                                                rels.relations.push_back(secondRel.get<Relation>());
+                                        }
+                                }
+
+                                return rels;
+                        }
+                }
+        } catch (nlohmann::json &e) {
+        }
+        return {};
+}
+
 void
-from_json(const json &obj, RelatesTo &relates_to)
+add_relations(nlohmann::json &content, const Relations &relations)
+{
+        if (relations.relations.empty())
+                return;
+
+        std::optional<Relation> edit, not_edit;
+        for (const auto &r : relations.relations) {
+                if (r.rel_type == RelationType::Replace)
+                        edit = r;
+                else
+                        not_edit = r;
+        }
+
+        if (not_edit) {
+                if (not_edit->rel_type == RelationType::InReplyTo) {
+                        content["m.relates_to"]["m.in_reply_to"]["event_id"] = not_edit->event_id;
+                } else {
+                        content["m.relates_to"] = *not_edit;
+                }
+        }
+
+        if (edit) {
+                if (not_edit)
+                        content["m.new_content"]["m.relates_to"] = content["m.relates_to"];
+                content["m.relates_to"] = *edit;
+        }
+
+        if (!relations.synthesized) {
+                for (const auto &r : relations.relations) {
+                        if (r.rel_type != RelationType::Unsupported)
+                                content["im.nheko.relations.v1.relations"].push_back(r);
+                }
+        }
+}
+void
+apply_relations(nlohmann::json &content, const Relations &relations)
+{
+        add_relations(content, relations);
+
+        if (relations.replaces()) {
+                for (const auto &e : content.items()) {
+                        if (e.key() != "m.relates_to" &&
+                            e.key() != "im.nheko.relations.v1.relations" &&
+                            e.key() != "m.new_content") {
+                                content["m.new_content"][e.key()] = e.value();
+                        }
+                }
+
+                if (content.contains("body")) {
+                        content["body"] = "* " + content["body"].get<std::string>();
+                }
+                if (content.contains("formatted_body")) {
+                        content["formatted_body"] =
+                          "* " + content["formatted_body"].get<std::string>();
+                }
+        }
+}
+
+void
+from_json(const json &obj, Relation &relates_to)
 {
         if (obj.find("rel_type") != obj.end())
                 relates_to.rel_type = obj.at("rel_type").get<RelationType>();
@@ -227,7 +333,7 @@ from_json(const json &obj, RelatesTo &relates_to)
 }
 
 void
-to_json(json &obj, const RelatesTo &relates_to)
+to_json(json &obj, const Relation &relates_to)
 {
         obj["rel_type"] = relates_to.rel_type;
         obj["event_id"] = relates_to.event_id;
@@ -235,18 +341,36 @@ to_json(json &obj, const RelatesTo &relates_to)
                 obj["key"] = relates_to.key.value();
 }
 
-void
-from_json(const json &obj, ReplyRelatesTo &relates_to)
+static inline std::optional<std::string>
+return_first_relation_matching(RelationType t, const Relations &rels)
 {
-        if (obj.find("m.in_reply_to") != obj.end())
-                relates_to.in_reply_to = obj.at("m.in_reply_to").get<InReplyTo>();
+        for (const auto &r : rels.relations)
+                if (r.rel_type == t)
+                        return r.event_id;
+        return std::nullopt;
 }
-
-void
-to_json(json &obj, const ReplyRelatesTo &relates_to)
+std::optional<std::string>
+Relations::reply_to() const
 {
-        obj["m.in_reply_to"] = relates_to.in_reply_to;
+        return return_first_relation_matching(RelationType::InReplyTo, *this);
+}
+std::optional<std::string>
+Relations::replaces() const
+{
+        return return_first_relation_matching(RelationType::Replace, *this);
+}
+std::optional<std::string>
+Relations::references() const
+{
+        return return_first_relation_matching(RelationType::Reference, *this);
+}
+std::optional<Relation>
+Relations::annotates() const
+{
+        for (const auto &r : relations)
+                if (r.rel_type == RelationType::Annotation)
+                        return r;
+        return std::nullopt;
 }
-
 } // namespace common
 } // namespace mtx
diff --git a/lib/structs/events/encrypted.cpp b/lib/structs/events/encrypted.cpp
index 432f365dfb927c0d0ed3f08c0d3bf3f77810caa0..04d80dee765d6ea9d10fde0378b2be9bbeb6699f 100644
--- a/lib/structs/events/encrypted.cpp
+++ b/lib/structs/events/encrypted.cpp
@@ -100,12 +100,7 @@ from_json(const json &obj, Encrypted &content)
         content.device_id  = obj.at("device_id").get<std::string>();
         content.sender_key = obj.at("sender_key").get<std::string>();
         content.session_id = obj.at("session_id").get<std::string>();
-        if (obj.count("m.relates_to") != 0) {
-                if (obj.at("m.relates_to").contains("m.in_reply_to"))
-                        content.relates_to = obj.at("m.relates_to").get<common::ReplyRelatesTo>();
-                else
-                        content.r_relates_to = obj.at("m.relates_to").get<common::RelatesTo>();
-        }
+        content.relations  = common::parse_relations(obj);
 }
 
 void
@@ -117,10 +112,8 @@ to_json(json &obj, const Encrypted &content)
         obj["sender_key"] = content.sender_key;
         obj["session_id"] = content.session_id;
 
-        if (!content.relates_to.in_reply_to.event_id.empty())
-                obj["m.relates_to"] = content.relates_to;
-        if (!content.r_relates_to.event_id.empty())
-                obj["m.relates_to"] = content.r_relates_to;
+        // For encrypted events, only add releations, don't generate new_content and friends
+        common::add_relations(obj, content.relations);
 }
 
 void
@@ -272,8 +265,7 @@ from_json(const json &obj, KeyVerificationStart &event)
           obj.at("message_authentication_codes").get<std::vector<std::string>>();
         event.short_authentication_string =
           obj.at("short_authentication_string").get<std::vector<SASMethods>>();
-        if (obj.count("m.relates_to") != 0)
-                event.relates_to = obj.at("m.relates_to").get<common::RelatesTo>();
+        event.relations = common::parse_relations(obj);
 }
 
 void
@@ -289,8 +281,7 @@ to_json(json &obj, const KeyVerificationStart &event)
         obj["hashes"]                       = event.hashes;
         obj["message_authentication_codes"] = event.message_authentication_codes;
         obj["short_authentication_string"]  = event.short_authentication_string;
-        if (event.relates_to.has_value())
-                obj["m.relates_to"] = event.relates_to.value();
+        common::apply_relations(obj, event.relations);
 }
 
 void
@@ -301,8 +292,7 @@ from_json(const json &obj, KeyVerificationReady &event)
         }
         event.methods     = obj.at("methods").get<std::vector<VerificationMethods>>();
         event.from_device = obj.at("from_device").get<std::string>();
-        if (obj.count("m.relates_to") != 0)
-                event.relates_to = obj.at("m.relates_to").get<common::RelatesTo>();
+        event.relations   = common::parse_relations(obj);
 }
 
 void
@@ -312,8 +302,7 @@ to_json(json &obj, const KeyVerificationReady &event)
         if (event.transaction_id.has_value())
                 obj["transaction_id"] = event.transaction_id.value();
         obj["from_device"] = event.from_device;
-        if (event.relates_to.has_value())
-                obj["m.relates_to"] = event.relates_to.value();
+        common::apply_relations(obj, event.relations);
 }
 
 void
@@ -322,8 +311,7 @@ from_json(const nlohmann::json &obj, KeyVerificationDone &event)
         if (obj.count("transaction_id") != 0) {
                 event.transaction_id = obj.at("transaction_id").get<std::string>();
         }
-        if (obj.count("m.relates_to") != 0)
-                event.relates_to = obj.at("m.relates_to").get<common::RelatesTo>();
+        event.relations = common::parse_relations(obj);
 }
 
 void
@@ -331,8 +319,7 @@ to_json(nlohmann::json &obj, const KeyVerificationDone &event)
 {
         if (event.transaction_id.has_value())
                 obj["transaction_id"] = event.transaction_id.value();
-        if (event.relates_to.has_value())
-                obj["m.relates_to"] = event.relates_to.value();
+        common::apply_relations(obj, event.relations);
 }
 
 void
@@ -349,8 +336,7 @@ from_json(const json &obj, KeyVerificationAccept &event)
           obj.at("short_authentication_string").get<std::vector<SASMethods>>();
         event.commitment = obj.at("commitment").get<std::string>();
         event.method     = obj.value("method", VerificationMethods::SASv1);
-        if (obj.count("m.relates_to") != 0)
-                event.relates_to = obj.at("m.relates_to").get<common::RelatesTo>();
+        event.relations  = common::parse_relations(obj);
 }
 
 void
@@ -364,8 +350,7 @@ to_json(json &obj, const KeyVerificationAccept &event)
         obj["short_authentication_string"] = event.short_authentication_string;
         obj["commitment"]                  = event.commitment;
         obj["method"]                      = event.method;
-        if (event.relates_to.has_value())
-                obj["m.relates_to"] = event.relates_to.value();
+        common::apply_relations(obj, event.relations);
 }
 
 void
@@ -374,10 +359,9 @@ from_json(const json &obj, KeyVerificationCancel &event)
         if (obj.count("transaction_id") != 0) {
                 event.transaction_id = obj.at("transaction_id").get<std::string>();
         }
-        event.reason = obj.value("reason", "");
-        event.code   = obj.value("code", "");
-        if (obj.count("m.relates_to") != 0)
-                event.relates_to = obj.at("m.relates_to").get<common::RelatesTo>();
+        event.reason    = obj.value("reason", "");
+        event.code      = obj.value("code", "");
+        event.relations = common::parse_relations(obj);
 }
 
 void
@@ -387,8 +371,7 @@ to_json(json &obj, const KeyVerificationCancel &event)
                 obj["transaction_id"] = event.transaction_id.value();
         obj["reason"] = event.reason;
         obj["code"]   = event.code;
-        if (event.relates_to.has_value())
-                obj["m.relates_to"] = event.relates_to.value();
+        common::apply_relations(obj, event.relations);
 }
 
 void
@@ -397,9 +380,8 @@ from_json(const json &obj, KeyVerificationKey &event)
         if (obj.count("transaction_id") != 0) {
                 event.transaction_id = obj.at("transaction_id").get<std::string>();
         }
-        event.key = obj.at("key").get<std::string>();
-        if (obj.count("m.relates_to") != 0)
-                event.relates_to = obj.at("m.relates_to").get<common::RelatesTo>();
+        event.key       = obj.at("key").get<std::string>();
+        event.relations = common::parse_relations(obj);
 }
 
 void
@@ -408,8 +390,7 @@ to_json(json &obj, const KeyVerificationKey &event)
         if (event.transaction_id.has_value())
                 obj["transaction_id"] = event.transaction_id.value();
         obj["key"] = event.key;
-        if (event.relates_to.has_value())
-                obj["m.relates_to"] = event.relates_to.value();
+        common::apply_relations(obj, event.relations);
 }
 
 void
@@ -418,10 +399,9 @@ from_json(const json &obj, KeyVerificationMac &event)
         if (obj.count("transaction_id") != 0) {
                 event.transaction_id = obj.at("transaction_id").get<std::string>();
         }
-        event.mac  = obj.at("mac").get<std::map<std::string, std::string>>();
-        event.keys = obj.at("keys").get<std::string>();
-        if (obj.count("m.relates_to") != 0)
-                event.relates_to = obj.at("m.relates_to").get<common::RelatesTo>();
+        event.mac       = obj.at("mac").get<std::map<std::string, std::string>>();
+        event.keys      = obj.at("keys").get<std::string>();
+        event.relations = common::parse_relations(obj);
 }
 
 void
@@ -431,8 +411,7 @@ to_json(json &obj, const KeyVerificationMac &event)
                 obj["transaction_id"] = event.transaction_id.value();
         obj["mac"]  = event.mac;
         obj["keys"] = event.keys;
-        if (event.relates_to.has_value())
-                obj["m.relates_to"] = event.relates_to.value();
+        common::apply_relations(obj, event.relations);
 }
 
 void
diff --git a/lib/structs/events/messages/audio.cpp b/lib/structs/events/messages/audio.cpp
index 87fbe37608fd4c831730a708bc9f69eaeabaf4a6..c44a85b845e3255302fa7890b8396ab14dc54357 100644
--- a/lib/structs/events/messages/audio.cpp
+++ b/lib/structs/events/messages/audio.cpp
@@ -26,8 +26,7 @@ from_json(const json &obj, Audio &content)
         if (obj.find("file") != obj.end())
                 content.file = obj.at("file").get<crypto::EncryptedFile>();
 
-        if (obj.count("m.relates_to") != 0)
-                content.relates_to = obj.at("m.relates_to").get<common::ReplyRelatesTo>();
+        content.relations = common::parse_relations(obj);
 }
 
 void
@@ -42,8 +41,7 @@ to_json(json &obj, const Audio &content)
         else
                 obj["url"] = content.url;
 
-        if (!content.relates_to.in_reply_to.event_id.empty())
-                obj["m.relates_to"] = content.relates_to;
+        common::apply_relations(obj, content.relations);
 }
 
 } // namespace msg
diff --git a/lib/structs/events/messages/emote.cpp b/lib/structs/events/messages/emote.cpp
index 7a15fd5c54b5228d19250fee855396ca5d297ae1..abe97d75129535b9882a87de22dd37f6da029dac 100644
--- a/lib/structs/events/messages/emote.cpp
+++ b/lib/structs/events/messages/emote.cpp
@@ -22,8 +22,7 @@ from_json(const json &obj, Emote &content)
         if (obj.count("formatted_body") != 0)
                 content.formatted_body = obj.at("formatted_body").get<std::string>();
 
-        if (obj.count("m.relates_to") != 0)
-                content.relates_to = obj.at("m.relates_to").get<common::ReplyRelatesTo>();
+        content.relations = common::parse_relations(obj);
 }
 
 void
@@ -37,8 +36,7 @@ to_json(json &obj, const Emote &content)
                 obj["formatted_body"] = content.formatted_body;
         }
 
-        if (!content.relates_to.in_reply_to.event_id.empty())
-                obj["m.relates_to"] = content.relates_to;
+        common::apply_relations(obj, content.relations);
 }
 
 } // namespace msg
diff --git a/lib/structs/events/messages/file.cpp b/lib/structs/events/messages/file.cpp
index abc80e343e70b0e73d7fdb1b7001ee0eaf43df73..ead6edbaf957afda9c7e8bafcd7c8d923437b71d 100644
--- a/lib/structs/events/messages/file.cpp
+++ b/lib/structs/events/messages/file.cpp
@@ -29,25 +29,25 @@ from_json(const json &obj, File &content)
         if (obj.find("file") != obj.end())
                 content.file = obj.at("file").get<crypto::EncryptedFile>();
 
-        if (obj.count("m.relates_to") != 0)
-                content.relates_to = obj.at("m.relates_to").get<common::ReplyRelatesTo>();
+        content.relations = common::parse_relations(obj);
 }
 
 void
 to_json(json &obj, const File &content)
 {
-        obj["msgtype"]  = "m.file";
-        obj["body"]     = content.body;
-        obj["filename"] = content.filename;
-        obj["info"]     = content.info;
+        obj["msgtype"] = "m.file";
+        obj["body"]    = content.body;
+
+        if (!content.filename.empty())
+                obj["filename"] = content.filename;
+        obj["info"] = content.info;
 
         if (content.file)
                 obj["file"] = content.file.value();
         else
                 obj["url"] = content.url;
 
-        if (!content.relates_to.in_reply_to.event_id.empty())
-                obj["m.relates_to"] = content.relates_to;
+        common::apply_relations(obj, content.relations);
 }
 
 } // namespace msg
diff --git a/lib/structs/events/messages/image.cpp b/lib/structs/events/messages/image.cpp
index 1d9fe2c28b51297983f727ab789a9dd13eff8eaf..4e4acf7ed3286fa63f29be23ddd78837021917a2 100644
--- a/lib/structs/events/messages/image.cpp
+++ b/lib/structs/events/messages/image.cpp
@@ -25,8 +25,7 @@ from_json(const json &obj, Image &content)
         if (obj.find("file") != obj.end())
                 content.file = obj.at("file").get<crypto::EncryptedFile>();
 
-        if (obj.count("m.relates_to") != 0)
-                content.relates_to = obj.at("m.relates_to").get<common::ReplyRelatesTo>();
+        content.relations = common::parse_relations(obj);
 }
 
 void
@@ -41,8 +40,7 @@ to_json(json &obj, const Image &content)
         else
                 obj["url"] = content.url;
 
-        if (!content.relates_to.in_reply_to.event_id.empty())
-                obj["m.relates_to"] = content.relates_to;
+        common::apply_relations(obj, content.relations);
 }
 
 void
@@ -58,8 +56,7 @@ from_json(const json &obj, StickerImage &content)
         if (obj.find("file") != obj.end())
                 content.file = obj.at("file").get<crypto::EncryptedFile>();
 
-        if (obj.count("m.relates_to") != 0)
-                content.relates_to = obj.at("m.relates_to").get<common::ReplyRelatesTo>();
+        content.relations = common::parse_relations(obj);
 }
 
 void
@@ -73,8 +70,7 @@ to_json(json &obj, const StickerImage &content)
         else
                 obj["url"] = content.url;
 
-        if (!content.relates_to.in_reply_to.event_id.empty())
-                obj["m.relates_to"] = content.relates_to;
+        common::apply_relations(obj, content.relations);
 }
 
 } // namespace msg
diff --git a/lib/structs/events/messages/notice.cpp b/lib/structs/events/messages/notice.cpp
index cd59cf38629db3fb1cdbbc95072d1a2e74b45643..258bf8edba06d517259ffd9b85bbb25b6c634826 100644
--- a/lib/structs/events/messages/notice.cpp
+++ b/lib/structs/events/messages/notice.cpp
@@ -22,8 +22,7 @@ from_json(const json &obj, Notice &content)
         if (obj.count("formatted_body") != 0)
                 content.formatted_body = obj.at("formatted_body").get<std::string>();
 
-        if (obj.count("m.relates_to") != 0)
-                content.relates_to = obj.at("m.relates_to").get<common::ReplyRelatesTo>();
+        content.relations = common::parse_relations(obj);
 }
 
 void
@@ -37,7 +36,7 @@ to_json(json &obj, const Notice &content)
                 obj["formatted_body"] = content.formatted_body;
         }
 
-        obj["m.relates_to"] = content.relates_to;
+        common::apply_relations(obj, content.relations);
 }
 
 } // namespace msg
diff --git a/lib/structs/events/messages/text.cpp b/lib/structs/events/messages/text.cpp
index 5c0bf7a75fcd6c08219f5ca78ab3604c821676ba..dd80187d9ab3289af013ca3b1c16cda941a1791c 100644
--- a/lib/structs/events/messages/text.cpp
+++ b/lib/structs/events/messages/text.cpp
@@ -22,8 +22,7 @@ from_json(const json &obj, Text &content)
         if (obj.count("formatted_body") != 0)
                 content.formatted_body = obj.at("formatted_body").get<std::string>();
 
-        if (obj.count("m.relates_to") != 0)
-                content.relates_to = obj.at("m.relates_to").get<common::ReplyRelatesTo>();
+        content.relations = common::parse_relations(obj);
 }
 
 void
@@ -37,8 +36,7 @@ to_json(json &obj, const Text &content)
                 obj["formatted_body"] = content.formatted_body;
         }
 
-        if (!content.relates_to.in_reply_to.event_id.empty())
-                obj["m.relates_to"] = content.relates_to;
+        common::apply_relations(obj, content.relations);
 }
 
 } // namespace msg
diff --git a/lib/structs/events/messages/video.cpp b/lib/structs/events/messages/video.cpp
index 884184a60255e4506f53ff4c0a338f396a82f03c..3f8fb52f1731cb8a43a7000079cb38db2bdd1b9d 100644
--- a/lib/structs/events/messages/video.cpp
+++ b/lib/structs/events/messages/video.cpp
@@ -27,8 +27,7 @@ from_json(const json &obj, Video &content)
         if (obj.find("file") != obj.end())
                 content.file = obj.at("file").get<crypto::EncryptedFile>();
 
-        if (obj.count("m.relates_to") != 0)
-                content.relates_to = obj.at("m.relates_to").get<common::ReplyRelatesTo>();
+        content.relations = common::parse_relations(obj);
 }
 
 void
@@ -43,8 +42,7 @@ to_json(json &obj, const Video &content)
         else
                 obj["url"] = content.url;
 
-        if (!content.relates_to.in_reply_to.event_id.empty())
-                obj["m.relates_to"] = content.relates_to;
+        common::apply_relations(obj, content.relations);
 }
 
 } // namespace msg
diff --git a/lib/structs/events/reaction.cpp b/lib/structs/events/reaction.cpp
index c897478db4d9fb513fa7db5a8970e8eff0536dd7..43c68342f79c6ec300d496181dcbbae52aab5a7d 100644
--- a/lib/structs/events/reaction.cpp
+++ b/lib/structs/events/reaction.cpp
@@ -10,16 +10,17 @@ namespace events {
 namespace msg {
 
 void
-from_json(const json &obj, Reaction &event)
+from_json(const json &obj, Reaction &content)
 {
-        if (obj.count("m.relates_to") != 0)
-                event.relates_to = obj.at("m.relates_to").get<common::RelatesTo>();
+        content.relations = common::parse_relations(obj);
 }
 
 void
-to_json(json &obj, const Reaction &event)
+to_json(json &obj, const Reaction &content)
 {
-        obj["m.relates_to"] = event.relates_to;
+        obj = nlohmann::json::object();
+
+        common::apply_relations(obj, content.relations);
 }
 
 } // namespace msg
diff --git a/tests/messages.cpp b/tests/messages.cpp
index c98ab6a790c1fd728fdaa666db02afb1e2705e76..649f833398816000e8b8a188d3301f33f8784bc3 100644
--- a/tests/messages.cpp
+++ b/tests/messages.cpp
@@ -35,10 +35,11 @@ TEST(RoomEvents, Reaction)
         EXPECT_EQ(event.sender, "@example:localhost");
         EXPECT_EQ(event.origin_server_ts, 1588536414112L);
         EXPECT_EQ(event.unsigned_data.age, 1905609L);
-        EXPECT_EQ(event.content.relates_to.event_id,
+        EXPECT_EQ(event.content.relations.relations.at(0).event_id,
                   "$oGKg0tfsnDamWPsGxUptGLWR5b8Xq6QNFFsysQNSnake");
-        EXPECT_EQ(event.content.relates_to.key, "👀");
-        EXPECT_EQ(event.content.relates_to.rel_type, mtx::common::RelationType::Annotation);
+        EXPECT_EQ(event.content.relations.relations.at(0).key, "👀");
+        EXPECT_EQ(event.content.relations.relations.at(0).rel_type,
+                  mtx::common::RelationType::Annotation);
 
         EXPECT_EQ(data.dump(), json(event).dump());
 }
@@ -112,8 +113,10 @@ TEST(RoomEvents, AudioMessage)
         EXPECT_EQ(event.content.info.mimetype, "audio/mpeg");
         EXPECT_EQ(event.content.info.size, 1563685);
         EXPECT_EQ(event.content.info.duration, 2140786);
-        EXPECT_EQ(event.content.relates_to.in_reply_to.event_id,
+        EXPECT_EQ(event.content.relations.reply_to().value(),
                   "$6GKhAfJOcwNd69lgSizdcTob8z2pWQgBOZPrnsWMA1E");
+        EXPECT_EQ(event.content.relations.relations.at(0).rel_type,
+                  mtx::common::RelationType::InReplyTo);
 }
 
 TEST(RoomEvents, EmoteMessage)
@@ -148,8 +151,10 @@ TEST(RoomEvents, EmoteMessage)
         EXPECT_EQ(event.unsigned_data.age, 626351821);
         EXPECT_EQ(event.content.body, "tests");
         EXPECT_EQ(event.content.msgtype, "m.emote");
-        EXPECT_EQ(event.content.relates_to.in_reply_to.event_id,
+        EXPECT_EQ(event.content.relations.relations.at(0).event_id,
                   "$6GKhAfJOcwNd69lgSizdcTob8z2pWQgBOZPrnsWMA1E");
+        EXPECT_EQ(event.content.relations.relations.at(0).rel_type,
+                  mtx::common::RelationType::InReplyTo);
 }
 
 TEST(RoomEvents, FileMessage)
@@ -202,8 +207,10 @@ TEST(RoomEvents, FileMessage)
         EXPECT_EQ(event.content.url, "mxc://matrix.org/XpxykZBESCSQnYkLKbbIKnVn");
         EXPECT_EQ(event.content.info.mimetype, "application/pdf");
         EXPECT_EQ(event.content.info.size, 40565);
-        EXPECT_EQ(event.content.relates_to.in_reply_to.event_id,
+        EXPECT_EQ(event.content.relations.relations.at(0).event_id,
                   "$6GKhAfJOcwNd69lgSizdcTob8z2pWQgBOZPrnsWMA1E");
+        EXPECT_EQ(event.content.relations.relations.at(0).rel_type,
+                  mtx::common::RelationType::InReplyTo);
 
         json withThumb = event;
         EXPECT_EQ(withThumb["content"]["info"].count("thumbnail_url"), 1);
@@ -350,8 +357,10 @@ TEST(RoomEvents, ImageMessage)
         EXPECT_EQ(event.content.info.thumbnail_info.w, 474);
         EXPECT_EQ(event.content.info.thumbnail_info.h, 302);
         EXPECT_EQ(event.content.info.thumbnail_info.size, 33504);
-        EXPECT_EQ(event.content.relates_to.in_reply_to.event_id,
+        EXPECT_EQ(event.content.relations.relations.at(0).event_id,
                   "$6GKhAfJOcwNd69lgSizdcTob8z2pWQgBOZPrnsWMA1E");
+        EXPECT_EQ(event.content.relations.relations.at(0).rel_type,
+                  mtx::common::RelationType::InReplyTo);
 
         json withThumb = event;
         EXPECT_EQ(withThumb["content"]["info"].count("thumbnail_url"), 1);
@@ -415,8 +424,10 @@ TEST(RoomEvents, ImageMessage)
         EXPECT_EQ(event.content.info.thumbnail_info.w, 0);
         EXPECT_EQ(event.content.info.thumbnail_info.h, 0);
         EXPECT_EQ(event.content.info.thumbnail_info.size, 0);
-        EXPECT_EQ(event.content.relates_to.in_reply_to.event_id,
+        EXPECT_EQ(event.content.relations.relations.at(0).event_id,
                   "$6GKhAfJOcwNd69lgSizdcTob8z2pWQgBOZPrnsWMA1E");
+        EXPECT_EQ(event.content.relations.relations.at(0).rel_type,
+                  mtx::common::RelationType::InReplyTo);
 }
 
 TEST(RoomEvents, LocationMessage) {}
@@ -454,8 +465,10 @@ TEST(RoomEvents, NoticeMessage)
         EXPECT_EQ(event.content.body,
                   "https://github.com/postmarketOS/pmbootstrap/issues/900 : Package nheko");
         EXPECT_EQ(event.content.msgtype, "m.notice");
-        EXPECT_EQ(event.content.relates_to.in_reply_to.event_id,
+        EXPECT_EQ(event.content.relations.relations.at(0).event_id,
                   "$6GKhAfJOcwNd69lgSizdcTob8z2pWQgBOZPrnsWMA1E");
+        EXPECT_EQ(event.content.relations.relations.at(0).rel_type,
+                  mtx::common::RelationType::InReplyTo);
 }
 
 TEST(RoomEvents, TextMessage)
@@ -493,8 +506,10 @@ TEST(RoomEvents, TextMessage)
 
         EXPECT_EQ(event.content.body, "hey there");
         EXPECT_EQ(event.content.msgtype, "m.text");
-        EXPECT_EQ(event.content.relates_to.in_reply_to.event_id,
+        EXPECT_EQ(event.content.relations.relations.at(0).event_id,
                   "$6GKhAfJOcwNd69lgSizdcTob8z2pWQgBOZPrnsWMA1E");
+        EXPECT_EQ(event.content.relations.relations.at(0).rel_type,
+                  mtx::common::RelationType::InReplyTo);
 
         EXPECT_EQ(data.dump(), json(event).dump());
 }
@@ -556,8 +571,10 @@ TEST(RoomEvents, VideoMessage)
         EXPECT_EQ(event.content.info.thumbnail_info.h, 300);
         EXPECT_EQ(event.content.info.thumbnail_info.w, 310);
         EXPECT_EQ(event.content.info.thumbnail_info.size, 46144);
-        EXPECT_EQ(event.content.relates_to.in_reply_to.event_id,
+        EXPECT_EQ(event.content.relations.relations.at(0).event_id,
                   "$6GKhAfJOcwNd69lgSizdcTob8z2pWQgBOZPrnsWMA1E");
+        EXPECT_EQ(event.content.relations.relations.at(0).rel_type,
+                  mtx::common::RelationType::InReplyTo);
 }
 
 TEST(RoomEvents, Sticker)
@@ -602,8 +619,10 @@ TEST(RoomEvents, Sticker)
         EXPECT_EQ(event.content.info.w, 140);
         EXPECT_EQ(event.content.info.h, 200);
         EXPECT_EQ(event.content.info.size, 73602);
-        EXPECT_EQ(event.content.relates_to.in_reply_to.event_id,
+        EXPECT_EQ(event.content.relations.relations.at(0).event_id,
                   "$6GKhAfJOcwNd69lgSizdcTob8z2pWQgBOZPrnsWMA1E");
+        EXPECT_EQ(event.content.relations.relations.at(0).rel_type,
+                  mtx::common::RelationType::InReplyTo);
 
         json data2     = R"({
 	  "type": "m.sticker",
@@ -742,8 +761,10 @@ TEST(RoomEvents, Encrypted)
         EXPECT_EQ(event.content.device_id, "RJYKSTBOIE");
         EXPECT_EQ(event.content.sender_key, "IlRMeOPX2e0MurIyfWEucYBRVOEEUMrOHqn/8mLqMjA");
         EXPECT_EQ(event.content.session_id, "X3lUlvLELLYxeTx4yOVu6UDpasGEVO0Jbu+QFnm0cKQ");
-        EXPECT_EQ(event.content.relates_to.in_reply_to.event_id,
+        EXPECT_EQ(event.content.relations.relations.at(0).event_id,
                   "$6GKhAfJOcwNd69lgSizdcTob8z2pWQgBOZPrnsWMA1E");
+        EXPECT_EQ(event.content.relations.relations.at(0).rel_type,
+                  mtx::common::RelationType::InReplyTo);
 
         EXPECT_EQ(data, json(event));
 }