diff --git a/src/database/entities/message.rs b/src/database/entities/message.rs
index 8cec6ce3c2c33d08e5766bfdbfd5cde26099b86b..13975de4f7c0f678134a482aca9740071fd15bc9 100644
--- a/src/database/entities/message.rs
+++ b/src/database/entities/message.rs
@@ -58,4 +58,24 @@ impl Message {
 
         Ok(())
     }
+
+    pub async fn publish_edit(self) -> Result<()> {
+        let channel = self.channel.clone();
+        ClientboundNotification::MessageEdit(self)
+            .publish(channel)
+            .await
+            .ok();
+
+        Ok(())
+    }
+
+    pub async fn publish_delete(self) -> Result<()> {
+        let channel = self.channel.clone();
+        ClientboundNotification::MessageDelete(self.id)
+            .publish(channel)
+            .await
+            .ok();
+
+        Ok(())
+    }
 }
diff --git a/src/database/guards/reference.rs b/src/database/guards/reference.rs
index a1262c33e1517267ee1fe27f3097c2741a2b241d..829ff244e2bd8d4ec7fc4b4221066cf403ad7b47 100644
--- a/src/database/guards/reference.rs
+++ b/src/database/guards/reference.rs
@@ -1,7 +1,7 @@
 use crate::database::*;
 use crate::util::result::{Error, Result};
 
-use mongodb::bson::{doc, from_bson, from_document, Bson};
+use mongodb::bson::{doc, from_document};
 use rocket::http::RawStr;
 use rocket::request::FromParam;
 use serde::{de::DeserializeOwned, Deserialize, Serialize};
diff --git a/src/database/guards/user.rs b/src/database/guards/user.rs
index c7363d1a3ce6e4b80a34ecea7884cd6c195d29ec..fb8e53dc24f4838c1668169eaaec0ddad1b60b65 100644
--- a/src/database/guards/user.rs
+++ b/src/database/guards/user.rs
@@ -1,6 +1,6 @@
 use crate::database::*;
 
-use mongodb::bson::{doc, from_bson, from_document, Bson};
+use mongodb::bson::{doc, from_document};
 use rauth::auth::Session;
 use rocket::http::Status;
 use rocket::request::{self, FromRequest, Outcome, Request};
diff --git a/src/database/migrations/scripts.rs b/src/database/migrations/scripts.rs
index 64866c036fba29e3de9e5fb4f1866b12cd2d32c6..4f570007328613486597208d77be8643692e152d 100644
--- a/src/database/migrations/scripts.rs
+++ b/src/database/migrations/scripts.rs
@@ -2,7 +2,7 @@ use super::super::{get_collection, get_db};
 
 use crate::rocket::futures::StreamExt;
 use log::info;
-use mongodb::bson::{doc, from_bson, from_document, Bson};
+use mongodb::bson::{doc, from_document};
 use mongodb::options::FindOptions;
 use serde::{Deserialize, Serialize};
 
diff --git a/src/notifications/events.rs b/src/notifications/events.rs
index eeb03a9fe673774761dc097f8f9c6dde608c9cd4..cde3946232aaed49d024a827d0c4904b0d1b82f5 100644
--- a/src/notifications/events.rs
+++ b/src/notifications/events.rs
@@ -37,6 +37,8 @@ pub enum ClientboundNotification {
     },
 
     Message(Message),
