diff --git a/src/database/entities/autumn.rs b/src/database/entities/autumn.rs
index 10eb7c10a4bb47f16a7ce9a135183f6f9aca2034..8c57250bfdadd26fc5aa33fa6ee44560319bd277 100644
--- a/src/database/entities/autumn.rs
+++ b/src/database/entities/autumn.rs
@@ -1,5 +1,9 @@
+use mongodb::bson::{doc, from_document};
 use serde::{Deserialize, Serialize};
 
+use crate::util::result::{Error, Result};
+use crate::database::*;
+
 #[derive(Serialize, Deserialize, Debug, Clone)]
 #[serde(tag = "type")]
 enum Metadata {
@@ -32,3 +36,54 @@ pub struct File {
     #[serde(skip_serializing_if = "Option::is_none")]
     object_id: Option<String>,
 }
+
+impl File {
+    pub async fn find_and_use(attachment_id: &str, tag: &str, parent_type: &str, parent_id: &str) -> Result<File> {
+        let attachments = get_collection("attachments");
+        let key = format!("{}_id", parent_type);
+        if let Some(doc) = attachments
+            .find_one(
+                doc! {
+                    "_id": attachment_id,
+                    "tag": &tag,
+                    key.clone(): {
+                        "$exists": false
+                    }
+                },
+                None,
+            )
+            .await
+            .map_err(|_| Error::DatabaseError {
+                operation: "find_one",
+                with: "attachment",
+            })?
+        {
+            let attachment = from_document::<File>(doc).map_err(|_| Error::DatabaseError {
+                operation: "from_document",
+                with: "attachment",
+            })?;
+
+            attachments
+                .update_one(
+                    doc! {
+                        "_id": &attachment.id
+                    },
+                    doc! {
+                        "$set": {
+                            key: &parent_id
+                        }
+                    },
+                    None,
+                )
+                .await
+                .map_err(|_| Error::DatabaseError {
+                    operation: "update_one",
+                    with: "attachment",
+                })?;
+
+            Ok(attachment)
+        } else {
+            Err(Error::UnknownAttachment)
+        }
+    }
+}
diff --git a/src/database/entities/user.rs b/src/database/entities/user.rs
index 165e9a163314ab718334285a853dbf42aa5085d5..04220db9c75acca81aa15a11ee57cd912eb6d7a5 100644
--- a/src/database/entities/user.rs
+++ b/src/database/entities/user.rs
@@ -63,6 +63,8 @@ pub struct User {
     pub id: String,
     pub username: String,
     #[serde(skip_serializing_if = "Option::is_none")]
+    pub avatar: Option<File>,
+    #[serde(skip_serializing_if = "Option::is_none")]
     pub relations: Option<Vec<Relationship>>,
 
     #[serde(skip_serializing_if = "Option::is_none")]
diff --git a/src/routes/channels/message_send.rs b/src/routes/channels/message_send.rs
index 742f47643429749a042eed5890194ddeb8437bf3..2984e02d72c2b0372da1d80ae672ca575d3f3ac2 100644
--- a/src/routes/channels/message_send.rs
+++ b/src/routes/channels/message_send.rs
@@ -2,7 +2,7 @@ use crate::database::*;
 use crate::util::result::{Error, Result};
 
 use mongodb::{
-    bson::{doc, from_document},
+    bson::{doc},
     options::FindOneOptions,
 };
 use rocket_contrib::json::{Json, JsonValue};
@@ -61,52 +61,8 @@ pub async fn req(user: User, target: Ref, message: Json<Data>) -> Result<JsonVal
     }
 
     let id = Ulid::new().to_string();
-    let attachments = get_collection("attachments");
     let attachment = if let Some(attachment_id) = &message.attachment {
-        if let Some(doc) = attachments
-            .find_one(
-                doc! {
-                    "_id": attachment_id,
-                    "tag": "attachments",
-                    "message_id": {
-                        "$exists": false
-                    }
-                },
-                None,
-            )
-            .await
-            .map_err(|_| Error::DatabaseError {
-                operation: "find_one",
-                with: "attachment",
-            })?
-        {
-            let attachment = from_document::<File>(doc).map_err(|_| Error::DatabaseError {
-                operation: "from_document",
-                with: "attachment",
-            })?;
-
-            attachments
-                .update_one(
-                    doc! {
-                        "_id": &attachment.id
-                    },
-                    doc! {
-                        "$set": {
-                            "message_id": &id
-                        }
-                    },
-                    None,
-                )
-                .await
-                .map_err(|_| Error::DatabaseError {
-                    operation: "update_one",
-                    with: "attachment",
-                })?;
-
-            Some(attachment)
-        } else {
-            return Err(Error::UnknownAttachment);
-        }
+        Some(File::find_and_use(attachment_id, "attachments", "message", &id).await?)
     } else {
         None
     };
diff --git a/src/routes/users/edit_user.rs b/src/routes/users/edit_user.rs
index ab5e1f6e71c8f1c40eedecde2a521f013c1c6df4..5dfde797ed2d5135c633dbc84d5352430ce5346d 100644
--- a/src/routes/users/edit_user.rs
+++ b/src/routes/users/edit_user.rs
@@ -15,17 +15,35 @@ pub struct Data {
     #[serde(skip_serializing_if = "Option::is_none")]
     #[validate]
     profile: Option<UserProfile>,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    #[validate(length(min = 1, max = 128))]
+    avatar: Option<String>,
 }
 
 #[patch("/<_ignore_id>", data = "<data>")]
-pub async fn req(user: User, data: Json<Data>, _ignore_id: String) -> Result<()> {
+pub async fn req(user: User, mut data: Json<Data>, _ignore_id: String) -> Result<()> {
+    if data.0.status.is_none() && data.0.profile.is_none() && data.0.avatar.is_none() {
+        return Ok(())
+    }
+
     data.validate()
         .map_err(|error| Error::FailedValidation { error })?;
 
+    let mut set = to_document(&data.0).map_err(|_| Error::DatabaseError { operation: "to_document", with: "data" })?;
+
+    let avatar = std::mem::replace(&mut data.0.avatar, None);
+    let attachment = if let Some(attachment_id) = avatar {
+        let attachment = File::find_and_use(&attachment_id, "avatars", "user", &user.id).await?;
+        set.insert("avatar", to_document(&attachment).map_err(|_| Error::DatabaseError { operation: "to_document", with: "attachment" })?);
+        Some(attachment)
+    } else {
+        None
+    };
+
     get_collection("users")
     .update_one(
         doc! { "_id": &user.id },
-        doc! { "$set": to_document(&data.0).map_err(|_| Error::DatabaseError { operation: "to_document", with: "data" })? },
+        doc! { "$set": set },
         None
     )
     .await
@@ -41,5 +59,15 @@ pub async fn req(user: User, data: Json<Data>, _ignore_id: String) -> Result<()>
         .ok();
     }
 
+    if let Some(avatar) = attachment {
+        ClientboundNotification::UserUpdate {
+            id: user.id.clone(),
+            data: json!({ "avatar": avatar }),
+        }
+        .publish(user.id.clone())
+        .await
+        .ok();
+    }
+
     Ok(())
 }
diff --git a/src/routes/users/get_avatar.rs b/src/routes/users/get_avatar.rs
deleted file mode 100644
index d928a4474749d3eaeed89a3c5f6fa837975a0ce7..0000000000000000000000000000000000000000
--- a/src/routes/users/get_avatar.rs
+++ /dev/null
@@ -1,49 +0,0 @@
-use md5;
-use mongodb::bson::doc;
-use mongodb::options::FindOneOptions;
-use rocket::response::Redirect;
-use urlencoding;
-
-use crate::database::*;
-use crate::util::result::{Error, Result};
-use crate::util::variables::PUBLIC_URL;
-
-#[get("/<target>/avatar")]
-pub async fn req(target: Ref) -> Result<Redirect> {
-    let doc = get_collection("accounts")
-        .find_one(
-            doc! {
-                "_id": &target.id
-            },
-            FindOneOptions::builder()
-                .projection(doc! { "email": 1 })
-                .build(),
-        )
-        .await
-        .map_err(|_| Error::DatabaseError {
-            operation: "find_one",
-            with: "user",
-        })?
-        .ok_or_else(|| Error::UnknownUser)?;
-
-    let email = doc
-        .get_str("email")
-        .map_err(|_| Error::DatabaseError {
-            operation: "get_str(email)",
-            with: "user",
-        })?
-        .to_lowercase();
-
-    let url = format!(
-        "https://www.gravatar.com/avatar/{:x}?s=128&d={}",
-        md5::compute(email),
-        urlencoding::encode(&format!(
-            "{}/users/{}/default_avatar",
-            *PUBLIC_URL, &target.id
-        ))
-    );
-
-    dbg!(&url);
-
-    Ok(Redirect::to(url))
-}
diff --git a/src/routes/users/mod.rs b/src/routes/users/mod.rs
index 331186db25f28a91db24d72ffbc5b128a5ec4631..c9805545e3e2218c37c4c93b98c53627a85c0880 100644
--- a/src/routes/users/mod.rs
+++ b/src/routes/users/mod.rs
@@ -10,7 +10,6 @@ mod fetch_relationship;
 mod fetch_relationships;
 mod fetch_user;
 mod find_mutual;
-mod get_avatar;
 mod get_default_avatar;
 mod open_dm;
 mod remove_friend;
@@ -23,7 +22,6 @@ pub fn routes() -> Vec<Route> {
         edit_user::req,
         change_username::req,
         get_default_avatar::req,
-        get_avatar::req,
         fetch_profile::req,
         // Direct Messaging
         fetch_dms::req,