diff --git a/.dockerignore b/.dockerignore
index 796603f34902b0cef04fd36fb06bf5102bfeb762..8f7866a8a44b8235ad9fe5c16f18bf14349ad678 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -1,2 +1,3 @@
 target
+.mongo
 .env
\ No newline at end of file
diff --git a/src/database/entities/autumn.rs b/src/database/entities/autumn.rs
new file mode 100644
index 0000000000000000000000000000000000000000..7ae1102cdab2aa31f20ae0e5ea1d5feb59ca7123
--- /dev/null
+++ b/src/database/entities/autumn.rs
@@ -0,0 +1,21 @@
+use serde::{Serialize, Deserialize};
+
+#[derive(Serialize, Deserialize, Debug, Clone)]
+#[serde(tag = "type")]
+enum Metadata {
+    File,
+    Image { width: isize, height: isize },
+    Video { width: isize, height: isize },
+    Audio,
+}
+
+#[derive(Serialize, Deserialize, Debug, Clone)]
+pub struct File {
+    #[serde(rename = "_id")]
+    pub id: String,
+    filename: String,
+    metadata: Metadata,
+    content_type: String,
+
+    message_id: Option<String>
+}
diff --git a/src/database/entities/message.rs b/src/database/entities/message.rs
index 8594f40052d37b8d1742dc1ea3a99240be18a09d..ee26c40d00c84d0ac79fca5fea9a26f84035bc3b 100644
--- a/src/database/entities/message.rs
+++ b/src/database/entities/message.rs
@@ -19,6 +19,8 @@ pub struct Message {
 
     pub content: String,
     #[serde(skip_serializing_if = "Option::is_none")]
+    pub attachment: Option<File>,
+    #[serde(skip_serializing_if = "Option::is_none")]
     pub edited: Option<DateTime>,
 }
 
@@ -31,6 +33,7 @@ impl Message {
             author,
 
             content,
+            attachment: None,
             edited: None,
         }
     }
diff --git a/src/database/entities/mod.rs b/src/database/entities/mod.rs
index d64512a278f511c6b5bf9a974e0882ca7e36a1c5..864ee9ca7106616d663a78aa7f4b980316aa7ff2 100644
--- a/src/database/entities/mod.rs
+++ b/src/database/entities/mod.rs
@@ -2,8 +2,10 @@ mod channel;
 mod guild;
 mod message;
 mod user;
+mod autumn;
 
 pub use channel::*;
 pub use guild::*;
 pub use message::*;
 pub use user::*;
+pub use autumn::*;
diff --git a/src/routes/channels/message_send.rs b/src/routes/channels/message_send.rs
index 1a7df5591c2a182e8f870f2f4bce05635ff389e6..66c9e94cafd335b50b5ba5aef800d1ad76803072 100644
--- a/src/routes/channels/message_send.rs
+++ b/src/routes/channels/message_send.rs
@@ -1,7 +1,7 @@
 use crate::database::*;
 use crate::util::result::{Error, Result};
 
-use mongodb::bson::doc;
+use mongodb::{bson::{doc, from_document}, options::FindOneOptions};
 use rocket_contrib::json::{Json, JsonValue};
 use serde::{Deserialize, Serialize};
 use ulid::Ulid;
@@ -14,6 +14,8 @@ pub struct Data {
     // Maximum length of 36 allows both ULIDs and UUIDs.
     #[validate(length(min = 1, max = 36))]
     nonce: String,
+    #[validate(length(min = 1, max = 128))]
+    attachment: Option<String>,
 }
 
 #[post("/<target>/messages", data = "<message>")]
@@ -37,7 +39,9 @@ pub async fn req(user: User, target: Ref, message: Json<Data>) -> Result<JsonVal
             doc! {
                 "nonce": &message.nonce
             },
-            None,
+            FindOneOptions::builder()
+                .projection(doc! { "_id": 1 })
+                .build()
         )
         .await
         .map_err(|_| Error::DatabaseError {
@@ -49,12 +53,56 @@ pub async fn req(user: User, target: Ref, message: Json<Data>) -> Result<JsonVal
         Err(Error::DuplicateNonce)?
     }
 
+    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,
+                    "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)
+        }
+    } else {
+        None
+    };
+
     let msg = Message {
-        id: Ulid::new().to_string(),
+        id,
         channel: target.id().to_string(),
         author: user.id,
 
         content: message.content.clone(),
+        attachment,
         nonce: Some(message.nonce.clone()),
         edited: None,
     };