+    MessageEdit(Message),
+    MessageDelete(String),
 
     /*MessageCreate {
         id: String,
diff --git a/src/notifications/payload.rs b/src/notifications/payload.rs
index c3b25599e941bbe51d201e38b17d5038a839e257..32ae01034064f7bd08519f8f715dadb3ef05d4a5 100644
--- a/src/notifications/payload.rs
+++ b/src/notifications/payload.rs
@@ -5,7 +5,7 @@ use crate::{
 };
 use futures::StreamExt;
 use mongodb::{
-    bson::{doc, from_bson, from_document, Bson},
+    bson::{doc, from_document},
     options::FindOptions,
 };
 
diff --git a/src/routes/channels/message_edit.rs b/src/routes/channels/message_edit.rs
new file mode 100644
index 0000000000000000000000000000000000000000..ccc2f8a320fcc35efbb38730f438e449e2772edb
--- /dev/null
+++ b/src/routes/channels/message_edit.rs
@@ -0,0 +1,58 @@
+use crate::database::*;
+use crate::util::result::{Error, Result};
+
+use chrono::Utc;
+use mongodb::bson::{Bson, DateTime, doc};
+use rocket_contrib::json::Json;
+use serde::{Deserialize, Serialize};
+use validator::Validate;
+
+#[derive(Validate, Serialize, Deserialize)]
+pub struct Data {
+    #[validate(length(min = 1, max = 2000))]
+    content: String,
+}
+
+#[patch("/<target>/messages/<msg>", data = "<edit>")]
+pub async fn req(user: User, target: Ref, msg: Ref, edit: Json<Data>) -> Result<()> {
+    edit.validate()
+        .map_err(|error| Error::FailedValidation { error })?;
+
+    let channel = target.fetch_channel().await?;
+
+    let perm = permissions::channel::calculate(&user, &channel).await;
+    if !perm.get_view() {
+        Err(Error::LabelMe)?
+    }
+
+    let mut message = msg.fetch_message().await?;
+    if message.author != user.id {
+        Err(Error::CannotEditMessage)?
+    }
+
+    let edited = Utc::now();
+    get_collection("messages")
+        .update_one(
+            doc! {
+                "_id": &message.id
+            },
+            doc! {
+                "$set": {
+                    "content": &edit.content,
+                    "edited": Bson::DateTime(edited)
+                }
+            },
+            None,
+        )
+        .await
+        .map_err(|_| Error::DatabaseError {
+            operation: "update_one",
+            with: "message",
+        })?;
+
+    message.content = edit.content.clone();
+    message.edited = Some(DateTime(edited));
+    message.publish_edit().await?;
+
+    Ok(())
+}
diff --git a/src/routes/channels/mod.rs b/src/routes/channels/mod.rs
index fbd9e7d0ad5f245be1a42418bd640e79e987ba94..f8885574c592ca6c209c29d3d18bee1027db8e28 100644
--- a/src/routes/channels/mod.rs
+++ b/src/routes/channels/mod.rs
@@ -2,6 +2,7 @@ use rocket::Route;
 
 mod delete_channel;
 mod fetch_channel;
+mod message_edit;
 mod message_fetch;
 mod message_query;
 mod message_send;
@@ -12,6 +13,7 @@ pub fn routes() -> Vec<Route> {
         delete_channel::req,
         message_send::req,
         message_query::req,
-        message_fetch::req
+        message_fetch::req,
+        message_edit::req
     ]
 }
diff --git a/src/util/result.rs b/src/util/result.rs
index 2afc5460d9a53c3478c40482030be55b74c11d7a..0f2dec321d949d3f5e96673f19ec9f3d61103408 100644
--- a/src/util/result.rs
+++ b/src/util/result.rs
@@ -34,6 +34,8 @@ pub enum Error {
     // ? Channel related errors.
     #[snafu(display("Already sent a message with this nonce."))]
     AlreadySentMessage,
+    #[snafu(display("Cannot edit someone else's message."))]
+    CannotEditMessage,
 
     // ? General errors.
     #[snafu(display("Failed to validate fields."))]
@@ -67,6 +69,7 @@ impl<'r> Responder<'r, 'static> for Error {
             Error::BlockedByOther => Status::Forbidden,
 
             Error::AlreadySentMessage => Status::Conflict,
+            Error::CannotEditMessage => Status::Forbidden,
 
             Error::FailedValidation { .. } => Status::UnprocessableEntity,
             Error::DatabaseError { .. } => Status::InternalServerError,