diff --git a/set_version.sh b/set_version.sh
index 44ddcfd6dc885be754d31e0c167709eb219b6116..866f7474c58438c3fb84f147e8a7e2dfba1b8c74 100755
--- a/set_version.sh
+++ b/set_version.sh
@@ -1,3 +1,3 @@
 #!/bin/bash
-export version=0.4.1-alpha.13
+export version=0.4.2-alpha.0
 echo "pub const VERSION: &str = \"${version}\";" > src/version.rs
diff --git a/src/database/entities/message.rs b/src/database/entities/message.rs
index ccd9954489f4e6283645cdfac4a963d8fd64944b..177c5304928450f9899caeb2bc4a1e7a282c2e3d 100644
--- a/src/database/entities/message.rs
+++ b/src/database/entities/message.rs
@@ -54,7 +54,7 @@ pub struct Message {
 
     pub content: Content,
     #[serde(skip_serializing_if = "Option::is_none")]
-    pub attachment: Option<File>,
+    pub attachments: Option<Vec<File>>,
     #[serde(skip_serializing_if = "Option::is_none")]
     pub edited: Option<DateTime>,
     #[serde(skip_serializing_if = "Option::is_none")]
@@ -70,7 +70,7 @@ impl Message {
             author,
 
             content,
-            attachment: None,
+            attachments: None,
             edited: None,
             embeds: None,
         }
@@ -275,8 +275,10 @@ impl Message {
     }
 
     pub async fn delete(&self) -> Result<()> {
-        if let Some(attachment) = &self.attachment {
-            attachment.delete().await?;
+        if let Some(attachments) = &self.attachments {
+            for attachment in attachments {
+                attachment.delete().await?;
+            }
         }
 
         get_collection("messages")
@@ -299,11 +301,14 @@ impl Message {
         }
         .publish(channel);
 
-        if let Some(attachment) = &self.attachment {
+        if let Some(attachments) = &self.attachments {
+            let attachment_ids: Vec<String> = attachments.iter().map(|f| f.id.to_string()).collect();
             get_collection("attachments")
                 .update_one(
                     doc! {
-                        "_id": &attachment.id
+                        "_id": {
+                            "$in": attachment_ids
+                        }
                     },
                     doc! {
                         "$set": {
diff --git a/src/database/migrations/init.rs b/src/database/migrations/init.rs
index fa1ab83a51bf3df5583e665458e5bbf562569890..d089c0d8c8f35cd01bbaf5cd3d0500cefccef077 100644
--- a/src/database/migrations/init.rs
+++ b/src/database/migrations/init.rs
@@ -37,6 +37,14 @@ pub async fn create_database() {
         .await
         .expect("Failed to create attachments collection.");
 
+    db.create_collection("channel_unreads", None)
+        .await
+        .expect("Failed to create channel_unreads collection.");
+
+    db.create_collection("user_settings", None)
+        .await
+        .expect("Failed to create user_settings collection.");
+
     db.create_collection(
         "pubsub",
         CreateCollectionOptions::builder()
diff --git a/src/database/migrations/scripts.rs b/src/database/migrations/scripts.rs
index d123ab27f001a4ddf7fd9ba105f8ce01667243d6..e08595990fba6357f89accea4c2e7d4ebfa8914b 100644
--- a/src/database/migrations/scripts.rs
+++ b/src/database/migrations/scripts.rs
@@ -1,8 +1,9 @@
 use crate::database::{get_collection, get_db};
 
 use log::info;
-use mongodb::bson::{doc, from_document};
+use futures::StreamExt;
 use serde::{Deserialize, Serialize};
+use mongodb::{bson::{doc, from_document}, options::FindOptions};
 
 #[derive(Serialize, Deserialize)]
 struct MigrationInfo {
@@ -10,7 +11,7 @@ struct MigrationInfo {
     revision: i32,
 }
 
-pub const LATEST_REVISION: i32 = 3;
+pub const LATEST_REVISION: i32 = 4;
 
 pub async fn migrate_database() {
     let migrations = get_collection("migrations");
@@ -87,6 +88,51 @@ pub async fn run_migrations(revision: i32) -> i32 {
             .expect("Failed to create servers collection.");
     }
 
+    if revision <= 3 {
+        info!("Running migration [revision 3 / 2021-05-25]: Support multiple file uploads, add channel_unreads and user_settings.");
+
+        let messages = get_collection("messages");
+        let mut cursor = messages.find(
+            doc! {
+                "attachment": {
+                    "$exists": 1
+                }
+            },
+            FindOptions::builder()
+                .projection(doc! {
+                    "_id": 1,
+                    "attachments": [ "$attachment" ]
+                })
+                .build()
+        )
+        .await
+        .expect("Failed to fetch messages.");
+
+        while let Some(result) = cursor.next().await {
+            let doc = result.unwrap();
+            let id = doc.get_str("_id").unwrap();
+            let attachments = doc.get_array("attachments").unwrap();
+
+            messages.update_one(
+                doc! { "_id": id },
+                doc! { "$unset": { "attachment": 1 }, "$set": { "attachments": attachments } },
+                None
+            )
+            .await
+            .unwrap();
+        }
+
+        get_db()
+            .create_collection("channel_unreads", None)
+            .await
+            .expect("Failed to create channel_unreads collection.");
+
+        get_db()
+            .create_collection("user_settings", None)
+            .await
+            .expect("Failed to create user_settings collection.");
+    }
+
     // Reminder to update LATEST_REVISION when adding new migrations.
     LATEST_REVISION
 }
diff --git a/src/routes/channels/message_send.rs b/src/routes/channels/message_send.rs
index 3f2e4394fda036cc3c9b643c8ccff8c3ac4c44af..38c529d22c14936519437544d23f53a7720d344a 100644
--- a/src/routes/channels/message_send.rs
+++ b/src/routes/channels/message_send.rs
@@ -58,8 +58,8 @@ pub async fn req(user: User, target: Ref, message: Json<Data>) -> Result<JsonVal
     }
 
     let id = Ulid::new().to_string();
-    let attachment = if let Some(attachment_id) = &message.attachment {
-        Some(File::find_and_use(attachment_id, "attachments", "message", &id).await?)
+    let attachments = if let Some(attachment_id) = &message.attachment {
+        Some(vec![ File::find_and_use(attachment_id, "attachments", "message", &id).await? ])
     } else {
         None
     };
@@ -70,7 +70,7 @@ pub async fn req(user: User, target: Ref, message: Json<Data>) -> Result<JsonVal
         author: user.id,
 
         content: Content::Text(message.content.clone()),
-        attachment,
+        attachments,
         nonce: Some(message.nonce.clone()),
         edited: None,
         embeds: None,
diff --git a/src/version.rs b/src/version.rs
index 3ea17218961c31e178a72f525de561152f8961ae..cc9aa597a820aff3d835602a721fb3a318f1be3a 100644
--- a/src/version.rs
+++ b/src/version.rs
@@ -1 +1 @@
-pub const VERSION: &str = "0.4.1-alpha.13";
+pub const VERSION: &str = "0.4.2-alpha.0";