diff --git a/src/database/entities/channel.rs b/src/database/entities/channel.rs
index 6cef965de94c234c37ef924d6d9381af47421fed..881afd25799e951f722a104b6daa9b27b2e3b3b4 100644
--- a/src/database/entities/channel.rs
+++ b/src/database/entities/channel.rs
@@ -115,6 +115,20 @@ impl Channel {
         let id = self.id();
         let messages = get_collection("messages");
 
+        // Delete any invites.
+        get_collection("invites")
+            .delete_many(
+                doc! {
+                    "channel": id
+                },
+                None,
+            )
+            .await
+            .map_err(|_| Error::DatabaseError {
+                operation: "delete_many",
+                with: "invites",
+            })?;
+
         // Check if there are any attachments we need to delete.
         let message_ids = messages
             .find(
@@ -190,15 +204,45 @@ impl Channel {
 
         // Remove from server object.
         if let Channel::TextChannel { server, .. } = &self {
+            let server = Ref::from_unchecked(server.clone()).fetch_server().await?;
+            let mut unset = doc! {};
+
+            if let Some(sys) = &server.system_messages {
+                if let Some(cid) = &sys.user_joined {
+                    if id == cid {
+                        unset.insert("system_messages.user_joined", 1);
+                    }
+                }
+
+                if let Some(cid) = &sys.user_left {
+                    if id == cid {
+                        unset.insert("system_messages.user_left", 1);
+                    }
+                }
+
+                if let Some(cid) = &sys.user_kicked {
+                    if id == cid {
+                        unset.insert("system_messages.user_kicked", 1);
+                    }
+                }
+
+                if let Some(cid) = &sys.user_banned {
+                    if id == cid {
+                        unset.insert("system_messages.user_banned", 1);
+                    }
+                }
+            }
+
             get_collection("servers")
                 .update_one(
                     doc! {
-                        "_id": server
+                        "_id": server.id
                     },
                     doc! {
                         "$pull": {
                             "channels": id
-                        }
+                        },
+                        "$unset": unset
                     },
                     None,
                 )
diff --git a/src/database/entities/message.rs b/src/database/entities/message.rs
index 7d107ffe965057b47f08c4ae4e01c5a346039fe7..4de99ff9dba54ab866b8b6920998cff440ab56e1 100644
--- a/src/database/entities/message.rs
+++ b/src/database/entities/message.rs
@@ -26,8 +26,14 @@ pub enum SystemMessage {
     UserAdded { id: String, by: String },
     #[serde(rename = "user_remove")]
     UserRemove { id: String, by: String },
+    #[serde(rename = "user_joined")]
+    UserJoined { id: String },
     #[serde(rename = "user_left")]
     UserLeft { id: String },
+    #[serde(rename = "user_kicked")]
+    UserKicked { id: String },
+    #[serde(rename = "user_banned")]
+    UserBanned { id: String },
     #[serde(rename = "channel_renamed")]
     ChannelRenamed { name: String, by: String },
     #[serde(rename = "channel_description_changed")]
diff --git a/src/database/entities/server.rs b/src/database/entities/server.rs
index 5f4eaf0cc18a7fe83b747485418503f08d658414..4984677e50b0f9b17b495a62678a6907625727ad 100644
--- a/src/database/entities/server.rs
+++ b/src/database/entities/server.rs
@@ -34,6 +34,20 @@ pub struct Ban {
     pub reason: Option<String>,
 }
 
+#[derive(Serialize, Deserialize, Debug, Clone)]
+pub struct SystemMessageChannels {
+    pub user_joined: Option<String>,
+    pub user_left: Option<String>,
+    pub user_kicked: Option<String>,
+    pub user_banned: Option<String>
+}
+
+pub enum RemoveMember {
+    Leave,
+    Kick,
+    Ban
+}
+
 #[derive(Serialize, Deserialize, Debug, Clone)]
 pub struct Server {
     #[serde(rename = "_id")]
@@ -46,6 +60,8 @@ pub struct Server {
     #[serde(skip_serializing_if = "Option::is_none")]
     pub description: Option<String>,
     pub channels: Vec<String>,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub system_messages: Option<SystemMessageChannels>,
 
     #[serde(skip_serializing_if = "Option::is_none")]
     pub icon: Option<File>,
@@ -296,10 +312,19 @@ impl Server {
         }
         .publish(self.id.clone());
 
+        if let Some(channels) = &self.system_messages {
+            if let Some(cid) = &channels.user_joined {
+                let channel = Ref::from_unchecked(cid.clone()).fetch_channel().await?;
+                Content::SystemMessage(SystemMessage::UserJoined { id: id.to_string() })
+                    .send_as_system(&channel)
+                    .await?;
+            }
+        }
+
         Ok(())
     }
 
-    pub async fn remove_member(&self, id: &str) -> Result<()> {
+    pub async fn remove_member(&self, id: &str, removal: RemoveMember) -> Result<()> {
         let result = get_collection("server_members")
             .delete_one(
                 doc! {
@@ -322,6 +347,39 @@ impl Server {
                 user: id.to_string(),
             }
             .publish(self.id.clone());
+
+            if let Some(channels) = &self.system_messages {
+                let message = match removal {
+                    RemoveMember::Leave => {
+                        if let Some(cid) = &channels.user_left {
+                            Some((cid.clone(), SystemMessage::UserLeft { id: id.to_string() }))
+                        } else {
+                            None
+                        }
+                    }
+                    RemoveMember::Kick => {
+                        if let Some(cid) = &channels.user_kicked {
+                            Some((cid.clone(), SystemMessage::UserKicked { id: id.to_string() }))
+                        } else {
+                            None
+                        }
+                    }
+                    RemoveMember::Ban => {
+                        if let Some(cid) = &channels.user_banned {
+                            Some((cid.clone(), SystemMessage::UserBanned { id: id.to_string() }))
+                        } else {
+                            None
+                        }
+                    }
+                };
+
+                if let Some((cid, message)) = message {
+                    let channel = Ref::from_unchecked(cid).fetch_channel().await?;
+                    Content::SystemMessage(message)
+                        .send_as_system(&channel)
+                        .await?;
+                }
+            }
         }
 
         Ok(())
diff --git a/src/routes/servers/ban_create.rs b/src/routes/servers/ban_create.rs
index b14a814b94dd2eeeaf8bf51f29a76b8a8702a586..b54d68ca845789380cdf46909e0aa242e9ee0ffe 100644
--- a/src/routes/servers/ban_create.rs
+++ b/src/routes/servers/ban_create.rs
@@ -49,5 +49,5 @@ pub async fn req(user: User, server: Ref, target: Ref, data: Json<Data>) -> Resu
             with: "server_ban",
         })?;
 
-    server.remove_member(&target.id).await
+    server.remove_member(&target.id, RemoveMember::Ban).await
 }
diff --git a/src/routes/servers/member_remove.rs b/src/routes/servers/member_remove.rs
index cac0807d3341743137373769dfd0b7a0d70cd171..fa2ba9c941d3a38984423d9e3cc1d9c6fe50c026 100644
--- a/src/routes/servers/member_remove.rs
+++ b/src/routes/servers/member_remove.rs
@@ -25,5 +25,5 @@ pub async fn req(user: User, target: Ref, member: String) -> Result<()> {
         return Err(Error::MissingPermission);
     }
 
-    target.remove_member(&member.id.user).await
+    target.remove_member(&member.id.user, RemoveMember::Kick).await
 }
diff --git a/src/routes/servers/server_create.rs b/src/routes/servers/server_create.rs
index 12cd741ac04cd94f21f4652ddb881baa2abc8017..55846d1a6d001984b864d4888c1c8918f5ddeaaf 100644
--- a/src/routes/servers/server_create.rs
+++ b/src/routes/servers/server_create.rs
@@ -52,6 +52,14 @@ pub async fn req(user: User, info: Json<Data>) -> Result<JsonValue> {
         name: info.name,
         description: info.description,
         channels: vec![cid.clone()],
+        system_messages: Some(
+            SystemMessageChannels {
+                user_joined: Some(cid.clone()),
+                user_left: Some(cid.clone()),
+                user_kicked: Some(cid.clone()),
+                user_banned: Some(cid.clone())
+            }
+        ),
 
         icon: None,
         banner: None,
diff --git a/src/routes/servers/server_delete.rs b/src/routes/servers/server_delete.rs
index e558c4edd895d93a5a04fa1d5bffe74c70f0e4bf..e335b81c945a246c1ad903a4a833b804a2919f24 100644
--- a/src/routes/servers/server_delete.rs
+++ b/src/routes/servers/server_delete.rs
@@ -18,6 +18,6 @@ pub async fn req(user: User, target: Ref) -> Result<()> {
     if user.id == target.owner {
         target.delete().await
     } else {
-        target.remove_member(&user.id).await
+        target.remove_member(&user.id, RemoveMember::Leave).await
     }
 }