diff --git a/src/routes/root.rs b/src/routes/root.rs
index 70ff4a51909c0a923c975817a32f89b555a6b199..9242cafcd21e7a4e6edbae17e9dabc6c2c15d73b 100644
--- a/src/routes/root.rs
+++ b/src/routes/root.rs
@@ -1,5 +1,5 @@
 use crate::util::variables::{
-    DISABLE_REGISTRATION, EXTERNAL_WS_URL, HCAPTCHA_SITEKEY, INVITE_ONLY, USE_EMAIL, USE_HCAPTCHA,
+    DISABLE_REGISTRATION, EXTERNAL_WS_URL, HCAPTCHA_SITEKEY, INVITE_ONLY, USE_EMAIL, USE_HCAPTCHA, USE_AUTUMN, AUTUMN_URL
 };
 
 use mongodb::bson::doc;
@@ -16,7 +16,11 @@ pub async fn root() -> JsonValue {
                 "key": HCAPTCHA_SITEKEY.to_string()
             },
             "email": *USE_EMAIL,
-            "invite_only": *INVITE_ONLY
+            "invite_only": *INVITE_ONLY,
+            "autumn": {
+                "enabled": *USE_AUTUMN,
+                "url": *AUTUMN_URL
+            }
         },
         "ws": *EXTERNAL_WS_URL,
     })
diff --git a/src/util/result.rs b/src/util/result.rs
index 4a548032b98e18d88791d1c62c2053b36754cdc6..e69c48a5e7696da6b84888ddfe72719a431ba451 100644
--- a/src/util/result.rs
+++ b/src/util/result.rs
@@ -36,6 +36,8 @@ pub enum Error {
     // ? Channel related errors.
     #[snafu(display("This channel does not exist!"))]
     UnknownChannel,
+    #[snafu(display("Attachment does not exist!"))]
+    UnknownAttachment,
     #[snafu(display("Cannot edit someone else's message."))]
     CannotEditMessage,
     #[snafu(display("Cannot remove yourself from a group, use delete channel instead."))]
@@ -84,6 +86,7 @@ impl<'r> Responder<'r, 'static> for Error {
             Error::NotFriends => Status::Forbidden,
 
             Error::UnknownChannel => Status::NotFound,
+            Error::UnknownAttachment => Status::BadRequest,
             Error::CannotEditMessage => Status::Forbidden,
             Error::CannotRemoveYourself => Status::BadRequest,
             Error::GroupTooLarge { .. } => Status::Forbidden,
diff --git a/src/util/variables.rs b/src/util/variables.rs
index 00f3d020d30fabdf7ca46c2d65518c53395aa407..17a5084aebef5870216101e1994ceacd7d8bfbd4 100644
--- a/src/util/variables.rs
+++ b/src/util/variables.rs
@@ -7,18 +7,20 @@ lazy_static! {
     // Application Settings
     pub static ref MONGO_URI: String =
         env::var("REVOLT_MONGO_URI").expect("Missing REVOLT_MONGO_URI environment variable.");
+    pub static ref WS_HOST: String =
+            env::var("REVOLT_WS_HOST").unwrap_or_else(|_| "0.0.0.0:9000".to_string());
     pub static ref PUBLIC_URL: String =
         env::var("REVOLT_PUBLIC_URL").expect("Missing REVOLT_PUBLIC_URL environment variable.");
     pub static ref APP_URL: String =
         env::var("REVOLT_APP_URL").unwrap_or_else(|_| "https://app.revolt.chat".to_string());
     pub static ref EXTERNAL_WS_URL: String =
         env::var("REVOLT_EXTERNAL_WS_URL").expect("Missing REVOLT_EXTERNAL_WS_URL environment variable.");
+    pub static ref AUTUMN_URL: String =
+        env::var("AUTUMN_PUBLIC_URL").unwrap_or_else(|_| "https://example.com".to_string());
     pub static ref HCAPTCHA_KEY: String =
         env::var("REVOLT_HCAPTCHA_KEY").unwrap_or_else(|_| "0x0000000000000000000000000000000000000000".to_string());
     pub static ref HCAPTCHA_SITEKEY: String =
         env::var("REVOLT_HCAPTCHA_SITEKEY").unwrap_or_else(|_| "10000000-ffff-ffff-ffff-000000000001".to_string());
-    pub static ref WS_HOST: String =
-        env::var("REVOLT_WS_HOST").unwrap_or_else(|_| "0.0.0.0:9000".to_string());
 
     // Application Flags
     pub static ref DISABLE_REGISTRATION: bool = env::var("REVOLT_DISABLE_REGISTRATION").map_or(false, |v| v == "1");
@@ -32,6 +34,7 @@ lazy_static! {
     );
     pub static ref USE_HCAPTCHA: bool = env::var("REVOLT_HCAPTCHA_KEY").is_ok();
     pub static ref USE_PROMETHEUS: bool = env::var("REVOLT_ENABLE_PROMETHEUS").map_or(false, |v| v == "1");
+    pub static ref USE_AUTUMN: bool = env::var("AUTUMN_PUBLIC_URL").is_ok();
 
     // SMTP Settings
     pub static ref SMTP_HOST: String =