Repository: thunderbird/thunderbird-android (language: kotlin)

Issue:
Not swiping when archive action is set
### Checklist

- [X] I have used the search function to see if someone else has already submitted the same bug report.
- [X] I will describe the problem with as much detail as possible.

### App version

6.708-SNAPSHOT

### Where did you get the app from?

Other

### Android version

10

### Device model

Samsung A6

### Steps to reproduce

1. go to Settings
2. click on General settings
3. click on Interaction
4. click on swipe actions
5. click on right swipe
6. select Archive
7. go to the message list
8. Swipe to the right of one of the items

### Expected behavior

swipe, and after the swipe is completed, show a Toast to the user that the archive folder is not selected

### Actual behavior

We see that the swipe does not happen, because we have not created an archive folder.

### Logs

_No response_


Discussion hints:
It's been a while since it was reported but just wanted to chime in to say that it still occurs on K-9 6.801
This issue is occuring on Thunderbird 8.0b2 - swiping to Archive worked for me after installing the beta initially, but the issue appeared on my device in the past couple days. All other actions work with left or right swiping, but the Archive action works with neither (behaves as if no swipe action is set).

I'm using a Google Pixel 8 Pro running GrapheneOS 2024100800 (Android 14). Also tried force stopping the app, clearing cache, and disabling Graphene's exploit protections temporarily but the issue remains.
We talked a bit about this internally and don't think that showing a toast/snackbar is the way to go. Ideally, the app should try to prevent this situation from happening. Failing that, it should help the user to resolve the problem.

There's a couple of cases we need to consider:
1. The user chooses "archive" for a swipe action but not all of the accounts have an archive folder configured or support the archive action.
2. The user chooses "archive" for a swipe action but none of the accounts have an archive folder configured.
3. The user chooses "archive" for a swipe action but none of the accounts support the archive action (POP3).
4. The user chooses "archive" for a swipe action and all accounts have an archive folder configured. (There's nothing we need to do in this case.)

#### Some ideas on how to address these cases (This is not an official plan; Don't implement yet!)
1. When the user configures the swipe actions, we could show a dialog to let the user know that the archive action is not available in all accounts. This would only be informational and not block the user from configuring the archive swipe action.
The first time the swipe action is used in an account that doesn't have an archive folder configured, display a dialog to inform the user that no archive folder is configured. Allow them to select an archive folder in the same dialog. This dialog should only be displayed once per account. Or at least have a checkbox to not show the dialog again in the future. (Having only some accounts with an archive folder configured is a valid choice. We shouldn't annoy the user by displaying a dialog whenever they accidentally use the archive swipe action in an account where it is not available). This dialog could also allow the user to change the swipe action instead.
2. This could be handled like case 1. The message should probably be slightly different ("not all of your accounts" vs "none of your accounts").
3. The app could hide the archive action in the "swipe actions" screen. But it's possible to move from case 1 to this case by removing the last archive-capable account. Also, this can happen when importing settings. To avoid having to change all these places, we should simply show a warning in the "swipe actions" screen when the archive swipe action is selected. Additionally, we could prevent the user from selecting the archive action until an archive-capable account is added.

Since there's always the chance that not all accounts can use the archive action, we also need to think about how to handle the actual swipe gesture. Currently the app behaves like no swipe action was configured, i.e. the message list item can't be moved to the side. Instead, we could allow moving the message list item a little bit to the side and reveal a grey/disabled color underneath to signal the action is not available.
In terms of user experience, a few things would make sense:

1. allow swipe to happen, even if feature is unavailable
> list item a little bit to the side and reveal a grey/disabled color

that's a great solution, which preserves the consistency of the experience, while communicating the feature is unavailable. Could be a greyed out color, a crossed out archive icon, and text that says "No archive folder for that account" (or something more concise)
![Image](https://github.com/user-attachments/assets/81dd55fa-d807-4841-b8f4-0797d38244b0)


2. offer trying to create an archive folder:

For IMAP/exchange accounts, Thunderbird should offer to create or set needed folders as part of the adding account workflow. Correct folders would be set for non standard mailboxes, and mailboxes that do not come with an archive folder by default could have it created there. (maybe for pop3 it should create a local folder ?)

3. signpost users to the right settings to fix the situation from the swipe action menu

If archive selected, a concise warning banner could appear at the top of the settings page: "One or more account has no archive folder selected. Go to [manage your accounts](correctsetting) to fix this"

4. Don't conditionally hide the option

In my opinion, thunderbird is having a swipe that is greyed out and has no effect, with good explanations and signposting, than conditionally hiding or disabling the feature entirely.


Hello, finally posting some screen designs in response to this post in the case that a developer wants to pick this up. 

We're assuming the user has gone past the configuration phase and is using swipe gestures for the first time.
Also, noting that while I'm showing dialogs here, we may have to send the user to the settings to perform the tasks in the dialog. (but the dialogs technically should be possible)

**For a single account that does not have an archive folder set up, but the swipe gesture is set to move to archive folder**

![Image](https://github.com/user-attachments/assets/e69b6cd9-26a8-4626-bb9b-cb7b04d0694c)

**Single account where archiving is not possible**

![Image](https://github.com/user-attachments/assets/173cbffb-a9b0-4b89-b79a-3bc8a8e18f58)

**Unified Inbox / All accounts - set up archive folder**

Note: Need to add “archive” to more menu for unified selected message actions. Otherwise, inbox would follow a similar flow to per account above (Assumes we don’t have designated unified inbox folders)

![Image](https://github.com/user-attachments/assets/c44414a7-8e97-4478-9222-75fa2b78e174)

Pull request: Enable creating Archive folder when swipe action is archive and account doesn't have one.
Resolves #7062.

Test command: ./gradlew :feature:mail:message:list:testDebugUnitTest --tests "net.thunderbird.feature.mail.message.list.domain.usecase.SetArchiveFolderTest" --tests "net.thunderbird.feature.mail.message.list.domain.usecase.BuildSwipeActionsTest" --tests "net.thunderbird.feature.mail.message.list.FeatureMessageListModuleKtTest" --tests "net.thunderbird.feature.mail.message.list.domain.usecase.CreateArchiveFolderTest" --tests "net.thunderbird.feature.mail.message.list.domain.usecase.GetAccountFoldersTest" :backend:imap:test --tests "net.thunderbird.backend.imap.ImapRemoteFolderCreatorTest" :legacy:core:testDebugUnitTest --tests "com.fsck.k9.notification.NotificationContentCreatorTest" --tests "com.fsck.k9.helper.MessageHelperTest" :mail:protocols:imap:test --tests "com.fsck.k9.mail.store.imap.RealImapFolderTest" :app-thunderbird:testFossDebugUnitTest --tests "net.thunderbird.android.DependencyInjectionTest" :app-k9mail:testFossDebugUnitTest --tests "app.k9mail.DependencyInjectionTest"

Fix patch:
diff --git a/app-k9mail/build.gradle.kts b/app-k9mail/build.gradle.kts
index a7842fa86e2..6b22f1fa8e1 100644
--- a/app-k9mail/build.gradle.kts
+++ b/app-k9mail/build.gradle.kts
@@ -138,6 +138,7 @@ dependencies {
     implementation(projects.core.ui.compose.theme2.k9mail)
     implementation(projects.core.ui.legacy.theme2.k9mail)
     implementation(projects.feature.launcher)
+    implementation(projects.feature.mail.message.list)
 
     implementation(projects.legacy.core)
     implementation(projects.legacy.ui.legacy)
diff --git a/app-k9mail/src/main/kotlin/app/k9mail/feature/FeatureModule.kt b/app-k9mail/src/main/kotlin/app/k9mail/feature/FeatureModule.kt
index b008d73b38e..5fd16ed15e1 100644
--- a/app-k9mail/src/main/kotlin/app/k9mail/feature/FeatureModule.kt
+++ b/app-k9mail/src/main/kotlin/app/k9mail/feature/FeatureModule.kt
@@ -6,6 +6,7 @@ import app.k9mail.feature.migration.launcher.featureMigrationModule
 import app.k9mail.feature.onboarding.migration.onboardingMigrationModule
 import app.k9mail.feature.telemetry.telemetryModule
 import net.thunderbird.feature.account.settings.featureAccountSettingsModule
+import net.thunderbird.feature.mail.message.list.featureMessageListModule
 import org.koin.dsl.module
 
 val featureModule = module {
@@ -14,6 +15,7 @@ val featureModule = module {
     includes(featureFundingModule)
     includes(onboardingMigrationModule)
     includes(featureMigrationModule)
+    includes(featureMessageListModule)
 
     single<FundingSettings> { K9FundingSettings() }
 }
diff --git a/app-thunderbird/build.gradle.kts b/app-thunderbird/build.gradle.kts
index 426b7f0809b..9d6375d0681 100644
--- a/app-thunderbird/build.gradle.kts
+++ b/app-thunderbird/build.gradle.kts
@@ -185,7 +185,6 @@ android {
         resources {
             excludes += listOf(
                 "META-INF/*.kotlin_module",
-                "META-INF/*.version",
                 "kotlin/**",
                 "DebugProbesKt.bin",
             )
@@ -193,6 +192,14 @@ android {
     }
 }
 
+androidComponents {
+    onVariants(selector().withBuildType("release")) { variant ->
+        variant.packaging.resources.excludes.addAll(
+            "META-INF/*.version",
+        )
+    }
+}
+
 // Initialize placeholders for the product flavor and build type combinations needed for dependency declarations.
 // They are required to avoid "Unresolved configuration" errors.
 val fullDebugImplementation by configurations.creating
@@ -212,6 +219,7 @@ dependencies {
     implementation(projects.core.featureflag)
 
     implementation(projects.feature.account.settings.impl)
+    implementation(projects.feature.mail.message.list)
 
     implementation(projects.feature.widget.messageList)
     implementation(projects.feature.widget.messageListGlance)
diff --git a/app-thunderbird/src/main/kotlin/net/thunderbird/android/feature/FeatureModule.kt b/app-thunderbird/src/main/kotlin/net/thunderbird/android/feature/FeatureModule.kt
index d5875093a1f..82a9250486c 100644
--- a/app-thunderbird/src/main/kotlin/net/thunderbird/android/feature/FeatureModule.kt
+++ b/app-thunderbird/src/main/kotlin/net/thunderbird/android/feature/FeatureModule.kt
@@ -6,6 +6,7 @@ import app.k9mail.feature.migration.launcher.featureMigrationModule
 import app.k9mail.feature.onboarding.migration.onboardingMigrationModule
 import app.k9mail.feature.telemetry.telemetryModule
 import net.thunderbird.feature.account.settings.featureAccountSettingsModule
+import net.thunderbird.feature.mail.message.list.featureMessageListModule
 import org.koin.dsl.module
 
 internal val featureModule = module {
@@ -14,6 +15,7 @@ internal val featureModule = module {
     includes(featureFundingModule)
     includes(onboardingMigrationModule)
     includes(featureMigrationModule)
+    includes(featureMessageListModule)
 
     single<FundingSettings> { TbFundingSettings() }
 }
diff --git a/app-ui-catalog/src/main/kotlin/net/thunderbird/ui/catalog/ui/page/atom/items/ButtonItems.kt b/app-ui-catalog/src/main/kotlin/net/thunderbird/ui/catalog/ui/page/atom/items/ButtonItems.kt
index 2262c2e63ef..349d7b04fee 100644
--- a/app-ui-catalog/src/main/kotlin/net/thunderbird/ui/catalog/ui/page/atom/items/ButtonItems.kt
+++ b/app-ui-catalog/src/main/kotlin/net/thunderbird/ui/catalog/ui/page/atom/items/ButtonItems.kt
@@ -14,6 +14,7 @@ import app.k9mail.core.ui.compose.designsystem.atom.button.ButtonIcon
 import app.k9mail.core.ui.compose.designsystem.atom.button.ButtonOutlined
 import app.k9mail.core.ui.compose.designsystem.atom.button.ButtonSegmentedSingleChoice
 import app.k9mail.core.ui.compose.designsystem.atom.button.ButtonText
+import app.k9mail.core.ui.compose.designsystem.atom.button.RadioButton
 import app.k9mail.core.ui.compose.designsystem.atom.icon.Icons
 import kotlinx.collections.immutable.persistentListOf
 import net.thunderbird.ui.catalog.ui.page.common.list.defaultItem
@@ -125,6 +126,24 @@ fun LazyGridScope.buttonItems() {
         )
     }
 
+    sectionHeaderItem(text = "Button - RadioButton")
+    defaultItem {
+        RadioButton(
+            selected = false,
+            label = "Radio Button",
+            onClick = { },
+            modifier = Modifier.padding(defaultItemPadding()),
+        )
+    }
+    defaultItem {
+        RadioButton(
+            selected = true,
+            label = "Selected Radio Button",
+            onClick = { },
+            modifier = Modifier.padding(defaultItemPadding()),
+        )
+    }
+
     sectionHeaderItem(text = "Button - Segmented Single Choice")
     wideItem {
         val options = persistentListOf(
diff --git a/app-ui-catalog/src/main/kotlin/net/thunderbird/ui/catalog/ui/page/organism/items/DialogItems.kt b/app-ui-catalog/src/main/kotlin/net/thunderbird/ui/catalog/ui/page/organism/items/DialogItems.kt
index cb09851cdda..1ce8da2970c 100644
--- a/app-ui-catalog/src/main/kotlin/net/thunderbird/ui/catalog/ui/page/organism/items/DialogItems.kt
+++ b/app-ui-catalog/src/main/kotlin/net/thunderbird/ui/catalog/ui/page/organism/items/DialogItems.kt
@@ -1,103 +1,10 @@
 package net.thunderbird.ui.catalog.ui.page.organism.items
 
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.lazy.grid.LazyGridScope
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.outlined.AccountCircle
-import androidx.compose.material.icons.outlined.Info
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.vector.ImageVector
-import app.k9mail.core.ui.compose.designsystem.atom.button.ButtonFilled
-import app.k9mail.core.ui.compose.designsystem.atom.text.TextBodyLarge
-import app.k9mail.core.ui.compose.designsystem.atom.text.TextBodyMedium
-import app.k9mail.core.ui.compose.designsystem.atom.text.TextBodySmall
-import app.k9mail.core.ui.compose.designsystem.organism.AlertDialog
-import app.k9mail.core.ui.compose.theme2.MainTheme
-import net.thunderbird.ui.catalog.ui.page.common.list.defaultItem
-import net.thunderbird.ui.catalog.ui.page.common.list.defaultItemPadding
-import net.thunderbird.ui.catalog.ui.page.common.list.sectionHeaderItem
-import net.thunderbird.ui.catalog.ui.page.common.list.sectionSubtitleItem
+import net.thunderbird.ui.catalog.ui.page.organism.items.dialogs.alertDialogs
+import net.thunderbird.ui.catalog.ui.page.organism.items.dialogs.basicDialogs
 
 fun LazyGridScope.dialogItems() {
-    sectionHeaderItem("Alert dialogs")
-    sectionSubtitleItem("Simple dialog")
-    dialogItem(
-        title = "Simple dialog",
-        text = "This is a simple dialog",
-    )
-    sectionSubtitleItem("Dialog with icon")
-    dialogItem(
-        icon = Icons.Outlined.Info,
-        title = "Dialog with icon",
-        text = "This is a dialog with icon",
-    )
-    sectionSubtitleItem("Dialog with cancel")
-    dialogItem(
-        icon = Icons.Outlined.AccountCircle,
-        title = "Dialog with cancel",
-        text = "This is a dialog with cancel",
-        hasCancel = true,
-    )
-    sectionSubtitleItem("Dialog with custom content")
-    dialogItem(
-        title = "Dialog with custom content",
-        text = "This is a dialog with custom content",
-    ) {
-        Column(
-            verticalArrangement = Arrangement.spacedBy(MainTheme.spacings.default),
-        ) {
-            TextBodyLarge("Large body")
-            TextBodyMedium("Medium body")
-            TextBodySmall("Small body")
-        }
-    }
-}
-
-private fun LazyGridScope.dialogItem(
-    title: String,
-    text: String,
-    icon: ImageVector? = null,
-    hasCancel: Boolean = false,
-    content: @Composable (() -> Unit)? = null,
-) = defaultItem {
-    var showDialog by remember { mutableStateOf(false) }
-
-    ButtonFilled(
-        text = "Show dialog",
-        onClick = { showDialog = true },
-        modifier = Modifier.padding(defaultItemPadding()),
-    )
-
-    if (showDialog) {
-        if (content != null) {
-            AlertDialog(
-                title = title,
-                confirmText = "Accept",
-                onConfirmClick = { showDialog = false },
-                dismissText = if (hasCancel) "Cancel" else null,
-                onDismissClick = { showDialog = false },
-                onDismissRequest = { showDialog = false },
-            ) {
-                content()
-            }
-        } else {
-            AlertDialog(
-                icon = icon,
-                title = title,
-                text = text,
-                confirmText = "Accept",
-                onConfirmClick = { showDialog = false },
-                dismissText = if (hasCancel) "Cancel" else null,
-                onDismissClick = { showDialog = false },
-                onDismissRequest = { showDialog = false },
-            )
-        }
-    }
+    basicDialogs()
+    alertDialogs()
 }
diff --git a/app-ui-catalog/src/main/kotlin/net/thunderbird/ui/catalog/ui/page/organism/items/dialogs/AlertDialogItems.kt b/app-ui-catalog/src/main/kotlin/net/thunderbird/ui/catalog/ui/page/organism/items/dialogs/AlertDialogItems.kt
new file mode 100644
index 00000000000..3d2f0493c38
--- /dev/null
+++ b/app-ui-catalog/src/main/kotlin/net/thunderbird/ui/catalog/ui/page/organism/items/dialogs/AlertDialogItems.kt
@@ -0,0 +1,103 @@
+package net.thunderbird.ui.catalog.ui.page.organism.items.dialogs
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.lazy.grid.LazyGridScope
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.AccountCircle
+import androidx.compose.material.icons.outlined.Info
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.vector.ImageVector
+import app.k9mail.core.ui.compose.designsystem.atom.button.ButtonFilled
+import app.k9mail.core.ui.compose.designsystem.atom.text.TextBodyLarge
+import app.k9mail.core.ui.compose.designsystem.atom.text.TextBodyMedium
+import app.k9mail.core.ui.compose.designsystem.atom.text.TextBodySmall
+import app.k9mail.core.ui.compose.designsystem.organism.AlertDialog
+import app.k9mail.core.ui.compose.theme2.MainTheme
+import net.thunderbird.ui.catalog.ui.page.common.list.defaultItem
+import net.thunderbird.ui.catalog.ui.page.common.list.defaultItemPadding
+import net.thunderbird.ui.catalog.ui.page.common.list.sectionHeaderItem
+import net.thunderbird.ui.catalog.ui.page.common.list.sectionSubtitleItem
+
+internal fun LazyGridScope.alertDialogs() {
+    sectionHeaderItem("Alert dialogs")
+    sectionSubtitleItem("Simple dialog")
+    alertDialogItem(
+        title = "Simple dialog",
+        text = "This is a simple dialog",
+    )
+    sectionSubtitleItem("Dialog with icon")
+    alertDialogItem(
+        icon = Icons.Outlined.Info,
+        title = "Dialog with icon",
+        text = "This is a dialog with icon",
+    )
+    sectionSubtitleItem("Dialog with cancel")
+    alertDialogItem(
+        icon = Icons.Outlined.AccountCircle,
+        title = "Dialog with cancel",
+        text = "This is a dialog with cancel",
+        hasCancel = true,
+    )
+    sectionSubtitleItem("Dialog with custom content")
+    alertDialogItem(
+        title = "Dialog with custom content",
+        text = "This is a dialog with custom content",
+    ) {
+        Column(
+            verticalArrangement = Arrangement.spacedBy(MainTheme.spacings.default),
+        ) {
+            TextBodyLarge("Large body")
+            TextBodyMedium("Medium body")
+            TextBodySmall("Small body")
+        }
+    }
+}
+
+private fun LazyGridScope.alertDialogItem(
+    title: String,
+    text: String,
+    icon: ImageVector? = null,
+    hasCancel: Boolean = false,
+    content: @Composable (() -> Unit)? = null,
+) = defaultItem {
+    var showDialog by remember { mutableStateOf(false) }
+
+    ButtonFilled(
+        text = "Show dialog",
+        onClick = { showDialog = true },
+        modifier = Modifier.padding(defaultItemPadding()),
+    )
+
+    if (showDialog) {
+        if (content != null) {
+            AlertDialog(
+                title = title,
+                confirmText = "Accept",
+                onConfirmClick = { showDialog = false },
+                dismissText = if (hasCancel) "Cancel" else null,
+                onDismissClick = { showDialog = false },
+                onDismissRequest = { showDialog = false },
+            ) {
+                content()
+            }
+        } else {
+            AlertDialog(
+                icon = icon,
+                title = title,
+                text = text,
+                confirmText = "Accept",
+                onConfirmClick = { showDialog = false },
+                dismissText = if (hasCancel) "Cancel" else null,
+                onDismissClick = { showDialog = false },
+                onDismissRequest = { showDialog = false },
+            )
+        }
+    }
+}
diff --git a/app-ui-catalog/src/main/kotlin/net/thunderbird/ui/catalog/ui/page/organism/items/dialogs/BasicDialogItems.kt b/app-ui-catalog/src/main/kotlin/net/thunderbird/ui/catalog/ui/page/organism/items/dialogs/BasicDialogItems.kt
new file mode 100644
index 00000000000..c6d8eb1c439
--- /dev/null
+++ b/app-ui-catalog/src/main/kotlin/net/thunderbird/ui/catalog/ui/page/organism/items/dialogs/BasicDialogItems.kt
@@ -0,0 +1,230 @@
+package net.thunderbird.ui.catalog.ui.page.organism.items.dialogs
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.lazy.grid.LazyGridScope
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Refresh
+import androidx.compose.material3.Icon
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.State
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import app.k9mail.core.ui.compose.designsystem.atom.button.ButtonFilled
+import app.k9mail.core.ui.compose.designsystem.atom.button.ButtonText
+import app.k9mail.core.ui.compose.designsystem.atom.text.TextBodyLarge
+import app.k9mail.core.ui.compose.designsystem.atom.text.TextBodyMedium
+import app.k9mail.core.ui.compose.designsystem.atom.text.TextHeadlineSmall
+import app.k9mail.core.ui.compose.designsystem.organism.BasicDialog
+import app.k9mail.core.ui.compose.theme2.MainTheme
+import net.thunderbird.ui.catalog.ui.page.common.list.defaultItem
+import net.thunderbird.ui.catalog.ui.page.common.list.defaultItemPadding
+import net.thunderbird.ui.catalog.ui.page.common.list.sectionHeaderItem
+import net.thunderbird.ui.catalog.ui.page.common.list.sectionSubtitleItem
+
+internal fun LazyGridScope.basicDialogs() {
+    sectionHeaderItem("Basic dialogs")
+    basicDialogContentAndButtonItem()
+    basicDialogContentButtonHeadlineItem()
+    basicDialogContentButtonHeadlineSupportingTextItem()
+    basicDialogWithDividers()
+    basicDialogComplexImplementation()
+}
+
+private fun LazyGridScope.basicDialogContentAndButtonItem() {
+    basicDialogItem(
+        sectionSubtitle = "Basic Dialog with content and buttons",
+    ) {
+        BasicDialog(
+            onDismissRequest = { dismiss() },
+            content = {
+                TextBodyLarge("Dialog content")
+            },
+            buttons = { ButtonText(text = "Dismiss", onClick = { dismiss() }) },
+            contentPadding = PaddingValues(horizontal = MainTheme.spacings.triple),
+        )
+    }
+}
+
+private fun LazyGridScope.basicDialogContentButtonHeadlineItem() {
+    basicDialogItem(
+        sectionSubtitle = "Basic Dialog with content, buttons and headline as text",
+    ) {
+        BasicDialog(
+            headlineText = "Headline text",
+            onDismissRequest = { dismiss() },
+            content = {
+                TextBodyLarge("Dialog content")
+            },
+            buttons = { ButtonText(text = "Dismiss", onClick = { dismiss() }) },
+            contentPadding = PaddingValues(horizontal = MainTheme.spacings.triple),
+        )
+    }
+}
+
+private fun LazyGridScope.basicDialogContentButtonHeadlineSupportingTextItem() {
+    basicDialogItem(
+        sectionSubtitle = "Basic Dialog with content, buttons, headline and supporting text",
+    ) {
+        BasicDialog(
+            headlineText = "Headline text",
+            supportingText = "This is a supporting text",
+            onDismissRequest = { dismiss() },
+            content = {
+                TextBodyLarge("Dialog content")
+            },
+            buttons = { ButtonText(text = "Dismiss", onClick = { dismiss() }) },
+            contentPadding = PaddingValues(horizontal = MainTheme.spacings.triple),
+        )
+    }
+}
+
+private fun LazyGridScope.basicDialogWithDividers() {
+    basicDialogItem(
+        sectionSubtitle = "Basic Dialog with dividers",
+    ) {
+        BasicDialog(
+            onDismissRequest = { dismiss() },
+            headlineText = "Headline text",
+            supportingText = "This is a supporting text",
+            content = {
+                TextBodyLarge("Dialog content")
+            },
+            buttons = { ButtonText(text = "Dismiss", onClick = { dismiss() }) },
+            contentPadding = PaddingValues(all = MainTheme.spacings.triple),
+            showDividers = true,
+            dividerColor = MainTheme.colors.primary,
+        )
+    }
+}
+
+private fun LazyGridScope.basicDialogComplexImplementation() {
+    basicDialogItem(
+        sectionSubtitle = "Complex Basic dialog building",
+    ) {
+        BasicDialog(
+            onDismissRequest = { dismiss() },
+            content = { ComplexBasicDialogContent() },
+            buttons = {
+                ButtonText(text = "Cancel", onClick = { dismiss() })
+                ButtonText(text = "Accept", onClick = { dismiss() })
+            },
+            headline = {
+                Column(
+                    horizontalAlignment = Alignment.CenterHorizontally,
+                    verticalArrangement = Arrangement.spacedBy(MainTheme.spacings.double),
+                    modifier = Modifier.fillMaxWidth(),
+                ) {
+                    Icon(imageVector = Icons.Default.Refresh, contentDescription = null)
+                    TextHeadlineSmall(text = "Reset settings?")
+                }
+            },
+            supportingText = {
+                TextBodyMedium(
+                    text = "This will reset your app preferences back to their default settings. " +
+                        "The following accounts will also be signed out:",
+                    color = MainTheme.colors.onSurfaceVariant,
+                )
+            },
+            showDividers = true,
+        )
+    }
+}
+
+@Composable
+private fun ComplexBasicDialogContent(modifier: Modifier = Modifier) {
+    Column(
+        verticalArrangement = Arrangement.spacedBy(MainTheme.spacings.double),
+        modifier = modifier
+            .fillMaxWidth()
+            .padding(
+                start = MainTheme.spacings.triple,
+                end = MainTheme.spacings.triple,
+            ),
+    ) {
+        Row(
+            verticalAlignment = Alignment.CenterVertically,
+            horizontalArrangement = Arrangement.spacedBy(MainTheme.spacings.double),
+        ) {
+            Box(
+                modifier = Modifier
+                    .size(MainTheme.sizes.iconAvatar)
+                    .background(color = MainTheme.colors.primary, shape = CircleShape),
+            )
+            Text(text = "Account 1")
+        }
+        Row(
+            verticalAlignment = Alignment.CenterVertically,
+            horizontalArrangement = Arrangement.spacedBy(MainTheme.spacings.double),
+        ) {
+            Box(
+                modifier = Modifier
+                    .size(MainTheme.sizes.iconAvatar)
+                    .background(color = MainTheme.colors.primary, shape = CircleShape),
+            )
+            Text(text = "Account 2")
+        }
+        Row(
+            verticalAlignment = Alignment.CenterVertically,
+            horizontalArrangement = Arrangement.spacedBy(MainTheme.spacings.double),
+        ) {
+            Box(
+                modifier = Modifier
+                    .size(MainTheme.sizes.iconAvatar)
+                    .background(color = MainTheme.colors.primary, shape = CircleShape),
+            )
+            Text(text = "Account 3")
+        }
+    }
+}
+
+private fun LazyGridScope.basicDialogItem(
+    sectionSubtitle: String,
+    dialog: @Composable BasicDialogItemScope.() -> Unit,
+) {
+    sectionSubtitleItem(sectionSubtitle)
+    defaultItem {
+        val scope = remember { BasicDialogItemScope() }
+        ButtonFilled(
+            text = "Show dialog",
+            onClick = { scope.show() },
+            modifier = Modifier.padding(defaultItemPadding()),
+        )
+
+        if (scope.showDialog.value) {
+            scope.dialog()
+        }
+    }
+}
+
+private interface BasicDialogItemScope {
+    val showDialog: State<Boolean>
+    fun show()
+    fun dismiss()
+
+    companion object {
+        operator fun invoke(): BasicDialogItemScope = object : BasicDialogItemScope {
+            private val _showDialog = mutableStateOf(false)
+            override val showDialog: State<Boolean> = _showDialog
+
+            override fun show() {
+                _showDialog.value = true
+            }
+
+            override fun dismiss() {
+                _showDialog.value = false
+            }
+        }
+    }
+}
diff --git a/backend/api/src/main/java/com/fsck/k9/backend/api/BackendStorage.kt b/backend/api/src/main/java/com/fsck/k9/backend/api/BackendStorage.kt
index 42b66bf7dff..d551d176570 100644
--- a/backend/api/src/main/java/com/fsck/k9/backend/api/BackendStorage.kt
+++ b/backend/api/src/main/java/com/fsck/k9/backend/api/BackendStorage.kt
@@ -1,6 +1,7 @@
 package com.fsck.k9.backend.api
 
 import com.fsck.k9.mail.FolderType
+import com.fsck.k9.mail.MessagingException
 import java.io.Closeable
 
 interface BackendStorage {
@@ -17,11 +18,16 @@ interface BackendStorage {
 }
 
 interface BackendFolderUpdater : Closeable {
-    fun createFolders(folders: List<FolderInfo>)
+    @Throws(MessagingException::class)
+    fun createFolders(folders: List<FolderInfo>): Set<Long>
     fun deleteFolders(folderServerIds: List<String>)
+
+    @Throws(MessagingException::class)
     fun changeFolder(folderServerId: String, name: String, type: FolderType)
 }
 
-inline fun BackendStorage.updateFolders(block: BackendFolderUpdater.() -> Unit) {
-    createFolderUpdater().use { it.block() }
+fun BackendFolderUpdater.createFolder(folder: FolderInfo): Long? = createFolders(listOf(folder)).firstOrNull()
+
+inline fun <T> BackendStorage.updateFolders(block: BackendFolderUpdater.() -> T): T {
+    return createFolderUpdater().use { it.block() }
 }
diff --git a/backend/api/src/main/kotlin/net/thunderbird/backend/api/folder/RemoteFolderCreator.kt b/backend/api/src/main/kotlin/net/thunderbird/backend/api/folder/RemoteFolderCreator.kt
index e5fb3bb06dd..adbf6a34012 100644
--- a/backend/api/src/main/kotlin/net/thunderbird/backend/api/folder/RemoteFolderCreator.kt
+++ b/backend/api/src/main/kotlin/net/thunderbird/backend/api/folder/RemoteFolderCreator.kt
@@ -1,5 +1,6 @@
 package net.thunderbird.backend.api.folder
 
+import com.fsck.k9.mail.FolderType
 import com.fsck.k9.mail.folders.FolderServerId
 import net.thunderbird.core.outcome.Outcome
 import net.thunderbird.feature.mail.account.api.BaseAccount
@@ -13,6 +14,7 @@ interface RemoteFolderCreator {
      * @param mustCreate If `true`, the folder must be created returning
      * [RemoteFolderCreationOutcome.Error.FailedToCreateRemoteFolder]. If `false`, the folder will be created
      * only if it doesn't exist.
+     * @param folderType The folder type. This requires special handling for some servers. Default [FolderType.REGULAR].
      * @return The result of the operation.
      * @see RemoteFolderCreationOutcome.Success
      * @see RemoteFolderCreationOutcome.Error
@@ -20,6 +22,7 @@ interface RemoteFolderCreator {
     suspend fun create(
         folderServerId: FolderServerId,
         mustCreate: Boolean,
+        folderType: FolderType = FolderType.REGULAR,
     ): Outcome<RemoteFolderCreationOutcome.Success, RemoteFolderCreationOutcome.Error>
 
     interface Factory {
diff --git a/backend/imap/src/main/kotlin/net/thunderbird/backend/imap/ImapRemoteFolderCreator.kt b/backend/imap/src/main/kotlin/net/thunderbird/backend/imap/ImapRemoteFolderCreator.kt
index 37edd3a54e0..c65efa258cb 100644
--- a/backend/imap/src/main/kotlin/net/thunderbird/backend/imap/ImapRemoteFolderCreator.kt
+++ b/backend/imap/src/main/kotlin/net/thunderbird/backend/imap/ImapRemoteFolderCreator.kt
@@ -1,6 +1,7 @@
 package net.thunderbird.backend.imap
 
 import com.fsck.k9.backend.imap.ImapBackend
+import com.fsck.k9.mail.FolderType
 import com.fsck.k9.mail.MessagingException
 import com.fsck.k9.mail.folders.FolderServerId
 import com.fsck.k9.mail.store.imap.ImapStore
@@ -22,6 +23,7 @@ class ImapRemoteFolderCreator(
     override suspend fun create(
         folderServerId: FolderServerId,
         mustCreate: Boolean,
+        folderType: FolderType,
     ): Outcome<RemoteFolderCreationOutcome.Success, RemoteFolderCreationOutcome.Error> = withContext(ioDispatcher) {
         val remoteFolder = imapStore.getFolder(name = folderServerId.serverId)
         val outcome = try {
@@ -33,7 +35,9 @@ class ImapRemoteFolderCreator(
 
                 folderExists -> Outcome.success(RemoteFolderCreationOutcome.Success.AlreadyExists)
 
-                !folderExists && remoteFolder.create() -> Outcome.success(RemoteFolderCreationOutcome.Success.Created)
+                !folderExists && remoteFolder.create(folderType = folderType) -> Outcome.success(
+                    RemoteFolderCreationOutcome.Success.Created,
+                )
 
                 else -> Outcome.failure(
                     RemoteFolderCreationOutcome.Error.FailedToCreateRemoteFolder(
diff --git a/build-plugin/src/main/kotlin/thunderbird.library.android.gradle.kts b/build-plugin/src/main/kotlin/thunderbird.library.android.gradle.kts
index 3305e1dddd8..f4a67560344 100644
--- a/build-plugin/src/main/kotlin/thunderbird.library.android.gradle.kts
+++ b/build-plugin/src/main/kotlin/thunderbird.library.android.gradle.kts
@@ -1,3 +1,5 @@
+import org.jetbrains.kotlin.gradle.internal.config.LanguageFeature
+
 plugins {
     id("com.android.library")
     id("org.jetbrains.kotlin.android")
@@ -23,6 +25,12 @@ android {
     }
 }
 
+kotlin {
+    sourceSets.all {
+        languageSettings.enableLanguageFeature(LanguageFeature.WhenGuards.name)
+    }
+}
+
 dependencies {
     implementation(platform(libs.kotlin.bom))
     implementation(platform(libs.koin.bom))
diff --git a/config/detekt/detekt.yml b/config/detekt/detekt.yml
index f11271b6d34..d7bf9185b59 100644
--- a/config/detekt/detekt.yml
+++ b/config/detekt/detekt.yml
@@ -129,6 +129,10 @@ complexity:
   LongMethod:
     active: true
     threshold: 60
+    ignoreAnnotated:
+      - 'Preview'
+      - 'PreviewLightDark'
+      - 'PreviewLightDarkLandscape'
   LongParameterList:
     active: true
     functionThreshold: 8
@@ -622,6 +626,8 @@ style:
     ignoreAnnotation: false
     ignoreAnnotated:
       - 'Preview'
+      - 'PreviewLightDark'
+      - 'PreviewLightDarkLandscape'
     ignoreNamedArgument: true
     ignoreEnums: false
     ignoreRanges: false
@@ -740,11 +746,17 @@ style:
   UnusedPrivateMember:
     active: true
     allowedNames: ''
+    ignoreAnnotated:
+      - 'Preview'
+      - 'PreviewLightDark'
+      - 'PreviewLightDarkLandscape'
   UnusedPrivateProperty:
     active: true
     allowedNames: '_|ignored|expected|serialVersionUID'
     ignoreAnnotated:
       - 'Preview'
+      - 'PreviewLightDark'
+      - 'PreviewLightDarkLandscape'
   UseAnyOrNoneInsteadOfFind:
     active: true
   UseArrayLiteralsInAnnotations:
diff --git a/core/android/account/build.gradle.kts b/core/android/account/build.gradle.kts
index 7cbe652e774..b23d5d1be04 100644
--- a/core/android/account/build.gradle.kts
+++ b/core/android/account/build.gradle.kts
@@ -14,6 +14,7 @@ dependencies {
     api(projects.feature.notification)
     api(projects.mail.common)
 
+    implementation(projects.core.common)
     implementation(projects.core.preference.api)
 
     implementation(projects.feature.mail.account.api)
diff --git a/core/android/account/src/main/kotlin/net/thunderbird/core/android/account/AccountManager.kt b/core/android/account/src/main/kotlin/net/thunderbird/core/android/account/AccountManager.kt
index f0851247352..a8e0c19264d 100644
--- a/core/android/account/src/main/kotlin/net/thunderbird/core/android/account/AccountManager.kt
+++ b/core/android/account/src/main/kotlin/net/thunderbird/core/android/account/AccountManager.kt
@@ -1,15 +1,24 @@
 package net.thunderbird.core.android.account
 
 import kotlinx.coroutines.flow.Flow
+import net.thunderbird.feature.mail.account.api.AccountManager
 
-interface AccountManager {
-    fun getAccounts(): List<LegacyAccount>
-    fun getAccountsFlow(): Flow<List<LegacyAccount>>
-    fun getAccount(accountUuid: String): LegacyAccount?
-    fun getAccountFlow(accountUuid: String): Flow<LegacyAccount?>
+@Deprecated(
+    message = "Use net.thunderbird.feature.mail.account.api.AccountManager<TAccount : BaseAccount> instead",
+    replaceWith = ReplaceWith(
+        expression = "AccountManager<LegacyAccount>",
+        "net.thunderbird.feature.mail.account.api.AccountManager",
+        "app.k9mail.legacy.account.LegacyAccount",
+    ),
+)
+interface AccountManager : AccountManager<LegacyAccount> {
+    override fun getAccounts(): List<LegacyAccount>
+    override fun getAccountsFlow(): Flow<List<LegacyAccount>>
+    override fun getAccount(accountUuid: String): LegacyAccount?
+    override fun getAccountFlow(accountUuid: String): Flow<LegacyAccount?>
     fun addAccountRemovedListener(listener: AccountRemovedListener)
-    fun moveAccount(account: LegacyAccount, newPosition: Int)
+    override fun moveAccount(account: LegacyAccount, newPosition: Int)
     fun addOnAccountsChangeListener(accountsChangeListener: AccountsChangeListener)
     fun removeOnAccountsChangeListener(accountsChangeListener: AccountsChangeListener)
-    fun saveAccount(account: LegacyAccount)
+    override fun saveAccount(account: LegacyAccount)
 }
diff --git a/core/android/account/src/main/kotlin/net/thunderbird/core/android/account/LegacyAccountWrapper.kt b/core/android/account/src/main/kotlin/net/thunderbird/core/android/account/LegacyAccountWrapper.kt
index ddfb5a278d7..339abd0b1f9 100644
--- a/core/android/account/src/main/kotlin/net/thunderbird/core/android/account/LegacyAccountWrapper.kt
+++ b/core/android/account/src/main/kotlin/net/thunderbird/core/android/account/LegacyAccountWrapper.kt
@@ -2,6 +2,7 @@ package net.thunderbird.core.android.account
 
 import com.fsck.k9.mail.ServerSettings
 import net.thunderbird.core.android.account.AccountDefaultsProvider.Companion.NO_OPENPGP_KEY
+import net.thunderbird.core.common.mail.Protocols
 import net.thunderbird.feature.account.Account
 import net.thunderbird.feature.account.AccountId
 import net.thunderbird.feature.account.storage.profile.ProfileDto
@@ -144,4 +145,7 @@ data class LegacyAccountWrapper(
     fun hasOpenPgpKey(): Boolean {
         return openPgpKey != NO_OPENPGP_KEY
     }
+
+    fun isIncomingServerPop3(): Boolean =
+        incomingServerSettings.type == Protocols.POP3
 }
diff --git a/core/android/common/src/main/kotlin/app/k9mail/core/android/common/CoreCommonAndroidModule.kt b/core/android/common/src/main/kotlin/app/k9mail/core/android/common/CoreCommonAndroidModule.kt
index f7937b54d47..5baf9a39887 100644
--- a/core/android/common/src/main/kotlin/app/k9mail/core/android/common/CoreCommonAndroidModule.kt
+++ b/core/android/common/src/main/kotlin/app/k9mail/core/android/common/CoreCommonAndroidModule.kt
@@ -2,11 +2,14 @@ package app.k9mail.core.android.common
 
 import app.k9mail.core.android.common.camera.cameraModule
 import app.k9mail.core.android.common.contact.contactModule
+import net.thunderbird.core.android.common.resources.resourcesAndroidModule
 import net.thunderbird.core.common.coreCommonModule
 import org.koin.core.module.Module
 import org.koin.dsl.module
 
 val coreCommonAndroidModule: Module = module {
+    includes(resourcesAndroidModule)
+
     includes(coreCommonModule)
 
     includes(contactModule)
diff --git a/core/android/common/src/main/kotlin/net/thunderbird/core/android/common/resources/AndroidResourceManager.kt b/core/android/common/src/main/kotlin/net/thunderbird/core/android/common/resources/AndroidResourceManager.kt
new file mode 100644
index 00000000000..dd5f5206823
--- /dev/null
+++ b/core/android/common/src/main/kotlin/net/thunderbird/core/android/common/resources/AndroidResourceManager.kt
@@ -0,0 +1,21 @@
+package net.thunderbird.core.android.common.resources
+
+import android.content.Context
+import androidx.annotation.PluralsRes
+import androidx.annotation.StringRes
+import net.thunderbird.core.common.resources.ResourceManager
+
+internal class AndroidResourceManager(
+    private val context: Context,
+) : ResourceManager {
+    override fun stringResource(@StringRes resourceId: Int): String = context.resources.getString(resourceId)
+
+    override fun stringResource(@StringRes resourceId: Int, vararg formatArgs: Any?): String =
+        context.resources.getString(resourceId, *formatArgs)
+
+    override fun pluralsString(
+        @PluralsRes resourceId: Int,
+        quantity: Int,
+        vararg formatArgs: Any?,
+    ): String = context.resources.getQuantityString(resourceId, quantity, *formatArgs)
+}
diff --git a/core/android/common/src/main/kotlin/net/thunderbird/core/android/common/resources/ResourcesAndroidModule.kt b/core/android/common/src/main/kotlin/net/thunderbird/core/android/common/resources/ResourcesAndroidModule.kt
new file mode 100644
index 00000000000..fbe385c96f7
--- /dev/null
+++ b/core/android/common/src/main/kotlin/net/thunderbird/core/android/common/resources/ResourcesAndroidModule.kt
@@ -0,0 +1,15 @@
+package net.thunderbird.core.android.common.resources
+
+import net.thunderbird.core.common.resources.PluralsResourceManager
+import net.thunderbird.core.common.resources.ResourceManager
+import net.thunderbird.core.common.resources.StringsResourceManager
+import org.koin.android.ext.koin.androidApplication
+import org.koin.core.module.Module
+import org.koin.dsl.module
+
+internal val resourcesAndroidModule: Module = module {
+    single { AndroidResourceManager(context = androidApplication()) }
+    single<ResourceManager> { get<AndroidResourceManager>() }
+    single<StringsResourceManager> { get<AndroidResourceManager>() }
+    single<PluralsResourceManager> { get<AndroidResourceManager>() }
+}
diff --git a/core/common/build.gradle.kts b/core/common/build.gradle.kts
index fc043d3aac5..2c3d7287676 100644
--- a/core/common/build.gradle.kts
+++ b/core/common/build.gradle.kts
@@ -11,5 +11,16 @@ kotlin {
         commonTest.dependencies {
             implementation(projects.core.testing)
         }
+        jvmMain.dependencies {
+            implementation(libs.androidx.annotation)
+        }
+    }
+
+    compilerOptions {
+        freeCompilerArgs.addAll(
+            listOf(
+                "-Xexpect-actual-classes",
+            ),
+        )
     }
 }
diff --git a/core/common/src/androidMain/kotlin/net/thunderbird/core/common/resources/ResourceAnnotations.android.kt b/core/common/src/androidMain/kotlin/net/thunderbird/core/common/resources/ResourceAnnotations.android.kt
new file mode 100644
index 00000000000..a2ee7edb80b
--- /dev/null
+++ b/core/common/src/androidMain/kotlin/net/thunderbird/core/common/resources/ResourceAnnotations.android.kt
@@ -0,0 +1,4 @@
+package net.thunderbird.core.common.resources
+
+actual typealias StringRes = androidx.annotation.StringRes
+actual typealias PluralsRes = androidx.annotation.PluralsRes
diff --git a/core/common/src/androidMain/kotlin/net/thunderbird/core/common/resources/ResourceNotFoundException.jvm.kt b/core/common/src/androidMain/kotlin/net/thunderbird/core/common/resources/ResourceNotFoundException.jvm.kt
new file mode 100644
index 00000000000..320fad0a1f2
--- /dev/null
+++ b/core/common/src/androidMain/kotlin/net/thunderbird/core/common/resources/ResourceNotFoundException.jvm.kt
@@ -0,0 +1,3 @@
+package net.thunderbird.core.common.resources
+
+actual typealias ResourceNotFoundException = android.content.res.Resources.NotFoundException
diff --git a/core/common/src/commonMain/kotlin/net/thunderbird/core/common/action/SwipeAction.kt b/core/common/src/commonMain/kotlin/net/thunderbird/core/common/action/SwipeAction.kt
new file mode 100644
index 00000000000..a8f6fe2058b
--- /dev/null
+++ b/core/common/src/commonMain/kotlin/net/thunderbird/core/common/action/SwipeAction.kt
@@ -0,0 +1,24 @@
+package net.thunderbird.core.common.action
+
+enum class SwipeAction(val removesItem: Boolean) {
+    None(removesItem = false),
+    ToggleSelection(removesItem = false),
+    ToggleRead(removesItem = false),
+    ToggleStar(removesItem = false),
+    Archive(removesItem = true),
+    ArchiveDisabled(removesItem = false),
+    ArchiveSetupArchiveFolder(removesItem = false),
+    Delete(removesItem = true),
+    Spam(removesItem = true),
+    Move(removesItem = true),
+}
+
+data class SwipeActions(
+    val leftAction: SwipeAction,
+    val rightAction: SwipeAction,
+) {
+    companion object {
+        const val KEY_SWIPE_ACTION_LEFT = "swipeLeftAction"
+        const val KEY_SWIPE_ACTION_RIGHT = "swipeRightAction"
+    }
+}
diff --git a/core/common/src/commonMain/kotlin/net/thunderbird/core/common/resources/PluralsResourceManager.kt b/core/common/src/commonMain/kotlin/net/thunderbird/core/common/resources/PluralsResourceManager.kt
new file mode 100644
index 00000000000..7ed5f309503
--- /dev/null
+++ b/core/common/src/commonMain/kotlin/net/thunderbird/core/common/resources/PluralsResourceManager.kt
@@ -0,0 +1,28 @@
+package net.thunderbird.core.common.resources
+
+// TODO: Add support for Multiplatform resources. See https://www.jetbrains.com/help/kotlin-multiplatform-dev/compose-multiplatform-resources.html
+interface PluralsResourceManager {
+    /**
+     * Formats the string necessary for grammatically correct pluralization
+     * of the given resource ID for the given quantity, using the given arguments.
+     * Note that the string is selected based solely on grammatical necessity,
+     * and that such rules differ between languages. Do not assume you know which string
+     * will be returned for a given quantity. See
+     * <a href="{@docRoot}guide/topics/resources/string-resource.html#Plurals">String Resources</a>
+     * for more detail.
+     *
+     * <p>Substitution of format arguments works as if using
+     * {@link java.util.Formatter} and {@link java.lang.String#format}.
+     * The resulting string will be stripped of any styled text information.
+     *
+     * @param resourceId The desired resource identifier, as generated by the aapt tool. This integer
+     * encodes the package, type, and resource entry. The value 0 is an invalid identifier.
+     * @param quantity The number used to get the correct string for the current language's plural rules.
+     * @param formatArgs The format arguments that will be used for substitution.
+     * @throws net.thunderbird.core.common.resources.ResourceNotFoundException Throws NotFoundException if the given ID
+     * does not exist.
+     * @return String The string data associated with the resource,
+     * stripped of styled text information.
+     */
+    fun pluralsString(@PluralsRes resourceId: Int, quantity: Int, vararg formatArgs: Any?): String
+}
diff --git a/core/common/src/commonMain/kotlin/net/thunderbird/core/common/resources/ResourceAnnotations.kt b/core/common/src/commonMain/kotlin/net/thunderbird/core/common/resources/ResourceAnnotations.kt
new file mode 100644
index 00000000000..113d2a2a7e4
--- /dev/null
+++ b/core/common/src/commonMain/kotlin/net/thunderbird/core/common/resources/ResourceAnnotations.kt
@@ -0,0 +1,4 @@
+package net.thunderbird.core.common.resources
+
+expect annotation class StringRes()
+expect annotation class PluralsRes()
diff --git a/core/common/src/commonMain/kotlin/net/thunderbird/core/common/resources/ResourceManager.kt b/core/common/src/commonMain/kotlin/net/thunderbird/core/common/resources/ResourceManager.kt
new file mode 100644
index 00000000000..067cd814708
--- /dev/null
+++ b/core/common/src/commonMain/kotlin/net/thunderbird/core/common/resources/ResourceManager.kt
@@ -0,0 +1,12 @@
+package net.thunderbird.core.common.resources
+
+/**
+ * Represents a comprehensive resource manager that combines string and plural resource handling.
+ *
+ * This interface extends both [StringsResourceManager] and [PluralsResourceManager], providing a unified
+ * interface for accessing different types of string-based resources within the application.
+ *
+ * Implementations of this interface should provide concrete implementations for fetching both
+ * simple strings and pluralized strings based on their respective resource IDs.
+ */
+interface ResourceManager : StringsResourceManager, PluralsResourceManager
diff --git a/core/common/src/commonMain/kotlin/net/thunderbird/core/common/resources/ResourceNotFoundException.kt b/core/common/src/commonMain/kotlin/net/thunderbird/core/common/resources/ResourceNotFoundException.kt
new file mode 100644
index 00000000000..d2be1f65d8a
--- /dev/null
+++ b/core/common/src/commonMain/kotlin/net/thunderbird/core/common/resources/ResourceNotFoundException.kt
@@ -0,0 +1,8 @@
+package net.thunderbird.core.common.resources
+
+/**
+ * Exception thrown when a requested resource cannot be found.
+ * This can occur, for example, when trying to access a file or data
+ * that does not exist at the specified location.
+ */
+expect class ResourceNotFoundException
diff --git a/core/common/src/commonMain/kotlin/net/thunderbird/core/common/resources/StringsResourceManager.kt b/core/common/src/commonMain/kotlin/net/thunderbird/core/common/resources/StringsResourceManager.kt
new file mode 100644
index 00000000000..d097b7932be
--- /dev/null
+++ b/core/common/src/commonMain/kotlin/net/thunderbird/core/common/resources/StringsResourceManager.kt
@@ -0,0 +1,35 @@
+package net.thunderbird.core.common.resources
+
+// TODO: Add support for Multiplatform resources. See https://www.jetbrains.com/help/kotlin-multiplatform-dev/compose-multiplatform-resources.html
+interface StringsResourceManager {
+    /**
+     * Return the string value associated with a particular resource ID.  It
+     * will be stripped of any styled text information.
+     *
+     * @param resourceId The desired resource identifier, as generated by the aapt tool.
+     * This integer encodes the package, type, and resource entry. The value 0 is an
+     * invalid identifier.
+     * @throws net.thunderbird.core.common.resources.ResourceNotFoundException Throws NotFoundException if the given ID
+     * does not exist.
+     * @return String The string data associated with the resource, stripped of styled
+     * text information.
+     */
+    fun stringResource(@StringRes resourceId: Int): String
+
+    /**
+     * Return the string value associated with a particular resource ID,
+     * substituting the format arguments as defined in {@link java.util.Formatter}
+     * and {@link java.lang.String#format}. It will be stripped of any styled text
+     * information.
+     *
+     * @param resourceId The desired resource identifier, as generated by the aapt tool.
+     * This integer encodes the package, type, and resource entry. The value 0 is an invalid
+     * identifier.
+     * @param formatArgs The format arguments that will be used for substitution.
+     * @throws net.thunderbird.core.common.resources.ResourceNotFoundException Throws NotFoundException if the given ID
+     * does not exist.
+     * @return String The string data associated with the resource, stripped of styled text
+     * information.
+     */
+    fun stringResource(@StringRes resourceId: Int, vararg formatArgs: Any?): String
+}
diff --git a/core/common/src/jvmMain/kotlin/net/thunderbird/core/common/resources/ResourceAnnotations.jvm.kt b/core/common/src/jvmMain/kotlin/net/thunderbird/core/common/resources/ResourceAnnotations.jvm.kt
new file mode 100644
index 00000000000..a2ee7edb80b
--- /dev/null
+++ b/core/common/src/jvmMain/kotlin/net/thunderbird/core/common/resources/ResourceAnnotations.jvm.kt
@@ -0,0 +1,4 @@
+package net.thunderbird.core.common.resources
+
+actual typealias StringRes = androidx.annotation.StringRes
+actual typealias PluralsRes = androidx.annotation.PluralsRes
diff --git a/core/common/src/jvmMain/kotlin/net/thunderbird/core/common/resources/ResourceNotFoundException.jvm.kt b/core/common/src/jvmMain/kotlin/net/thunderbird/core/common/resources/ResourceNotFoundException.jvm.kt
new file mode 100644
index 00000000000..eed419ed375
--- /dev/null
+++ b/core/common/src/jvmMain/kotlin/net/thunderbird/core/common/resources/ResourceNotFoundException.jvm.kt
@@ -0,0 +1,3 @@
+package net.thunderbird.core.common.resources
+
+actual typealias ResourceNotFoundException = java.lang.Exception
diff --git a/core/preference/api/src/commonMain/kotlin/net/thunderbird/core/preference/GeneralSettings.kt b/core/preference/api/src/commonMain/kotlin/net/thunderbird/core/preference/GeneralSettings.kt
index 89f4717d418..9ece70b53e5 100644
--- a/core/preference/api/src/commonMain/kotlin/net/thunderbird/core/preference/GeneralSettings.kt
+++ b/core/preference/api/src/commonMain/kotlin/net/thunderbird/core/preference/GeneralSettings.kt
@@ -22,6 +22,7 @@ data class GeneralSettings(
     val isShowMessageListStars: Boolean,
     val isShowAnimations: Boolean,
     val isShowCorrespondentNames: Boolean,
+    val shouldShowSetupArchiveFolderDialog: Boolean,
 )
 
 enum class BackgroundSync {
diff --git a/core/preference/api/src/commonMain/kotlin/net/thunderbird/core/preference/GeneralSettingsManager.kt b/core/preference/api/src/commonMain/kotlin/net/thunderbird/core/preference/GeneralSettingsManager.kt
index 251cafc5b6d..91a23e6357a 100644
--- a/core/preference/api/src/commonMain/kotlin/net/thunderbird/core/preference/GeneralSettingsManager.kt
+++ b/core/preference/api/src/commonMain/kotlin/net/thunderbird/core/preference/GeneralSettingsManager.kt
@@ -21,4 +21,5 @@ interface GeneralSettingsManager {
     fun setIsShowMessageListStars(isShowMessageListStars: Boolean)
     fun setIsShowAnimations(isShowAnimations: Boolean)
     fun setIsShowCorrespondentNames(isShowCorrespondentNames: Boolean)
+    fun setSetupArchiveShouldNotShowAgain(shouldShowSetupArchiveFolderDialog: Boolean)
 }
diff --git a/core/ui/compose/designsystem/src/debug/kotlin/app/k9mail/core/ui/compose/designsystem/PreviewLightDarkLandscape.kt b/core/ui/compose/designsystem/src/debug/kotlin/app/k9mail/core/ui/compose/designsystem/PreviewLightDarkLandscape.kt
new file mode 100644
index 00000000000..0c952ef682c
--- /dev/null
+++ b/core/ui/compose/designsystem/src/debug/kotlin/app/k9mail/core/ui/compose/designsystem/PreviewLightDarkLandscape.kt
@@ -0,0 +1,13 @@
+package app.k9mail.core.ui.compose.designsystem
+
+import android.content.res.Configuration.UI_MODE_NIGHT_YES
+import android.content.res.Configuration.UI_MODE_TYPE_NORMAL
+import androidx.compose.ui.tooling.preview.Preview
+
+@Preview(name = "Light", device = "spec:width=673dp,height=841dp,orientation=landscape")
+@Preview(
+    name = "Dark",
+    uiMode = UI_MODE_NIGHT_YES or UI_MODE_TYPE_NORMAL,
+    device = "spec:width=673dp,height=841dp,orientation=landscape",
+)
+annotation class PreviewLightDarkLandscape
diff --git a/core/ui/compose/designsystem/src/debug/kotlin/app/k9mail/core/ui/compose/designsystem/PreviewWithThemeLightDark.kt b/core/ui/compose/designsystem/src/debug/kotlin/app/k9mail/core/ui/compose/designsystem/PreviewWithThemeLightDark.kt
new file mode 100644
index 00000000000..f6cf6d21e72
--- /dev/null
+++ b/core/ui/compose/designsystem/src/debug/kotlin/app/k9mail/core/ui/compose/designsystem/PreviewWithThemeLightDark.kt
@@ -0,0 +1,106 @@
+package app.k9mail.core.ui.compose.designsystem
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.isSystemInDarkTheme
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.padding
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.movableContentOf
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import app.k9mail.core.ui.compose.theme2.MainTheme
+
+/**
+ * A Composable function that displays a preview of the content in both Thunderbird and K-9 Mail themes.
+ *
+ * It uses the current system theme (light or dark) for both previews.
+ *
+ * @param modifier The modifier to be applied to the layout.
+ * @param useRow Whether to display the previews in a row or column. Defaults to `false` (column).
+ * @param useScrim Whether to display a scrim behind the content. Defaults to `false`.
+ * @param scrimAlpha The alpha value for the scrim. Defaults to `0.8f`.
+ * @param scrimPadding The padding for the scrim. Defaults to `MainTheme.spacings.triple`.
+ * @param arrangement The arrangement for the previews. Defaults to `Arrangement.spacedBy(MainTheme.spacings.triple)`.
+ * @param content The content to be displayed in the previews.
+ *
+ * @see app.k9mail.core.ui.compose.theme2.default.defaultThemeSpacings for MainTheme.spacings
+ */
+@Composable
+fun PreviewWithThemeLightDark(
+    modifier: Modifier = Modifier,
+    useRow: Boolean = false,
+    useScrim: Boolean = false,
+    scrimAlpha: Float = 0.8f,
+    scrimPadding: PaddingValues = PaddingValues(MainTheme.spacings.triple),
+    arrangement: Arrangement.HorizontalOrVertical = Arrangement.spacedBy(MainTheme.spacings.triple),
+    content: @Composable () -> Unit,
+) {
+    val movableContent = remember {
+        movableContentOf {
+            ThemePreview(
+                themeType = PreviewThemeType.THUNDERBIRD,
+                useScrim = useScrim,
+                scrimAlpha = scrimAlpha,
+                scrimPadding = scrimPadding,
+                content = content,
+            )
+            ThemePreview(
+                themeType = PreviewThemeType.K9MAIL,
+                useScrim = useScrim,
+                scrimAlpha = scrimAlpha,
+                scrimPadding = scrimPadding,
+                content = content,
+            )
+        }
+    }
+
+    if (useRow) {
+        Row(
+            horizontalArrangement = arrangement,
+            modifier = modifier,
+        ) {
+            movableContent()
+        }
+    } else {
+        Column(
+            verticalArrangement = arrangement,
+            modifier = modifier,
+        ) {
+            movableContent()
+        }
+    }
+}
+
+@Composable
+private fun ThemePreview(
+    themeType: PreviewThemeType,
+    useScrim: Boolean,
+    scrimAlpha: Float,
+    scrimPadding: PaddingValues,
+    content: @Composable (() -> Unit),
+) {
+    val movableContent = remember { movableContentOf { content() } }
+    PreviewWithTheme(
+        themeType = themeType,
+        isDarkTheme = isSystemInDarkTheme(),
+    ) {
+        PreviewSurface {
+            if (useScrim) {
+                Box(
+                    modifier = Modifier
+                        .background(MainTheme.colors.scrim.copy(alpha = scrimAlpha))
+                        .padding(scrimPadding),
+                ) {
+                    movableContent()
+                }
+            } else {
+                movableContent()
+            }
+            PreviewHeader(themeName = themeType.name)
+        }
+    }
+}
diff --git a/core/ui/compose/designsystem/src/debug/kotlin/app/k9mail/core/ui/compose/designsystem/PreviewWithThemes.kt b/core/ui/compose/designsystem/src/debug/kotlin/app/k9mail/core/ui/compose/designsystem/PreviewWithThemes.kt
index d6c6ce9cc5d..3e993269fd8 100644
--- a/core/ui/compose/designsystem/src/debug/kotlin/app/k9mail/core/ui/compose/designsystem/PreviewWithThemes.kt
+++ b/core/ui/compose/designsystem/src/debug/kotlin/app/k9mail/core/ui/compose/designsystem/PreviewWithThemes.kt
@@ -99,7 +99,7 @@ private fun PreviewWithThunderbirdTheme(
 }
 
 @Composable
-private fun PreviewHeader(
+internal fun PreviewHeader(
     themeName: String,
 ) {
     Surface(
@@ -117,7 +117,7 @@ private fun PreviewHeader(
 }
 
 @Composable
-private fun PreviewSurface(
+internal fun PreviewSurface(
     content: @Composable () -> Unit,
 ) {
     Surface(
diff --git a/core/ui/compose/designsystem/src/debug/kotlin/app/k9mail/core/ui/compose/designsystem/atom/RadioGroupPreview.kt b/core/ui/compose/designsystem/src/debug/kotlin/app/k9mail/core/ui/compose/designsystem/atom/RadioGroupPreview.kt
index b1ccebce998..36ed4037d3e 100644
--- a/core/ui/compose/designsystem/src/debug/kotlin/app/k9mail/core/ui/compose/designsystem/atom/RadioGroupPreview.kt
+++ b/core/ui/compose/designsystem/src/debug/kotlin/app/k9mail/core/ui/compose/designsystem/atom/RadioGroupPreview.kt
@@ -2,6 +2,10 @@ package app.k9mail.core.ui.compose.designsystem.atom
 
 import androidx.compose.foundation.layout.padding
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.tooling.preview.Preview
 import app.k9mail.core.ui.compose.designsystem.PreviewWithThemes
@@ -19,11 +23,12 @@ val choice = persistentListOf(
 @Preview(showBackground = true)
 internal fun RadioGroupSelectedPreview() {
     PreviewWithThemes {
+        var selectedOption by remember { mutableStateOf(choice[0]) }
         RadioGroup(
-            onClick = {},
+            onClick = { selectedOption = it },
             options = choice,
             optionTitle = { it.second },
-            selectedOption = choice[0],
+            selectedOption = selectedOption,
             modifier = Modifier.padding(MainTheme.spacings.default),
         )
     }
diff --git a/core/ui/compose/designsystem/src/debug/kotlin/app/k9mail/core/ui/compose/designsystem/organism/BasicDialogPreview.kt b/core/ui/compose/designsystem/src/debug/kotlin/app/k9mail/core/ui/compose/designsystem/organism/BasicDialogPreview.kt
new file mode 100644
index 00000000000..9a1a7a46204
--- /dev/null
+++ b/core/ui/compose/designsystem/src/debug/kotlin/app/k9mail/core/ui/compose/designsystem/organism/BasicDialogPreview.kt
@@ -0,0 +1,147 @@
+package app.k9mail.core.ui.compose.designsystem.organism
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Refresh
+import androidx.compose.material3.Icon
+import androidx.compose.material3.Text
+import androidx.compose.material3.TextButton
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import app.k9mail.core.ui.compose.designsystem.PreviewLightDarkLandscape
+import app.k9mail.core.ui.compose.designsystem.PreviewWithThemeLightDark
+import app.k9mail.core.ui.compose.designsystem.atom.text.TextBodyMedium
+import app.k9mail.core.ui.compose.designsystem.atom.text.TextHeadlineSmall
+import app.k9mail.core.ui.compose.theme2.MainTheme
+
+@PreviewLightDarkLandscape
+@Composable
+private fun BasicDialogPreview() {
+    PreviewWithThemeLightDark(
+        useRow = true,
+        useScrim = true,
+        scrimPadding = PaddingValues(MainTheme.spacings.quadruple),
+        arrangement = Arrangement.spacedBy(MainTheme.spacings.triple),
+    ) {
+        BasicDialogContent(
+            headline = {
+                Column(
+                    horizontalAlignment = Alignment.CenterHorizontally,
+                    verticalArrangement = Arrangement.spacedBy(MainTheme.spacings.double),
+                    modifier = Modifier.fillMaxWidth(),
+                ) {
+                    Icon(imageVector = Icons.Default.Refresh, contentDescription = null)
+                    TextHeadlineSmall(text = "Reset settings?")
+                }
+            },
+            supportingText = {
+                TextBodyMedium(
+                    text = "This will reset your app preferences back to their default settings. " +
+                        "The following accounts will also be signed out:",
+                    color = MainTheme.colors.onSurfaceVariant,
+                )
+            },
+            content = {
+                Column(
+                    verticalArrangement = Arrangement.spacedBy(MainTheme.spacings.double),
+                    modifier = Modifier
+                        .fillMaxWidth()
+                        .padding(
+                            start = MainTheme.spacings.triple,
+                            end = MainTheme.spacings.triple,
+                        ),
+                ) {
+                    Row(
+                        verticalAlignment = Alignment.CenterVertically,
+                        horizontalArrangement = Arrangement.spacedBy(MainTheme.spacings.double),
+                    ) {
+                        Box(
+                            modifier = Modifier
+                                .size(MainTheme.sizes.iconAvatar)
+                                .background(color = MainTheme.colors.primary, shape = CircleShape),
+                        )
+                        Text(text = "Account 1")
+                    }
+                    Row(
+                        verticalAlignment = Alignment.CenterVertically,
+                        horizontalArrangement = Arrangement.spacedBy(MainTheme.spacings.double),
+                    ) {
+                        Box(
+                            modifier = Modifier
+                                .size(MainTheme.sizes.iconAvatar)
+                                .background(color = MainTheme.colors.primary, shape = CircleShape),
+                        )
+                        Text(text = "Account 2")
+                    }
+                    Row(
+                        verticalAlignment = Alignment.CenterVertically,
+                        horizontalArrangement = Arrangement.spacedBy(MainTheme.spacings.double),
+                    ) {
+                        Box(
+                            modifier = Modifier
+                                .size(MainTheme.sizes.iconAvatar)
+                                .background(color = MainTheme.colors.primary, shape = CircleShape),
+                        )
+                        Text(text = "Account 3")
+                    }
+                }
+            },
+            buttons = {
+                TextButton(onClick = {}) {
+                    Text(text = "Cancel")
+                }
+                TextButton(onClick = {}) {
+                    Text(text = "Accept")
+                }
+            },
+            showDividers = true,
+            modifier = Modifier.width(300.dp),
+        )
+    }
+}
+
+@PreviewLightDarkLandscape
+@Composable
+private fun PreviewOnlySupportingText() {
+    PreviewWithThemeLightDark(
+        useRow = true,
+        useScrim = true,
+        scrimPadding = PaddingValues(MainTheme.spacings.quadruple),
+        arrangement = Arrangement.spacedBy(MainTheme.spacings.triple),
+    ) {
+        BasicDialogContent(
+            headline = {
+                TextHeadlineSmall(text = "Email can not be archived")
+            },
+            supportingText = {
+                TextBodyMedium(
+                    text = "Configure archive folder now",
+                    color = MainTheme.colors.onSurfaceVariant,
+                )
+            },
+            content = null,
+            buttons = {
+                TextButton(onClick = {}) {
+                    Text(text = "Skip for now")
+                }
+                TextButton(onClick = {}) {
+                    Text(text = "Set archive folder")
+                }
+            },
+            showDividers = false,
+            modifier = Modifier.width(300.dp),
+        )
+    }
+}
diff --git a/core/ui/compose/designsystem/src/main/kotlin/app/k9mail/core/ui/compose/designsystem/atom/RadioGroup.kt b/core/ui/compose/designsystem/src/main/kotlin/app/k9mail/core/ui/compose/designsystem/atom/RadioGroup.kt
index f00678b9f03..61aa2451b43 100644
--- a/core/ui/compose/designsystem/src/main/kotlin/app/k9mail/core/ui/compose/designsystem/atom/RadioGroup.kt
+++ b/core/ui/compose/designsystem/src/main/kotlin/app/k9mail/core/ui/compose/designsystem/atom/RadioGroup.kt
@@ -1,12 +1,9 @@
 package app.k9mail.core.ui.compose.designsystem.atom
 
 import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Row
-import androidx.compose.material3.RadioButton
 import androidx.compose.runtime.Composable
-import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
-import app.k9mail.core.ui.compose.designsystem.atom.text.TextLabelLarge
+import app.k9mail.core.ui.compose.designsystem.atom.button.RadioButton
 import kotlinx.collections.immutable.ImmutableList
 
 @Composable
@@ -23,20 +20,11 @@ fun <T> RadioGroup(
 
     Column(modifier = modifier) {
         options.forEach { option ->
-            Row(
-                verticalAlignment = Alignment.CenterVertically,
-            ) {
-                RadioButton(
-                    onClick = {
-                        onClick(option)
-                    },
-                    selected = option == selectedOption,
-                )
-
-                TextLabelLarge(
-                    text = optionTitle(option),
-                )
-            }
+            RadioButton(
+                label = optionTitle(option),
+                onClick = { onClick(option) },
+                selected = option == selectedOption,
+            )
         }
     }
 }
diff --git a/core/ui/compose/designsystem/src/main/kotlin/app/k9mail/core/ui/compose/designsystem/atom/button/RadioButton.kt b/core/ui/compose/designsystem/src/main/kotlin/app/k9mail/core/ui/compose/designsystem/atom/button/RadioButton.kt
new file mode 100644
index 00000000000..a31c0750ce6
--- /dev/null
+++ b/core/ui/compose/designsystem/src/main/kotlin/app/k9mail/core/ui/compose/designsystem/atom/button/RadioButton.kt
@@ -0,0 +1,53 @@
+package app.k9mail.core.ui.compose.designsystem.atom.button
+
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.selection.selectable
+import androidx.compose.material3.RadioButton
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.semantics.Role
+import app.k9mail.core.ui.compose.designsystem.atom.text.TextLabelLarge
+
+@Composable
+fun RadioButton(
+    selected: Boolean,
+    label: @Composable () -> Unit,
+    onClick: () -> Unit,
+    modifier: Modifier = Modifier,
+    enabled: Boolean = true,
+) {
+    Row(
+        verticalAlignment = Alignment.CenterVertically,
+        modifier = modifier
+            .selectable(
+                selected = selected,
+                role = Role.RadioButton,
+                onClick = onClick,
+            ),
+    ) {
+        RadioButton(
+            selected = selected,
+            onClick = onClick,
+            enabled = enabled,
+        )
+        label()
+    }
+}
+
+@Composable
+fun RadioButton(
+    selected: Boolean,
+    label: String,
+    onClick: () -> Unit,
+    modifier: Modifier = Modifier,
+    enabled: Boolean = true,
+) {
+    RadioButton(
+        selected = selected,
+        label = { TextLabelLarge(label) },
+        onClick = onClick,
+        modifier = modifier,
+        enabled = enabled,
+    )
+}
diff --git a/core/ui/compose/designsystem/src/main/kotlin/app/k9mail/core/ui/compose/designsystem/organism/BasicDialog.kt b/core/ui/compose/designsystem/src/main/kotlin/app/k9mail/core/ui/compose/designsystem/organism/BasicDialog.kt
new file mode 100644
index 00000000000..b187f3be6b6
--- /dev/null
+++ b/core/ui/compose/designsystem/src/main/kotlin/app/k9mail/core/ui/compose/designsystem/organism/BasicDialog.kt
@@ -0,0 +1,164 @@
+package app.k9mail.core.ui.compose.designsystem.organism
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.ColumnScope
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.RowScope
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.wrapContentSize
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.window.DialogProperties
+import app.k9mail.core.ui.compose.designsystem.atom.DividerHorizontal
+import app.k9mail.core.ui.compose.designsystem.atom.Surface
+import app.k9mail.core.ui.compose.designsystem.atom.text.TextBodyMedium
+import app.k9mail.core.ui.compose.designsystem.atom.text.TextHeadlineSmall
+import app.k9mail.core.ui.compose.theme2.MainTheme
+import androidx.compose.ui.window.Dialog as MaterialDialog
+
+@Composable
+fun BasicDialog(
+    onDismissRequest: () -> Unit,
+    content: (@Composable () -> Unit)?,
+    buttons: @Composable RowScope.() -> Unit,
+    modifier: Modifier = Modifier,
+    headline: (@Composable ColumnScope.() -> Unit)? = null,
+    supportingText: (@Composable ColumnScope.() -> Unit)? = null,
+    contentPadding: PaddingValues = BasicDialogDefaults.contentPadding,
+    showDividers: Boolean = BasicDialogDefaults.showDividers,
+    dividerColor: Color = BasicDialogDefaults.dividerColor,
+    properties: DialogProperties = DialogProperties(),
+) {
+    MaterialDialog(
+        onDismissRequest = onDismissRequest,
+        properties = properties,
+    ) {
+        BasicDialogContent(
+            content = content,
+            buttons = buttons,
+            modifier = modifier,
+            headline = headline,
+            supportingText = supportingText,
+            contentPadding = contentPadding,
+            showDividers = showDividers,
+            dividerColor = dividerColor,
+        )
+    }
+}
+
+@Composable
+fun BasicDialog(
+    headlineText: String,
+    onDismissRequest: () -> Unit,
+    content: (@Composable () -> Unit)?,
+    buttons: @Composable RowScope.() -> Unit,
+    modifier: Modifier = Modifier,
+    supportingText: String? = null,
+    contentPadding: PaddingValues = BasicDialogDefaults.contentPadding,
+    showDividers: Boolean = BasicDialogDefaults.showDividers,
+    dividerColor: Color = BasicDialogDefaults.dividerColor,
+    properties: DialogProperties = DialogProperties(),
+) {
+    BasicDialog(
+        onDismissRequest = onDismissRequest,
+        content = content,
+        buttons = buttons,
+        modifier = modifier,
+        headline = { TextHeadlineSmall(text = headlineText) },
+        supportingText = supportingText?.let {
+            @Composable {
+                TextBodyMedium(
+                    text = supportingText,
+                    color = MainTheme.colors.onSurfaceVariant,
+                )
+            }
+        },
+        contentPadding = contentPadding,
+        showDividers = showDividers,
+        dividerColor = dividerColor,
+        properties = properties,
+    )
+}
+
+@Composable
+internal fun BasicDialogContent(
+    content: (@Composable () -> Unit)?,
+    buttons: @Composable RowScope.() -> Unit,
+    modifier: Modifier = Modifier,
+    headline: (@Composable ColumnScope.() -> Unit)? = null,
+    supportingText: (@Composable ColumnScope.() -> Unit)? = null,
+    contentPadding: PaddingValues = BasicDialogDefaults.contentPadding,
+    showDividers: Boolean = BasicDialogDefaults.showDividers,
+    dividerColor: Color = BasicDialogDefaults.dividerColor,
+) {
+    Surface(
+        modifier = modifier,
+        shape = MainTheme.shapes.extraLarge,
+    ) {
+        Column {
+            Column(
+                verticalArrangement = Arrangement.spacedBy(MainTheme.spacings.double),
+                modifier = Modifier
+                    .padding(
+                        start = MainTheme.spacings.triple,
+                        end = MainTheme.spacings.triple,
+                        top = MainTheme.spacings.triple,
+                        bottom = MainTheme.spacings.double,
+                    ),
+            ) {
+                headline?.invoke(this)
+                supportingText?.invoke(this)
+            }
+            if (showDividers && (headline != null || supportingText != null)) {
+                DividerHorizontal(
+                    color = dividerColor,
+                    modifier = Modifier.wrapContentSize(),
+                )
+            }
+            content?.let { content ->
+                Box(
+                    modifier = Modifier
+                        .weight(weight = 1f, fill = false)
+                        .padding(contentPadding),
+                ) {
+                    content()
+                }
+            }
+            if (showDividers && content != null) {
+                DividerHorizontal(
+                    color = dividerColor,
+                    modifier = Modifier.wrapContentSize(),
+                )
+            }
+            Box(
+                modifier = Modifier
+                    .align(Alignment.End)
+                    .padding(
+                        start = MainTheme.spacings.triple,
+                        end = MainTheme.spacings.triple,
+                        bottom = MainTheme.spacings.triple,
+                    ),
+            ) {
+                Row { buttons() }
+            }
+        }
+    }
+}
+
+object BasicDialogDefaults {
+    val showDividers: Boolean get() = false
+    val dividerColor: Color
+        @Composable
+        get() = MainTheme.colors.outlineVariant
+    val contentPadding: PaddingValues
+        @Composable
+        get() = PaddingValues(
+            top = MainTheme.spacings.oneHalf,
+            bottom = MainTheme.spacings.double,
+        )
+}
diff --git a/feature/mail/account/api/src/commonMain/kotlin/net/thunderbird/feature/mail/account/api/AccountManager.kt b/feature/mail/account/api/src/commonMain/kotlin/net/thunderbird/feature/mail/account/api/AccountManager.kt
new file mode 100644
index 00000000000..bacd3b562af
--- /dev/null
+++ b/feature/mail/account/api/src/commonMain/kotlin/net/thunderbird/feature/mail/account/api/AccountManager.kt
@@ -0,0 +1,12 @@
+package net.thunderbird.feature.mail.account.api
+
+import kotlinx.coroutines.flow.Flow
+
+interface AccountManager<TAccount : BaseAccount> {
+    fun getAccounts(): List<TAccount>
+    fun getAccountsFlow(): Flow<List<TAccount>>
+    fun getAccount(accountUuid: String): TAccount?
+    fun getAccountFlow(accountUuid: String): Flow<TAccount?>
+    fun moveAccount(account: TAccount, newPosition: Int)
+    fun saveAccount(account: TAccount)
+}
diff --git a/feature/mail/folder/api/src/commonMain/kotlin/net/thunderbird/feature/mail/folder/api/SpecialFolderUpdater.kt b/feature/mail/folder/api/src/commonMain/kotlin/net/thunderbird/feature/mail/folder/api/SpecialFolderUpdater.kt
index 1d92d39b32e..2cd05f82c70 100644
--- a/feature/mail/folder/api/src/commonMain/kotlin/net/thunderbird/feature/mail/folder/api/SpecialFolderUpdater.kt
+++ b/feature/mail/folder/api/src/commonMain/kotlin/net/thunderbird/feature/mail/folder/api/SpecialFolderUpdater.kt
@@ -3,12 +3,14 @@ package net.thunderbird.feature.mail.folder.api
 import net.thunderbird.feature.mail.account.api.BaseAccount
 
 // TODO move to ???
-fun interface SpecialFolderUpdater {
+interface SpecialFolderUpdater {
     /**
      * Updates all account's special folders. If POP3, only Inbox is updated.
      */
     fun updateSpecialFolders()
 
+    fun setSpecialFolder(type: FolderType, folderId: Long?, selection: SpecialFolderSelection)
+
     interface Factory<TAccount : BaseAccount> {
         fun create(account: TAccount): SpecialFolderUpdater
     }
diff --git a/feature/mail/message/list/build.gradle.kts b/feature/mail/message/list/build.gradle.kts
new file mode 100644
index 00000000000..e538fefc7a7
--- /dev/null
+++ b/feature/mail/message/list/build.gradle.kts
@@ -0,0 +1,22 @@
+plugins {
+    id(ThunderbirdPlugins.Library.androidCompose)
+    alias(libs.plugins.dev.mokkery)
+}
+
+android {
+    namespace = "net.thunderbird.feature.mail.message.list"
+}
+
+dependencies {
+    implementation(projects.backend.api)
+    implementation(projects.core.android.common)
+    implementation(projects.core.logging.api)
+    implementation(projects.core.outcome)
+    implementation(projects.core.preference.api)
+    implementation(projects.core.ui.compose.designsystem)
+    implementation(projects.core.ui.theme.api)
+    implementation(projects.feature.mail.account.api)
+    implementation(projects.feature.mail.folder.api)
+    implementation(projects.legacy.mailstore)
+    implementation(projects.mail.common)
+}
diff --git a/feature/mail/message/list/src/debug/kotlin/net/thunderbird/feature/mail/message/list/ui/dialog/ChooseArchiveFolderDialogContentPreview.kt b/feature/mail/message/list/src/debug/kotlin/net/thunderbird/feature/mail/message/list/ui/dialog/ChooseArchiveFolderDialogContentPreview.kt
new file mode 100644
index 00000000000..56aec741f25
--- /dev/null
+++ b/feature/mail/message/list/src/debug/kotlin/net/thunderbird/feature/mail/message/list/ui/dialog/ChooseArchiveFolderDialogContentPreview.kt
@@ -0,0 +1,94 @@
+package net.thunderbird.feature.mail.message.list.ui.dialog
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.width
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.tooling.preview.PreviewParameter
+import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
+import androidx.compose.ui.unit.dp
+import app.k9mail.core.ui.compose.designsystem.PreviewLightDarkLandscape
+import app.k9mail.core.ui.compose.designsystem.PreviewWithThemeLightDark
+import app.k9mail.core.ui.compose.designsystem.atom.Surface
+import app.k9mail.core.ui.compose.theme2.MainTheme
+import net.thunderbird.feature.mail.folder.api.FolderType
+import net.thunderbird.feature.mail.folder.api.RemoteFolder
+import net.thunderbird.feature.mail.message.list.ui.dialog.SetupArchiveFolderDialogContract.State
+
+private class ChooseArchiveFolderDialogContentParamsCol :
+    CollectionPreviewParameterProvider<State.ChooseArchiveFolder>(
+        setOf(
+            State.ChooseArchiveFolder(
+                isLoadingFolders = true,
+            ),
+            State.ChooseArchiveFolder(
+                isLoadingFolders = false,
+                folders = listOf(
+                    RemoteFolder(
+                        id = 1,
+                        serverId = "1",
+                        name = "[Gmail]/All Mail",
+                        type = FolderType.REGULAR,
+                    ),
+                    RemoteFolder(id = 2, serverId = "2", name = "[Gmail]/Draft", type = FolderType.REGULAR),
+                    RemoteFolder(
+                        id = 3,
+                        serverId = "3",
+                        name = "[Gmail]/Sent Mail",
+                        type = FolderType.REGULAR,
+                    ),
+                    RemoteFolder(id = 3, serverId = "3", name = "[Gmail]/Spam", type = FolderType.REGULAR),
+                    RemoteFolder(id = 3, serverId = "3", name = "[Gmail]/Trash", type = FolderType.REGULAR),
+                    RemoteFolder(
+                        id = 3,
+                        serverId = "3",
+                        name = "[Gmail]/Another Folder",
+                        type = FolderType.REGULAR,
+                    ),
+                ),
+            ),
+            State.ChooseArchiveFolder(
+                isLoadingFolders = false,
+                errorMessage = "Error message",
+            ),
+        ),
+    )
+
+@PreviewLightDarkLandscape
+@Composable
+private fun ChooseArchiveFolderDialogContentPreview(
+    @PreviewParameter(ChooseArchiveFolderDialogContentParamsCol::class) state: State.ChooseArchiveFolder,
+) {
+    PreviewWithThemeLightDark(
+        useRow = true,
+        useScrim = true,
+        scrimPadding = PaddingValues(32.dp),
+        arrangement = Arrangement.spacedBy(24.dp),
+    ) {
+        Surface(
+            shape = MainTheme.shapes.extraLarge,
+            modifier = Modifier.width(300.dp),
+        ) {
+            Column {
+                ChooseArchiveFolderDialogContent(
+                    state = state,
+                    onFolderSelect = {},
+                )
+                Row(
+                    modifier = Modifier.fillMaxWidth(),
+                    horizontalArrangement = Arrangement.End,
+                ) {
+                    ChooseArchiveFolderDialogButtons(
+                        state = state,
+                        onCreateNewFolderClick = {},
+                        onDoneClick = {},
+                    )
+                }
+            }
+        }
+    }
+}
diff --git a/feature/mail/message/list/src/debug/kotlin/net/thunderbird/feature/mail/message/list/ui/dialog/CreateNewArchiveFolderDialogContentPreview.kt b/feature/mail/message/list/src/debug/kotlin/net/thunderbird/feature/mail/message/list/ui/dialog/CreateNewArchiveFolderDialogContentPreview.kt
new file mode 100644
index 00000000000..6c512b26d3c
--- /dev/null
+++ b/feature/mail/message/list/src/debug/kotlin/net/thunderbird/feature/mail/message/list/ui/dialog/CreateNewArchiveFolderDialogContentPreview.kt
@@ -0,0 +1,101 @@
+package net.thunderbird.feature.mail.message.list.ui.dialog
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.width
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.tooling.preview.PreviewParameter
+import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
+import androidx.compose.ui.unit.dp
+import app.k9mail.core.ui.compose.designsystem.PreviewLightDarkLandscape
+import app.k9mail.core.ui.compose.designsystem.PreviewWithThemeLightDark
+import app.k9mail.core.ui.compose.designsystem.atom.Surface
+import app.k9mail.core.ui.compose.theme2.MainTheme
+
+private data class CreateArchiveFolderPreviewParams(
+    val folderName: String,
+    val synchronizingMessage: String? = null,
+    val errorMessage: String? = null,
+)
+
+private class CreateArchiveFolderPreviewParamsCollection :
+    CollectionPreviewParameterProvider<CreateArchiveFolderPreviewParams>(
+        setOf(
+            CreateArchiveFolderPreviewParams(
+                folderName = "",
+                synchronizingMessage = null,
+            ),
+            CreateArchiveFolderPreviewParams(
+                folderName = "My new awesome folder",
+                synchronizingMessage = null,
+            ),
+            CreateArchiveFolderPreviewParams(
+                folderName = "A ${"very ".repeat(n = 100)} long folder name",
+                synchronizingMessage = null,
+            ),
+            CreateArchiveFolderPreviewParams(
+                folderName = "",
+                synchronizingMessage = "Preparing sync",
+            ),
+            CreateArchiveFolderPreviewParams(
+                folderName = "My new awesome folder",
+                synchronizingMessage = "Doing some sync stuff.",
+            ),
+            CreateArchiveFolderPreviewParams(
+                folderName = "A ${"very ".repeat(n = 100)} long folder name",
+                synchronizingMessage = "A ${"very ".repeat(n = 100)} long sync message",
+            ),
+            CreateArchiveFolderPreviewParams(
+                folderName = "A ${"very ".repeat(n = 100)} long folder name",
+                synchronizingMessage = "",
+                errorMessage = "Can not create folder.",
+            ),
+            CreateArchiveFolderPreviewParams(
+                folderName = "A ${"very ".repeat(n = 100)} long folder name",
+                synchronizingMessage = null,
+                errorMessage = "A ${"very ".repeat(n = 100)} long error message",
+            ),
+        ),
+    )
+
+@PreviewLightDarkLandscape
+@Composable
+private fun CreateNewArchiveFolderDialogContentPreview(
+    @PreviewParameter(CreateArchiveFolderPreviewParamsCollection::class) params: CreateArchiveFolderPreviewParams,
+) {
+    PreviewWithThemeLightDark(
+        useRow = true,
+        useScrim = true,
+        scrimPadding = PaddingValues(32.dp),
+        arrangement = Arrangement.spacedBy(24.dp),
+    ) {
+        Surface(
+            shape = MainTheme.shapes.extraLarge,
+            modifier = Modifier.width(300.dp),
+        ) {
+            Column {
+                CreateNewArchiveFolderDialogContent(
+                    folderName = params.folderName,
+                    syncingMessage = params.synchronizingMessage,
+                    errorMessage = params.errorMessage,
+                    onFolderNameChange = {},
+                )
+
+                Row(
+                    modifier = Modifier.fillMaxWidth(),
+                    horizontalArrangement = Arrangement.End,
+                ) {
+                    CreateNewArchiveFolderDialogButtons(
+                        isSynchronizing = params.synchronizingMessage?.isNotEmpty() == true,
+                        onCreateAndSetClick = {},
+                        onCancelClick = {},
+                    )
+                }
+            }
+        }
+    }
+}
diff --git a/feature/mail/message/list/src/debug/kotlin/net/thunderbird/feature/mail/message/list/ui/dialog/EmailCantBeArchivedDialogButtonsPreview.kt b/feature/mail/message/list/src/debug/kotlin/net/thunderbird/feature/mail/message/list/ui/dialog/EmailCantBeArchivedDialogButtonsPreview.kt
new file mode 100644
index 00000000000..7875f38b066
--- /dev/null
+++ b/feature/mail/message/list/src/debug/kotlin/net/thunderbird/feature/mail/message/list/ui/dialog/EmailCantBeArchivedDialogButtonsPreview.kt
@@ -0,0 +1,62 @@
+package net.thunderbird.feature.mail.message.list.ui.dialog
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.unit.dp
+import app.k9mail.core.ui.compose.designsystem.PreviewLightDarkLandscape
+import app.k9mail.core.ui.compose.designsystem.PreviewWithThemeLightDark
+import app.k9mail.core.ui.compose.designsystem.atom.Surface
+import app.k9mail.core.ui.compose.designsystem.atom.text.TextBodyMedium
+import app.k9mail.core.ui.compose.theme2.MainTheme
+import net.thunderbird.feature.mail.message.list.R
+
+@PreviewLightDarkLandscape
+@Composable
+private fun EmailCantBeArchivedDialogButtonsPreview() {
+    PreviewWithThemeLightDark(
+        useRow = true,
+        useScrim = true,
+        scrimPadding = PaddingValues(32.dp),
+        arrangement = Arrangement.spacedBy(24.dp),
+    ) {
+        Surface(
+            shape = MainTheme.shapes.extraLarge,
+            modifier = Modifier.width(300.dp),
+        ) {
+            val state by remember {
+                mutableStateOf(
+                    SetupArchiveFolderDialogContract.State.EmailCantBeArchived(
+                        isDoNotShowDialogAgainChecked = false,
+                    ),
+                )
+            }
+            Column(
+                modifier = Modifier.padding(MainTheme.spacings.triple),
+            ) {
+                TextBodyMedium(text = stringResource(R.string.setup_archive_folder_dialog_configure_archive_folder))
+                Row(
+                    modifier = Modifier.fillMaxWidth(),
+                    horizontalArrangement = Arrangement.End,
+                ) {
+                    EmailCantBeArchivedDialogButtons(
+                        state = state,
+                        onSetArchiveFolderClick = {},
+                        onSkipClick = {},
+                        onDoNotShowAgainChange = {},
+                    )
+                }
+            }
+        }
+    }
+}
diff --git a/feature/mail/message/list/src/debug/kotlin/net/thunderbird/feature/mail/message/list/ui/dialog/SetupArchiveFolderDialogPreview.kt b/feature/mail/message/list/src/debug/kotlin/net/thunderbird/feature/mail/message/list/ui/dialog/SetupArchiveFolderDialogPreview.kt
new file mode 100644
index 00000000000..c91cc87812b
--- /dev/null
+++ b/feature/mail/message/list/src/debug/kotlin/net/thunderbird/feature/mail/message/list/ui/dialog/SetupArchiveFolderDialogPreview.kt
@@ -0,0 +1,47 @@
+package net.thunderbird.feature.mail.message.list.ui.dialog
+
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.tooling.preview.PreviewLightDark
+import androidx.compose.ui.tooling.preview.PreviewParameter
+import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
+import app.k9mail.core.ui.compose.designsystem.atom.Surface
+import app.k9mail.core.ui.compose.theme2.thunderbird.ThunderbirdTheme2
+import net.thunderbird.feature.mail.folder.api.FolderType
+import net.thunderbird.feature.mail.folder.api.RemoteFolder
+import net.thunderbird.feature.mail.message.list.ui.dialog.SetupArchiveFolderDialogContract.State
+
+private class SetupArchiveFolderDialogParamCol : CollectionPreviewParameterProvider<State>(
+    setOf(
+        State.EmailCantBeArchived(isDoNotShowDialogAgainChecked = true),
+        State.EmailCantBeArchived(isDoNotShowDialogAgainChecked = false),
+        State.ChooseArchiveFolder(isLoadingFolders = false, folders = emptyList()),
+        State.ChooseArchiveFolder(isLoadingFolders = true, folders = emptyList()),
+        State.ChooseArchiveFolder(
+            isLoadingFolders = false,
+            folders = List(size = 5) {
+                RemoteFolder(
+                    id = it.toLong(),
+                    serverId = "$it",
+                    name = "Folder 1",
+                    type = FolderType.REGULAR,
+                )
+            },
+        ),
+        State.CreateArchiveFolder(syncingMessage = null, folderName = ""),
+        State.CreateArchiveFolder(syncingMessage = "any message", folderName = ""),
+    ),
+)
+
+@PreviewLightDark
+@Composable
+private fun SetupArchiveFolderDialogPreview(
+    @PreviewParameter(SetupArchiveFolderDialogParamCol::class) state: State,
+) {
+    ThunderbirdTheme2 {
+        Surface(modifier = Modifier.fillMaxSize()) {
+            SetupArchiveFolderDialog(state = state)
+        }
+    }
+}
diff --git a/feature/mail/message/list/src/main/kotlin/net/thunderbird/feature/mail/message/list/FeatureMessageListModule.kt b/feature/mail/message/list/src/main/kotlin/net/thunderbird/feature/mail/message/list/FeatureMessageListModule.kt
new file mode 100644
index 00000000000..f152ccff2e2
--- /dev/null
+++ b/feature/mail/message/list/src/main/kotlin/net/thunderbird/feature/mail/message/list/FeatureMessageListModule.kt
@@ -0,0 +1,55 @@
+package net.thunderbird.feature.mail.message.list
+
+import net.thunderbird.feature.mail.account.api.BaseAccount
+import net.thunderbird.feature.mail.message.list.domain.DomainContract
+import net.thunderbird.feature.mail.message.list.domain.usecase.BuildSwipeActions
+import net.thunderbird.feature.mail.message.list.domain.usecase.CreateArchiveFolder
+import net.thunderbird.feature.mail.message.list.domain.usecase.GetAccountFolders
+import net.thunderbird.feature.mail.message.list.domain.usecase.SetArchiveFolder
+import net.thunderbird.feature.mail.message.list.ui.dialog.SetupArchiveFolderDialogContract
+import net.thunderbird.feature.mail.message.list.ui.dialog.SetupArchiveFolderDialogFragment
+import net.thunderbird.feature.mail.message.list.ui.dialog.SetupArchiveFolderDialogFragmentFactory
+import net.thunderbird.feature.mail.message.list.ui.dialog.SetupArchiveFolderDialogViewModel
+import org.koin.core.module.dsl.viewModel
+import org.koin.core.qualifier.named
+import org.koin.dsl.module
+
+val featureMessageListModule = module {
+    factory<DomainContract.UseCase.GetAccountFolders> { GetAccountFolders(folderRepository = get()) }
+    factory<DomainContract.UseCase.CreateArchiveFolder> {
+        CreateArchiveFolder(
+            accountManager = get(),
+            backendStorageFactory = get(),
+            specialFolderUpdaterFactory = get(),
+            remoteFolderCreatorFactory = get(named("imap")),
+        )
+    }
+    factory<DomainContract.UseCase.SetArchiveFolder> {
+        SetArchiveFolder(
+            accountManager = get(),
+            backendStorageFactory = get(),
+            specialFolderUpdaterFactory = get(),
+        )
+    }
+    factory<DomainContract.UseCase.BuildSwipeActions<BaseAccount>> { parameters ->
+        BuildSwipeActions(
+            generalSettingsManager = get(),
+            accountManager = get(),
+            storage = parameters.get(),
+        )
+    }
+    viewModel { parameters ->
+        SetupArchiveFolderDialogViewModel(
+            accountUuid = parameters.get(),
+            logger = get(),
+            getAccountFolders = get(),
+            createArchiveFolder = get(),
+            setArchiveFolder = get(),
+            resourceManager = get(),
+            generalSettingsManager = get(),
+        ) as SetupArchiveFolderDialogContract.ViewModel
+    }
+    factory<SetupArchiveFolderDialogFragmentFactory> {
+        SetupArchiveFolderDialogFragment.Factory
+    }
+}
diff --git a/feature/mail/message/list/src/main/kotlin/net/thunderbird/feature/mail/message/list/domain/DomainContract.kt b/feature/mail/message/list/src/main/kotlin/net/thunderbird/feature/mail/message/list/domain/DomainContract.kt
new file mode 100644
index 00000000000..81eb4aa61b9
--- /dev/null
+++ b/feature/mail/message/list/src/main/kotlin/net/thunderbird/feature/mail/message/list/domain/DomainContract.kt
@@ -0,0 +1,71 @@
+package net.thunderbird.feature.mail.message.list.domain
+
+import com.fsck.k9.mail.folders.FolderServerId
+import kotlinx.coroutines.flow.Flow
+import net.thunderbird.core.common.action.SwipeActions
+import net.thunderbird.core.outcome.Outcome
+import net.thunderbird.feature.mail.account.api.BaseAccount
+import net.thunderbird.feature.mail.folder.api.RemoteFolder
+
+interface DomainContract {
+    interface UseCase {
+        fun interface GetAccountFolders {
+            suspend operator fun invoke(accountUuid: String): Outcome<List<RemoteFolder>, AccountFolderError>
+        }
+
+        fun interface CreateArchiveFolder {
+            operator fun invoke(
+                accountUuid: String,
+                folderName: String,
+            ): Flow<Outcome<CreateArchiveFolderOutcome.Success, CreateArchiveFolderOutcome.Error>>
+        }
+
+        fun interface SetArchiveFolder {
+            suspend operator fun invoke(
+                accountUuid: String,
+                folder: RemoteFolder,
+            ): Outcome<SetAccountFolderOutcome.Success, SetAccountFolderOutcome.Error>
+        }
+
+        fun interface BuildSwipeActions<out TAccount : BaseAccount> {
+            operator fun invoke(
+                accountUuids: Set<String>,
+                isIncomingServerPop3: (TAccount) -> Boolean,
+                hasArchiveFolder: (TAccount) -> Boolean,
+            ): Map<String, SwipeActions>
+        }
+    }
+}
+
+data class AccountFolderError(val exception: Exception)
+
+sealed interface SetAccountFolderOutcome {
+    data object Success : SetAccountFolderOutcome
+    sealed interface Error : SetAccountFolderOutcome {
+        data object AccountNotFound : Error
+        data class UnhandledError(val throwable: Throwable) : Error
+    }
+}
+
+sealed interface CreateArchiveFolderOutcome {
+    sealed interface Success : CreateArchiveFolderOutcome {
+        data object LocalFolderCreated : Success
+        data class SyncStarted(val serverId: FolderServerId) : Success
+        data object UpdatingSpecialFolders : Success
+        data object Created : Success
+    }
+
+    sealed interface Error : CreateArchiveFolderOutcome {
+        data class LocalFolderCreationError(val folderName: String) : Error
+        data class InvalidFolderName(val folderName: String) : Error
+        data object AccountNotFound : Error
+        data class UnhandledError(val throwable: Throwable) : Error
+        sealed interface SyncError : Error {
+            data class Failed(
+                val serverId: FolderServerId,
+                val message: String,
+                val exception: Exception?,
+            ) : SyncError
+        }
+    }
+}
diff --git a/feature/mail/message/list/src/main/kotlin/net/thunderbird/feature/mail/message/list/domain/usecase/BuildSwipeActions.kt b/feature/mail/message/list/src/main/kotlin/net/thunderbird/feature/mail/message/list/domain/usecase/BuildSwipeActions.kt
new file mode 100644
index 00000000000..7de17f39b94
--- /dev/null
+++ b/feature/mail/message/list/src/main/kotlin/net/thunderbird/feature/mail/message/list/domain/usecase/BuildSwipeActions.kt
@@ -0,0 +1,72 @@
+package net.thunderbird.feature.mail.message.list.domain.usecase
+
+import net.thunderbird.core.common.action.SwipeAction
+import net.thunderbird.core.common.action.SwipeActions
+import net.thunderbird.core.preference.GeneralSettingsManager
+import net.thunderbird.core.preference.storage.Storage
+import net.thunderbird.core.preference.storage.getEnumOrDefault
+import net.thunderbird.feature.mail.account.api.AccountManager
+import net.thunderbird.feature.mail.account.api.BaseAccount
+import net.thunderbird.feature.mail.message.list.domain.DomainContract
+
+class BuildSwipeActions(
+    private val generalSettingsManager: GeneralSettingsManager,
+    private val accountManager: AccountManager<BaseAccount>,
+    storage: Storage,
+) : DomainContract.UseCase.BuildSwipeActions<BaseAccount> {
+    private val defaultLeftSwipeAction = storage.getEnumOrDefault(
+        key = SwipeActions.KEY_SWIPE_ACTION_LEFT,
+        default = SwipeAction.ToggleRead,
+    )
+    private val defaultRightSwipeAction = storage.getEnumOrDefault(
+        key = SwipeActions.KEY_SWIPE_ACTION_RIGHT,
+        default = SwipeAction.ToggleRead,
+    )
+
+    override fun invoke(
+        accountUuids: Set<String>,
+        isIncomingServerPop3: (BaseAccount) -> Boolean,
+        hasArchiveFolder: (BaseAccount) -> Boolean,
+    ): Map<String, SwipeActions> {
+        val shouldShowSetupArchiveFolderDialog = generalSettingsManager
+            .getSettings()
+            .shouldShowSetupArchiveFolderDialog
+        return accountUuids
+            .mapNotNull { uuid -> accountManager.getAccount(uuid) }
+            .associate { account ->
+                account.uuid to SwipeActions(
+                    leftAction = buildSwipeAction(
+                        account = account,
+                        defaultSwipeAction = defaultLeftSwipeAction,
+                        isIncomingServerPop3 = isIncomingServerPop3,
+                        hasArchiveFolder = hasArchiveFolder,
+                        shouldShowSetupArchiveFolderDialog = shouldShowSetupArchiveFolderDialog,
+                    ),
+                    rightAction = buildSwipeAction(
+                        account = account,
+                        defaultSwipeAction = defaultRightSwipeAction,
+                        isIncomingServerPop3 = isIncomingServerPop3,
+                        hasArchiveFolder = hasArchiveFolder,
+                        shouldShowSetupArchiveFolderDialog = shouldShowSetupArchiveFolderDialog,
+                    ),
+                )
+            }
+    }
+
+    private fun buildSwipeAction(
+        account: BaseAccount,
+        defaultSwipeAction: SwipeAction,
+        isIncomingServerPop3: BaseAccount.() -> Boolean,
+        hasArchiveFolder: BaseAccount.() -> Boolean,
+        shouldShowSetupArchiveFolderDialog: Boolean,
+    ): SwipeAction = when (defaultSwipeAction) {
+        SwipeAction.Archive if account.isIncomingServerPop3() -> SwipeAction.ArchiveDisabled
+
+        SwipeAction.Archive if account.hasArchiveFolder().not() && shouldShowSetupArchiveFolderDialog ->
+            SwipeAction.ArchiveSetupArchiveFolder
+
+        SwipeAction.Archive if account.hasArchiveFolder().not() -> SwipeAction.ArchiveDisabled
+
+        else -> defaultSwipeAction
+    }
+}
diff --git a/feature/mail/message/list/src/main/kotlin/net/thunderbird/feature/mail/message/list/domain/usecase/CreateArchiveFolder.kt b/feature/mail/message/list/src/main/kotlin/net/thunderbird/feature/mail/message/list/domain/usecase/CreateArchiveFolder.kt
new file mode 100644
index 00000000000..cba8c25b416
--- /dev/null
+++ b/feature/mail/message/list/src/main/kotlin/net/thunderbird/feature/mail/message/list/domain/usecase/CreateArchiveFolder.kt
@@ -0,0 +1,117 @@
+package net.thunderbird.feature.mail.message.list.domain.usecase
+
+import com.fsck.k9.backend.api.FolderInfo
+import com.fsck.k9.backend.api.createFolder
+import com.fsck.k9.backend.api.updateFolders
+import com.fsck.k9.mail.MessagingException
+import com.fsck.k9.mail.folders.FolderServerId
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.withContext
+import net.thunderbird.backend.api.BackendStorageFactory
+import net.thunderbird.backend.api.folder.RemoteFolderCreationOutcome
+import net.thunderbird.backend.api.folder.RemoteFolderCreator
+import net.thunderbird.core.outcome.Outcome
+import net.thunderbird.feature.mail.account.api.AccountManager
+import net.thunderbird.feature.mail.account.api.BaseAccount
+import net.thunderbird.feature.mail.folder.api.FolderType
+import net.thunderbird.feature.mail.folder.api.SpecialFolderSelection
+import net.thunderbird.feature.mail.folder.api.SpecialFolderUpdater
+import net.thunderbird.feature.mail.message.list.domain.CreateArchiveFolderOutcome
+import net.thunderbird.feature.mail.message.list.domain.DomainContract
+import com.fsck.k9.mail.FolderType as LegacyFolderType
+
+class CreateArchiveFolder(
+    private val accountManager: AccountManager<BaseAccount>,
+    private val backendStorageFactory: BackendStorageFactory<BaseAccount>,
+    private val remoteFolderCreatorFactory: RemoteFolderCreator.Factory,
+    private val specialFolderUpdaterFactory: SpecialFolderUpdater.Factory<BaseAccount>,
+    private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO,
+) : DomainContract.UseCase.CreateArchiveFolder {
+    override fun invoke(
+        accountUuid: String,
+        folderName: String,
+    ): Flow<Outcome<CreateArchiveFolderOutcome.Success, CreateArchiveFolderOutcome.Error>> = flow {
+        if (folderName.isBlank()) {
+            emit(Outcome.failure(CreateArchiveFolderOutcome.Error.InvalidFolderName(folderName = folderName)))
+            return@flow
+        }
+
+        val account = withContext(ioDispatcher) {
+            accountManager.getAccount(accountUuid)
+        } ?: run {
+            emit(Outcome.failure(CreateArchiveFolderOutcome.Error.AccountNotFound))
+            return@flow
+        }
+
+        val backendStorage = backendStorageFactory.createBackendStorage(account)
+        val folderInfo = FolderInfo(
+            serverId = folderName,
+            name = folderName,
+            type = LegacyFolderType.ARCHIVE,
+        )
+
+        try {
+            val folderId = withContext(ioDispatcher) {
+                backendStorage.updateFolders {
+                    createFolder(folderInfo)
+                }
+            }
+
+            if (folderId == null) {
+                emit(
+                    Outcome.failure(
+                        CreateArchiveFolderOutcome.Error.LocalFolderCreationError(folderName = folderName),
+                    ),
+                )
+            } else {
+                emit(Outcome.success(CreateArchiveFolderOutcome.Success.LocalFolderCreated))
+                val serverId = FolderServerId(folderInfo.serverId)
+                emit(Outcome.success(CreateArchiveFolderOutcome.Success.SyncStarted(serverId = serverId)))
+                val remoteFolderCreator = remoteFolderCreatorFactory.create(account)
+                val outcome = remoteFolderCreator
+                    .create(folderServerId = serverId, mustCreate = false, folderType = LegacyFolderType.ARCHIVE)
+                when (outcome) {
+                    is Outcome.Failure<RemoteFolderCreationOutcome.Error> -> emit(
+                        Outcome.failure(
+                            CreateArchiveFolderOutcome.Error.SyncError.Failed(
+                                serverId = serverId,
+                                message = outcome.error.toString(),
+                                exception = null,
+                            ),
+                        ),
+                    )
+
+                    is Outcome.Success<RemoteFolderCreationOutcome.Success> -> handleRemoteFolderCreationSuccess(
+                        localFolderId = folderId,
+                        account = account,
+                        emit = ::emit,
+                    )
+                }
+            }
+        } catch (e: MessagingException) {
+            emit(Outcome.failure(CreateArchiveFolderOutcome.Error.UnhandledError(throwable = e)))
+        }
+    }
+
+    private suspend fun handleRemoteFolderCreationSuccess(
+        localFolderId: Long,
+        account: BaseAccount,
+        emit: suspend (Outcome<CreateArchiveFolderOutcome.Success, CreateArchiveFolderOutcome.Error>) -> Unit,
+    ) {
+        val specialFolderUpdater = specialFolderUpdaterFactory.create(account)
+        emit(Outcome.success(CreateArchiveFolderOutcome.Success.UpdatingSpecialFolders))
+        withContext(ioDispatcher) {
+            specialFolderUpdater.setSpecialFolder(
+                type = FolderType.ARCHIVE,
+                folderId = localFolderId,
+                selection = SpecialFolderSelection.MANUAL,
+            )
+            specialFolderUpdater.updateSpecialFolders()
+            accountManager.saveAccount(account)
+        }
+        emit(Outcome.success(CreateArchiveFolderOutcome.Success.Created))
+    }
+}
diff --git a/feature/mail/message/list/src/main/kotlin/net/thunderbird/feature/mail/message/list/domain/usecase/GetAccountFolders.kt b/feature/mail/message/list/src/main/kotlin/net/thunderbird/feature/mail/message/list/domain/usecase/GetAccountFolders.kt
new file mode 100644
index 00000000000..1cee1ffd5d7
--- /dev/null
+++ b/feature/mail/message/list/src/main/kotlin/net/thunderbird/feature/mail/message/list/domain/usecase/GetAccountFolders.kt
@@ -0,0 +1,30 @@
+package net.thunderbird.feature.mail.message.list.domain.usecase
+
+import app.k9mail.legacy.mailstore.FolderRepository
+import com.fsck.k9.mail.MessagingException
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
+import net.thunderbird.core.outcome.Outcome
+import net.thunderbird.feature.mail.folder.api.FolderType
+import net.thunderbird.feature.mail.folder.api.RemoteFolder
+import net.thunderbird.feature.mail.message.list.domain.AccountFolderError
+import net.thunderbird.feature.mail.message.list.domain.DomainContract
+
+class GetAccountFolders(
+    private val folderRepository: FolderRepository,
+    private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO,
+) : DomainContract.UseCase.GetAccountFolders {
+    override suspend fun invoke(accountUuid: String): Outcome<List<RemoteFolder>, AccountFolderError> =
+        withContext(ioDispatcher) {
+            try {
+                Outcome.success(
+                    data = folderRepository
+                        .getRemoteFolders(accountUuid)
+                        .filter { it.type == FolderType.REGULAR || it.type == FolderType.ARCHIVE },
+                )
+            } catch (e: MessagingException) {
+                Outcome.failure(error = AccountFolderError(exception = e))
+            }
+        }
+}
diff --git a/feature/mail/message/list/src/main/kotlin/net/thunderbird/feature/mail/message/list/domain/usecase/SetArchiveFolder.kt b/feature/mail/message/list/src/main/kotlin/net/thunderbird/feature/mail/message/list/domain/usecase/SetArchiveFolder.kt
new file mode 100644
index 00000000000..d35029b2ffa
--- /dev/null
+++ b/feature/mail/message/list/src/main/kotlin/net/thunderbird/feature/mail/message/list/domain/usecase/SetArchiveFolder.kt
@@ -0,0 +1,60 @@
+package net.thunderbird.feature.mail.message.list.domain.usecase
+
+import com.fsck.k9.mail.MessagingException
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
+import net.thunderbird.backend.api.BackendStorageFactory
+import net.thunderbird.core.outcome.Outcome
+import net.thunderbird.feature.mail.account.api.AccountManager
+import net.thunderbird.feature.mail.account.api.BaseAccount
+import net.thunderbird.feature.mail.folder.api.FolderType
+import net.thunderbird.feature.mail.folder.api.RemoteFolder
+import net.thunderbird.feature.mail.folder.api.SpecialFolderSelection
+import net.thunderbird.feature.mail.folder.api.SpecialFolderUpdater
+import net.thunderbird.feature.mail.message.list.domain.DomainContract
+import net.thunderbird.feature.mail.message.list.domain.SetAccountFolderOutcome
+import com.fsck.k9.mail.FolderType as LegacyFolderType
+
+internal class SetArchiveFolder(
+    private val accountManager: AccountManager<BaseAccount>,
+    private val backendStorageFactory: BackendStorageFactory<BaseAccount>,
+    private val specialFolderUpdaterFactory: SpecialFolderUpdater.Factory<BaseAccount>,
+    private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO,
+) : DomainContract.UseCase.SetArchiveFolder {
+    override suspend fun invoke(
+        accountUuid: String,
+        folder: RemoteFolder,
+    ): Outcome<SetAccountFolderOutcome.Success, SetAccountFolderOutcome.Error> {
+        val account = withContext(ioDispatcher) {
+            accountManager.getAccount(accountUuid)
+        } ?: return Outcome.Failure(SetAccountFolderOutcome.Error.AccountNotFound)
+
+        val backend = backendStorageFactory.createBackendStorage(account)
+        val specialFolderUpdater = specialFolderUpdaterFactory.create(account)
+        return try {
+            withContext(ioDispatcher) {
+                backend
+                    .createFolderUpdater()
+                    .use { updater ->
+                        updater.changeFolder(
+                            folderServerId = folder.serverId,
+                            name = folder.name,
+                            type = LegacyFolderType.ARCHIVE,
+                        )
+                        specialFolderUpdater.setSpecialFolder(
+                            type = FolderType.ARCHIVE,
+                            folderId = folder.id,
+                            selection = SpecialFolderSelection.MANUAL,
+                        )
+                        specialFolderUpdater.updateSpecialFolders()
+                        accountManager.saveAccount(account)
+
+                        Outcome.success(SetAccountFolderOutcome.Success)
+                    }
+            }
+        } catch (e: MessagingException) {
+            Outcome.Failure(SetAccountFolderOutcome.Error.UnhandledError(throwable = e))
+        }
+    }
+}
diff --git a/feature/mail/message/list/src/main/kotlin/net/thunderbird/feature/mail/message/list/ui/dialog/ChooseArchiveFolderDialogButtons.kt b/feature/mail/message/list/src/main/kotlin/net/thunderbird/feature/mail/message/list/ui/dialog/ChooseArchiveFolderDialogButtons.kt
new file mode 100644
index 00000000000..5bf17316d42
--- /dev/null
+++ b/feature/mail/message/list/src/main/kotlin/net/thunderbird/feature/mail/message/list/ui/dialog/ChooseArchiveFolderDialogButtons.kt
@@ -0,0 +1,25 @@
+package net.thunderbird.feature.mail.message.list.ui.dialog
+
+import androidx.compose.foundation.layout.RowScope
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.res.stringResource
+import app.k9mail.core.ui.compose.designsystem.atom.button.ButtonText
+import net.thunderbird.feature.mail.message.list.R
+import net.thunderbird.feature.mail.message.list.ui.dialog.SetupArchiveFolderDialogContract.State
+
+@Composable
+internal fun RowScope.ChooseArchiveFolderDialogButtons(
+    state: State.ChooseArchiveFolder,
+    onCreateNewFolderClick: () -> Unit,
+    onDoneClick: () -> Unit,
+) {
+    ButtonText(
+        text = stringResource(R.string.setup_archive_folder_dialog_create_new_folder),
+        onClick = onCreateNewFolderClick,
+    )
+    ButtonText(
+        text = stringResource(R.string.setup_archive_folder_dialog_done),
+        enabled = state.isLoadingFolders.not(),
+        onClick = onDoneClick,
+    )
+}
diff --git a/feature/mail/message/list/src/main/kotlin/net/thunderbird/feature/mail/message/list/ui/dialog/ChooseArchiveFolderDialogContent.kt b/feature/mail/message/list/src/main/kotlin/net/thunderbird/feature/mail/message/list/ui/dialog/ChooseArchiveFolderDialogContent.kt
new file mode 100644
index 00000000000..78299240b57
--- /dev/null
+++ b/feature/mail/message/list/src/main/kotlin/net/thunderbird/feature/mail/message/list/ui/dialog/ChooseArchiveFolderDialogContent.kt
@@ -0,0 +1,122 @@
+package net.thunderbird.feature.mail.message.list.ui.dialog
+
+import androidx.compose.animation.Crossfade
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.heightIn
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.items
+import androidx.compose.foundation.selection.selectable
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.semantics.Role
+import androidx.compose.ui.unit.dp
+import app.k9mail.core.ui.compose.designsystem.atom.CircularProgressIndicator
+import app.k9mail.core.ui.compose.designsystem.atom.button.RadioButton
+import app.k9mail.core.ui.compose.designsystem.atom.icon.Icon
+import app.k9mail.core.ui.compose.designsystem.atom.icon.Icons
+import app.k9mail.core.ui.compose.designsystem.atom.text.TextBodyMedium
+import app.k9mail.core.ui.compose.theme2.MainTheme
+import net.thunderbird.feature.mail.folder.api.RemoteFolder
+import net.thunderbird.feature.mail.message.list.R
+import net.thunderbird.feature.mail.message.list.ui.dialog.SetupArchiveFolderDialogContract.State
+
+@Composable
+internal fun ChooseArchiveFolderDialogContent(
+    state: State.ChooseArchiveFolder,
+    onFolderSelect: (RemoteFolder) -> Unit,
+    modifier: Modifier = Modifier,
+) {
+    Crossfade(
+        targetState = state.isLoadingFolders,
+        modifier = modifier.fillMaxWidth(),
+    ) { isLoading ->
+        when {
+            isLoading -> {
+                Box(
+                    contentAlignment = Alignment.Center,
+                    modifier = Modifier
+                        .fillMaxWidth()
+                        .padding(MainTheme.spacings.triple),
+                ) {
+                    CircularProgressIndicator()
+                }
+            }
+
+            state.errorMessage?.isNotBlank() == true -> {
+                Column(
+                    verticalArrangement = Arrangement.spacedBy(MainTheme.spacings.double),
+                    modifier = Modifier
+                        .fillMaxWidth()
+                        .padding(MainTheme.spacings.triple),
+                ) {
+                    Icon(
+                        imageVector = Icons.Outlined.ErrorOutline,
+                        tint = MainTheme.colors.error,
+                        modifier = Modifier.align(Alignment.CenterHorizontally),
+                    )
+                    TextBodyMedium(
+                        text = stringResource(R.string.setup_archive_folder_dialog_error_retrieve_folders),
+                    )
+                    TextBodyMedium(
+                        text = stringResource(
+                            R.string.setup_archive_folder_dialog_error_retrieve_folders_detailed_message,
+                            state.errorMessage,
+                        ),
+                    )
+                }
+            }
+
+            else -> {
+                LazyColumn(
+                    modifier = Modifier
+                        .fillMaxWidth()
+                        .heightIn(max = 240.dp),
+                ) {
+                    items(items = state.folders) { folder ->
+                        RemoteFolderListItem(
+                            folderName = folder.name,
+                            isSelected = state.selectedFolder == folder,
+                            onFolderSelect = { onFolderSelect(folder) },
+                        )
+                    }
+                }
+            }
+        }
+    }
+}
+
+@Composable
+private fun RemoteFolderListItem(
+    folderName: String,
+    isSelected: Boolean,
+    onFolderSelect: () -> Unit,
+    modifier: Modifier = Modifier,
+) {
+    Row(
+        verticalAlignment = Alignment.CenterVertically,
+        modifier = modifier
+            .fillMaxWidth()
+            .selectable(
+                selected = isSelected,
+                role = Role.RadioButton,
+                onClick = onFolderSelect,
+            )
+            .padding(horizontal = MainTheme.spacings.oneHalf),
+    ) {
+        RadioButton(
+            selected = isSelected,
+            label = { TextBodyMedium(text = folderName) },
+            onClick = onFolderSelect,
+            modifier = Modifier
+                .fillMaxWidth()
+                .padding(horizontal = MainTheme.spacings.oneHalf),
+        )
+    }
+}
diff --git a/feature/mail/message/list/src/main/kotlin/net/thunderbird/feature/mail/message/list/ui/dialog/CreateNewArchiveFolderDialogButtons.kt b/feature/mail/message/list/src/main/kotlin/net/thunderbird/feature/mail/message/list/ui/dialog/CreateNewArchiveFolderDialogButtons.kt
new file mode 100644
index 00000000000..25d8165c44b
--- /dev/null
+++ b/feature/mail/message/list/src/main/kotlin/net/thunderbird/feature/mail/message/list/ui/dialog/CreateNewArchiveFolderDialogButtons.kt
@@ -0,0 +1,25 @@
+package net.thunderbird.feature.mail.message.list.ui.dialog
+
+import androidx.compose.foundation.layout.RowScope
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.res.stringResource
+import app.k9mail.core.ui.compose.designsystem.atom.button.ButtonText
+import net.thunderbird.feature.mail.message.list.R
+
+@Composable
+internal fun RowScope.CreateNewArchiveFolderDialogButtons(
+    isSynchronizing: Boolean,
+    onCancelClick: () -> Unit,
+    onCreateAndSetClick: () -> Unit,
+) {
+    ButtonText(
+        onClick = onCancelClick,
+        text = stringResource(R.string.setup_archive_folder_dialog_cancel),
+        enabled = isSynchronizing.not(),
+    )
+    ButtonText(
+        onClick = onCreateAndSetClick,
+        text = stringResource(R.string.setup_archive_folder_dialog_create_and_set_new_folder),
+        enabled = isSynchronizing.not(),
+    )
+}
diff --git a/feature/mail/message/list/src/main/kotlin/net/thunderbird/feature/mail/message/list/ui/dialog/CreateNewArchiveFolderDialogContent.kt b/feature/mail/message/list/src/main/kotlin/net/thunderbird/feature/mail/message/list/ui/dialog/CreateNewArchiveFolderDialogContent.kt
new file mode 100644
index 00000000000..b84cc38ff5a
--- /dev/null
+++ b/feature/mail/message/list/src/main/kotlin/net/thunderbird/feature/mail/message/list/ui/dialog/CreateNewArchiveFolderDialogContent.kt
@@ -0,0 +1,49 @@
+package net.thunderbird.feature.mail.message.list.ui.dialog
+
+import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringResource
+import app.k9mail.core.ui.compose.designsystem.atom.text.TextBodySmall
+import app.k9mail.core.ui.compose.designsystem.molecule.input.TextInput
+import app.k9mail.core.ui.compose.theme2.MainTheme
+import net.thunderbird.feature.mail.message.list.R
+
+@Composable
+internal fun CreateNewArchiveFolderDialogContent(
+    folderName: String,
+    onFolderNameChange: (String) -> Unit,
+    modifier: Modifier = Modifier,
+    syncingMessage: String? = null,
+    errorMessage: String? = null,
+) {
+    Column(
+        modifier = modifier
+            .padding(horizontal = MainTheme.spacings.oneHalf),
+    ) {
+        TextInput(
+            onTextChange = onFolderNameChange,
+            text = folderName,
+            label = stringResource(R.string.setup_archive_folder_dialog_create_new_folder),
+            isEnabled = syncingMessage.isNullOrEmpty(),
+            errorMessage = errorMessage,
+        )
+
+        AnimatedVisibility(
+            visible = syncingMessage != null,
+            modifier = Modifier.padding(horizontal = MainTheme.spacings.quadruple),
+        ) {
+            requireNotNull(syncingMessage)
+
+            Spacer(modifier = Modifier.height(MainTheme.spacings.oneHalf))
+            TextBodySmall(
+                text = syncingMessage,
+                color = MainTheme.colors.onSurfaceVariant,
+            )
+        }
+    }
+}
diff --git a/feature/mail/message/list/src/main/kotlin/net/thunderbird/feature/mail/message/list/ui/dialog/EmailCantBeArchivedDialogButtons.kt b/feature/mail/message/list/src/main/kotlin/net/thunderbird/feature/mail/message/list/ui/dialog/EmailCantBeArchivedDialogButtons.kt
new file mode 100644
index 00000000000..52d4674ab63
--- /dev/null
+++ b/feature/mail/message/list/src/main/kotlin/net/thunderbird/feature/mail/message/list/ui/dialog/EmailCantBeArchivedDialogButtons.kt
@@ -0,0 +1,65 @@
+package net.thunderbird.feature.mail.message.list.ui.dialog
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.selection.toggleable
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.semantics.Role
+import app.k9mail.core.ui.compose.designsystem.atom.Checkbox
+import app.k9mail.core.ui.compose.designsystem.atom.button.ButtonText
+import app.k9mail.core.ui.compose.designsystem.atom.text.TextLabelSmall
+import app.k9mail.core.ui.compose.theme2.MainTheme
+import net.thunderbird.feature.mail.message.list.R
+import net.thunderbird.feature.mail.message.list.ui.dialog.SetupArchiveFolderDialogContract.State
+
+@Composable
+internal fun EmailCantBeArchivedDialogButtons(
+    state: State.EmailCantBeArchived,
+    onSetArchiveFolderClick: () -> Unit,
+    onSkipClick: () -> Unit,
+    onDoNotShowAgainChange: (Boolean) -> Unit,
+    modifier: Modifier = Modifier,
+) {
+    Column(
+        modifier = modifier.fillMaxWidth(),
+        verticalArrangement = Arrangement.spacedBy(MainTheme.spacings.oneHalf),
+    ) {
+        Box(
+            modifier = Modifier.align(Alignment.End),
+        ) {
+            Row {
+                ButtonText(
+                    text = stringResource(R.string.setup_archive_folder_dialog_skip_for_now),
+                    onClick = onSkipClick,
+                )
+                ButtonText(
+                    text = stringResource(R.string.setup_archive_folder_dialog_set_archive_folder),
+                    onClick = onSetArchiveFolderClick,
+                )
+            }
+        }
+        Row(
+            modifier = Modifier
+                .align(Alignment.Start)
+                .toggleable(
+                    value = state.isDoNotShowDialogAgainChecked,
+                    role = Role.Checkbox,
+                    onValueChange = { onDoNotShowAgainChange(!state.isDoNotShowDialogAgainChecked) },
+                ),
+            verticalAlignment = Alignment.CenterVertically,
+            horizontalArrangement = Arrangement.spacedBy(MainTheme.spacings.half),
+        ) {
+            Checkbox(
+                checked = state.isDoNotShowDialogAgainChecked,
+                onCheckedChange = null,
+            )
+            TextLabelSmall(text = stringResource(R.string.setup_archive_folder_dialog_dont_show_again))
+        }
+    }
+}
diff --git a/feature/mail/message/list/src/main/kotlin/net/thunderbird/feature/mail/message/list/ui/dialog/SetupArchiveFolderDialog.kt b/feature/mail/message/list/src/main/kotlin/net/thunderbird/feature/mail/message/list/ui/dialog/SetupArchiveFolderDialog.kt
new file mode 100644
index 00000000000..8884beabfda
--- /dev/null
+++ b/feature/mail/message/list/src/main/kotlin/net/thunderbird/feature/mail/message/list/ui/dialog/SetupArchiveFolderDialog.kt
@@ -0,0 +1,210 @@
+package net.thunderbird.feature.mail.message.list.ui.dialog
+
+import androidx.compose.animation.AnimatedContent
+import androidx.compose.animation.slideInHorizontally
+import androidx.compose.animation.slideOutHorizontally
+import androidx.compose.animation.togetherWith
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.RowScope
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.height
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.window.DialogProperties
+import app.k9mail.core.ui.compose.common.mvi.observe
+import app.k9mail.core.ui.compose.designsystem.organism.BasicDialog
+import app.k9mail.core.ui.compose.designsystem.organism.BasicDialogDefaults
+import app.k9mail.core.ui.compose.theme2.MainTheme
+import net.thunderbird.feature.mail.folder.api.RemoteFolder
+import net.thunderbird.feature.mail.message.list.R
+import net.thunderbird.feature.mail.message.list.ui.dialog.SetupArchiveFolderDialogContract.Event
+import net.thunderbird.feature.mail.message.list.ui.dialog.SetupArchiveFolderDialogContract.State
+import net.thunderbird.feature.mail.message.list.ui.dialog.SetupArchiveFolderDialogContract.ViewModel
+import org.koin.androidx.compose.koinViewModel
+import org.koin.core.parameter.parametersOf
+
+@Composable
+internal fun SetupArchiveFolderDialog(
+    accountUuid: String,
+    onDismissDialog: () -> Unit,
+    modifier: Modifier = Modifier,
+    viewModel: ViewModel = koinViewModel<ViewModel> { parametersOf(accountUuid) },
+) {
+    val (state, dispatch) = viewModel.observe { effect ->
+        when (effect) {
+            SetupArchiveFolderDialogContract.Effect.DismissDialog -> onDismissDialog()
+        }
+    }
+
+    LaunchedEffect(onDismissDialog, state.value) {
+        if (state.value is State.Closed) {
+            onDismissDialog()
+        }
+    }
+
+    SetupArchiveFolderDialog(
+        state = state.value,
+        onNextClick = { dispatch(Event.MoveNext) },
+        onDoneClick = { dispatch(Event.OnDoneClicked) },
+        onDismissRequest = { dispatch(Event.OnDismissClicked) },
+        onDismissClick = { dispatch(Event.OnDismissClicked) },
+        onDoNotShowAgainChange = { isChecked ->
+            dispatch(
+                Event.OnDoNotShowDialogAgainChanged(
+                    isChecked = isChecked,
+                ),
+            )
+        },
+        onFolderSelect = { folder -> dispatch(Event.OnFolderSelected(folder)) },
+        onCreateAndSetClick = { folderName -> dispatch(Event.OnCreateFolderClicked(folderName)) },
+        modifier = modifier,
+    )
+}
+
+@Composable
+internal fun SetupArchiveFolderDialog(
+    state: State,
+    modifier: Modifier = Modifier,
+    onNextClick: () -> Unit = {},
+    onDoneClick: () -> Unit = {},
+    onDismissRequest: () -> Unit = {},
+    onDismissClick: () -> Unit = {},
+    onDoNotShowAgainChange: (Boolean) -> Unit = {},
+    onFolderSelect: (RemoteFolder) -> Unit = {},
+    onCreateAndSetClick: (folderName: String) -> Unit = {},
+) {
+    if (state !is State.Closed) {
+        val canBeDismissed = remember(state) {
+            state !is State.CreateArchiveFolder || state.syncingMessage.isNullOrBlank()
+        }
+        var folderName by rememberSaveable(state) {
+            mutableStateOf(
+                (state as? State.CreateArchiveFolder)?.folderName ?: "",
+            )
+        }
+        BasicDialog(
+            headlineText = when (state) {
+                is State.ChooseArchiveFolder -> stringResource(R.string.setup_archive_folder_dialog_set_archive_folder)
+                is State.CreateArchiveFolder -> stringResource(R.string.setup_archive_folder_dialog_create_new_folder)
+                is State.EmailCantBeArchived ->
+                    stringResource(R.string.setup_archive_folder_dialog_email_can_not_be_archived)
+
+                else -> error("Invalid state: $state")
+            },
+            supportingText = when (state) {
+                is State.EmailCantBeArchived ->
+                    stringResource(R.string.setup_archive_folder_dialog_configure_archive_folder)
+
+                else -> null
+            },
+            onDismissRequest = onDismissRequest,
+            content = {
+                SetupArchiveFolderDialogContent(
+                    state = state,
+                    folderName = folderName,
+                    onFolderSelect = onFolderSelect,
+                    onFolderNameChange = { newFolderName -> folderName = newFolderName },
+                )
+            },
+            buttons = {
+                SetupArchiveFolderDialogButtons(
+                    state = state,
+                    folderName = folderName,
+                    onDoneClick = onDoneClick,
+                    onNextClick = onNextClick,
+                    onDismissClick = onDismissClick,
+                    onCreateAndSetClick = onCreateAndSetClick,
+                    onDoNotShowAgainChange = onDoNotShowAgainChange,
+                )
+            },
+            properties = DialogProperties(
+                dismissOnBackPress = canBeDismissed,
+                dismissOnClickOutside = canBeDismissed,
+            ),
+            contentPadding = if (state is State.EmailCantBeArchived) {
+                PaddingValues()
+            } else {
+                BasicDialogDefaults.contentPadding
+            },
+            showDividers = state !is State.EmailCantBeArchived,
+            modifier = modifier,
+        )
+    }
+}
+
+@Composable
+private fun SetupArchiveFolderDialogContent(
+    state: State,
+    folderName: String,
+    onFolderSelect: (RemoteFolder) -> Unit,
+    onFolderNameChange: (String) -> Unit,
+    modifier: Modifier = Modifier,
+) {
+    AnimatedContent(
+        targetState = state,
+        transitionSpec = {
+            slideInHorizontally(initialOffsetX = { it }) togetherWith slideOutHorizontally(
+                targetOffsetX = { -it },
+            )
+        },
+        contentKey = { it::class },
+        modifier = modifier,
+    ) { state ->
+        when (state) {
+            is State.ChooseArchiveFolder -> ChooseArchiveFolderDialogContent(
+                state = state,
+                onFolderSelect = onFolderSelect,
+            )
+
+            is State.CreateArchiveFolder -> CreateNewArchiveFolderDialogContent(
+                folderName = folderName,
+                syncingMessage = state.syncingMessage,
+                errorMessage = state.errorMessage,
+                onFolderNameChange = onFolderNameChange,
+            )
+
+            else -> Spacer(modifier = Modifier.height(MainTheme.spacings.half))
+        }
+    }
+}
+
+@Composable
+private fun RowScope.SetupArchiveFolderDialogButtons(
+    state: State,
+    folderName: String,
+    onDoneClick: () -> Unit,
+    onNextClick: () -> Unit,
+    onDismissClick: () -> Unit,
+    onCreateAndSetClick: (String) -> Unit,
+    onDoNotShowAgainChange: (Boolean) -> Unit,
+) {
+    when (state) {
+        is State.ChooseArchiveFolder -> ChooseArchiveFolderDialogButtons(
+            state = state,
+            onDoneClick = onDoneClick,
+            onCreateNewFolderClick = onNextClick,
+        )
+
+        is State.Closed -> Unit
+
+        is State.CreateArchiveFolder -> CreateNewArchiveFolderDialogButtons(
+            isSynchronizing = state.syncingMessage?.isNotBlank() == true,
+            onCancelClick = onDismissClick,
+            onCreateAndSetClick = { onCreateAndSetClick(folderName) },
+        )
+
+        is State.EmailCantBeArchived -> EmailCantBeArchivedDialogButtons(
+            state = state,
+            onSetArchiveFolderClick = onNextClick,
+            onSkipClick = onDismissClick,
+            onDoNotShowAgainChange = onDoNotShowAgainChange,
+        )
+    }
+}
diff --git a/feature/mail/message/list/src/main/kotlin/net/thunderbird/feature/mail/message/list/ui/dialog/SetupArchiveFolderDialogContract.kt b/feature/mail/message/list/src/main/kotlin/net/thunderbird/feature/mail/message/list/ui/dialog/SetupArchiveFolderDialogContract.kt
new file mode 100644
index 00000000000..e895118258a
--- /dev/null
+++ b/feature/mail/message/list/src/main/kotlin/net/thunderbird/feature/mail/message/list/ui/dialog/SetupArchiveFolderDialogContract.kt
@@ -0,0 +1,48 @@
+package net.thunderbird.feature.mail.message.list.ui.dialog
+
+import androidx.compose.runtime.Stable
+import app.k9mail.core.ui.compose.common.mvi.BaseViewModel
+import app.k9mail.core.ui.compose.common.mvi.UnidirectionalViewModel
+import net.thunderbird.feature.mail.folder.api.RemoteFolder
+
+sealed interface SetupArchiveFolderDialogContract {
+    abstract class ViewModel(
+        initialState: State,
+    ) : BaseViewModel<State, Event, Effect>(initialState), UnidirectionalViewModel<State, Event, Effect>
+
+    sealed interface State {
+        val isDoNotShowDialogAgainChecked: Boolean
+
+        data class EmailCantBeArchived(override val isDoNotShowDialogAgainChecked: Boolean = false) : State
+        data class Closed(override val isDoNotShowDialogAgainChecked: Boolean = false) : State
+
+        @Stable
+        data class ChooseArchiveFolder(
+            val isLoadingFolders: Boolean,
+            override val isDoNotShowDialogAgainChecked: Boolean = false,
+            val folders: List<RemoteFolder> = emptyList(),
+            val selectedFolder: RemoteFolder? = folders.firstOrNull(),
+            val errorMessage: String? = null,
+        ) : State
+
+        data class CreateArchiveFolder(
+            val folderName: String,
+            override val isDoNotShowDialogAgainChecked: Boolean = false,
+            val syncingMessage: String? = null,
+            val errorMessage: String? = null,
+        ) : State
+    }
+
+    sealed interface Event {
+        data object MoveNext : Event
+        data object OnDoneClicked : Event
+        data object OnDismissClicked : Event
+        data class OnFolderSelected(val folder: RemoteFolder) : Event
+        data class OnCreateFolderClicked(val newFolderName: String) : Event
+        data class OnDoNotShowDialogAgainChanged(val isChecked: Boolean) : Event
+    }
+
+    sealed interface Effect {
+        data object DismissDialog : Effect
+    }
+}
diff --git a/feature/mail/message/list/src/main/kotlin/net/thunderbird/feature/mail/message/list/ui/dialog/SetupArchiveFolderDialogFragment.kt b/feature/mail/message/list/src/main/kotlin/net/thunderbird/feature/mail/message/list/ui/dialog/SetupArchiveFolderDialogFragment.kt
new file mode 100644
index 00000000000..94ba7695f63
--- /dev/null
+++ b/feature/mail/message/list/src/main/kotlin/net/thunderbird/feature/mail/message/list/ui/dialog/SetupArchiveFolderDialogFragment.kt
@@ -0,0 +1,68 @@
+package net.thunderbird.feature.mail.message.list.ui.dialog
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.view.Window
+import androidx.compose.ui.platform.ComposeView
+import androidx.compose.ui.platform.ViewCompositionStrategy
+import androidx.core.os.bundleOf
+import androidx.fragment.app.DialogFragment
+import androidx.fragment.app.FragmentManager
+import androidx.fragment.app.setFragmentResult
+import net.thunderbird.core.ui.theme.api.FeatureThemeProvider
+import org.koin.android.ext.android.inject
+
+internal class SetupArchiveFolderDialogFragment : DialogFragment() {
+    private val themeProvider: FeatureThemeProvider by inject<FeatureThemeProvider>()
+
+    override fun onCreateView(
+        inflater: LayoutInflater,
+        container: ViewGroup?,
+        savedInstanceState: Bundle?,
+    ): View? {
+        val accountUuid = requireNotNull(requireArguments().getString(ACCOUNT_UUID_ARG)) {
+            "The $ACCOUNT_UUID_ARG argument is missing from the arguments bundle."
+        }
+        dialog?.requestWindowFeature(Window.FEATURE_NO_TITLE)
+        return ComposeView(requireContext()).apply {
+            setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
+            setContent {
+                themeProvider.WithTheme {
+                    SetupArchiveFolderDialog(
+                        accountUuid = accountUuid,
+                        onDismissDialog = {
+                            dismiss()
+                            setFragmentResult(
+                                SetupArchiveFolderDialogFragmentFactory.RESULT_CODE_DISMISS_REQUEST_KEY,
+                                Bundle.EMPTY,
+                            )
+                        },
+                    )
+                }
+            }
+        }
+    }
+
+    companion object Factory : SetupArchiveFolderDialogFragmentFactory {
+        private const val TAG = "SetupArchiveFolderDialogFragmentFactory"
+        private const val ACCOUNT_UUID_ARG = "SetupArchiveFolderDialogFragmentFactory_accountUuid"
+
+        override fun show(accountUuid: String, fragmentManager: FragmentManager) {
+            SetupArchiveFolderDialogFragment()
+                .apply {
+                    arguments = bundleOf(ACCOUNT_UUID_ARG to accountUuid)
+                }
+                .show(fragmentManager, TAG)
+        }
+    }
+}
+
+interface SetupArchiveFolderDialogFragmentFactory {
+    companion object {
+        const val RESULT_CODE_DISMISS_REQUEST_KEY = "SetupArchiveFolderDialogFragmentFactory_dialog_dismiss"
+    }
+
+    fun show(accountUuid: String, fragmentManager: FragmentManager)
+}
diff --git a/feature/mail/message/list/src/main/kotlin/net/thunderbird/feature/mail/message/list/ui/dialog/SetupArchiveFolderDialogViewModel.kt b/feature/mail/message/list/src/main/kotlin/net/thunderbird/feature/mail/message/list/ui/dialog/SetupArchiveFolderDialogViewModel.kt
new file mode 100644
index 00000000000..cc177a7e640
--- /dev/null
+++ b/feature/mail/message/list/src/main/kotlin/net/thunderbird/feature/mail/message/list/ui/dialog/SetupArchiveFolderDialogViewModel.kt
@@ -0,0 +1,303 @@
+package net.thunderbird.feature.mail.message.list.ui.dialog
+
+import androidx.lifecycle.viewModelScope
+import kotlin.time.Duration.Companion.milliseconds
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.launch
+import net.thunderbird.core.common.resources.StringsResourceManager
+import net.thunderbird.core.logging.Logger
+import net.thunderbird.core.outcome.handle
+import net.thunderbird.core.outcome.handleAsync
+import net.thunderbird.core.preference.GeneralSettingsManager
+import net.thunderbird.feature.mail.folder.api.RemoteFolder
+import net.thunderbird.feature.mail.message.list.R
+import net.thunderbird.feature.mail.message.list.domain.CreateArchiveFolderOutcome
+import net.thunderbird.feature.mail.message.list.domain.DomainContract
+import net.thunderbird.feature.mail.message.list.domain.SetAccountFolderOutcome
+import net.thunderbird.feature.mail.message.list.ui.dialog.SetupArchiveFolderDialogContract.Effect
+import net.thunderbird.feature.mail.message.list.ui.dialog.SetupArchiveFolderDialogContract.Event
+import net.thunderbird.feature.mail.message.list.ui.dialog.SetupArchiveFolderDialogContract.State
+import net.thunderbird.feature.mail.message.list.ui.dialog.SetupArchiveFolderDialogContract.ViewModel
+
+internal class SetupArchiveFolderDialogViewModel(
+    private val accountUuid: String,
+    private val logger: Logger,
+    private val getAccountFolders: DomainContract.UseCase.GetAccountFolders,
+    private val createArchiveFolder: DomainContract.UseCase.CreateArchiveFolder,
+    private val setArchiveFolder: DomainContract.UseCase.SetArchiveFolder,
+    private val resourceManager: StringsResourceManager,
+    private val generalSettingsManager: GeneralSettingsManager,
+) : ViewModel(
+    initialState = if (generalSettingsManager.getSettings().shouldShowSetupArchiveFolderDialog) {
+        State.EmailCantBeArchived()
+    } else {
+        State.Closed(isDoNotShowDialogAgainChecked = true)
+    },
+) {
+
+    override fun event(event: Event) {
+        when (event) {
+            Event.MoveNext -> onNext(state = state.value)
+
+            Event.OnDoneClicked -> onDoneClicked(state = state.value)
+
+            Event.OnDismissClicked -> onDismissClicked()
+
+            is Event.OnDoNotShowDialogAgainChanged -> onDoNotShowDialogAgainChanged(isChecked = event.isChecked)
+
+            is Event.OnCreateFolderClicked -> onCreateFolderClicked(newFolderName = event.newFolderName)
+
+            is Event.OnFolderSelected -> onFolderSelected(folder = event.folder)
+        }
+    }
+
+    private fun onNext(state: State) {
+        when (state) {
+            is State.ChooseArchiveFolder -> updateState {
+                State.CreateArchiveFolder(folderName = "")
+            }
+
+            is State.EmailCantBeArchived -> {
+                updateState { State.ChooseArchiveFolder(isLoadingFolders = true) }
+                viewModelScope.launch {
+                    getAccountFolders(accountUuid = accountUuid).handle(
+                        onSuccess = { folders ->
+                            updateState {
+                                State.ChooseArchiveFolder(
+                                    isLoadingFolders = false,
+                                    folders = folders,
+                                )
+                            }
+                        },
+                        onFailure = { error ->
+                            updateState {
+                                State.ChooseArchiveFolder(
+                                    isLoadingFolders = false,
+                                    errorMessage = error.exception.message,
+                                )
+                            }
+                        },
+                    )
+                }
+            }
+
+            else -> error("The '$state' state doesn't support the MoveNext event")
+        }
+    }
+
+    private fun onDoneClicked(state: State) {
+        check(state is State.ChooseArchiveFolder) { "The '$state' state doesn't support the OnDoneClicked event" }
+        checkNotNull(state.selectedFolder) {
+            "The selected folder is null. This should not happen."
+        }
+
+        viewModelScope.launch {
+            setArchiveFolder(accountUuid = accountUuid, folder = state.selectedFolder).handle(
+                onSuccess = {
+                    updateState { State.Closed() }
+                    emitEffect(Effect.DismissDialog)
+                },
+                onFailure = { error ->
+                    updateState {
+                        when (error) {
+                            SetAccountFolderOutcome.Error.AccountNotFound ->
+                                state.copy(
+                                    errorMessage = resourceManager.stringResource(
+                                        R.string.setup_archive_folder_set_archive_error_account_not_found,
+                                        accountUuid,
+                                    ),
+                                )
+
+                            is SetAccountFolderOutcome.Error.UnhandledError -> state.copy(
+                                errorMessage = resourceManager.stringResource(
+                                    R.string.setup_archive_folder_unhandled_error,
+                                    error.throwable.message,
+                                ),
+                            )
+                        }
+                    }
+                },
+            )
+        }
+    }
+
+    private fun onDismissClicked() {
+        viewModelScope.launch {
+            generalSettingsManager.setSetupArchiveShouldNotShowAgain(state.value.isDoNotShowDialogAgainChecked.not())
+            updateState { State.Closed() }
+
+            emitEffect(Effect.DismissDialog)
+        }
+    }
+
+    private fun onDoNotShowDialogAgainChanged(isChecked: Boolean) {
+        updateState { state ->
+            when (state) {
+                is State.EmailCantBeArchived -> state.copy(
+                    isDoNotShowDialogAgainChecked = isChecked,
+                )
+
+                else -> state
+            }
+        }
+    }
+
+    private fun onCreateFolderClicked(newFolderName: String) {
+        updateState { state ->
+            when (state) {
+                is State.CreateArchiveFolder -> state.copy(
+                    folderName = newFolderName,
+                    syncingMessage = resourceManager.stringResource(
+                        R.string.setup_archive_folder_create_archive_folder_syncing,
+                    ),
+                    errorMessage = null,
+                )
+
+                else -> state
+            }
+        }
+
+        createArchiveFolder(accountUuid = accountUuid, folderName = newFolderName)
+            .onEach { outcome ->
+                outcome.handleAsync(
+                    onSuccess = ::onCreateArchiveFolderSuccess,
+                    onFailure = ::onCreateArchiveFolderError,
+                )
+            }
+            .launchIn(viewModelScope)
+    }
+
+    private suspend fun onCreateArchiveFolderSuccess(event: CreateArchiveFolderOutcome.Success) {
+        when (event) {
+            CreateArchiveFolderOutcome.Success.LocalFolderCreated -> {
+                updateState { state ->
+                    when (state) {
+                        is State.CreateArchiveFolder -> state.copy(
+                            syncingMessage = resourceManager.stringResource(
+                                R.string.setup_archive_folder_create_archive_folder_local_folder_created,
+                            ),
+                        )
+
+                        else -> state
+                    }
+                }
+                logger.debug { "Folder created" }
+            }
+
+            CreateArchiveFolderOutcome.Success.Created -> {
+                updateState { state ->
+                    when (state) {
+                        is State.CreateArchiveFolder -> state.copy(
+                            syncingMessage = resourceManager.stringResource(
+                                R.string.setup_archive_folder_create_archive_folder_remote_folder_created,
+                            ),
+                        )
+
+                        else -> state
+                    }
+                }
+                delay(100.milliseconds)
+                updateState { State.Closed() }
+                emitEffect(Effect.DismissDialog)
+                logger.debug { "Sync finished" }
+            }
+
+            is CreateArchiveFolderOutcome.Success.SyncStarted -> {
+                updateState { state ->
+                    when (state) {
+                        is State.CreateArchiveFolder -> state.copy(
+                            syncingMessage = resourceManager.stringResource(
+                                R.string.setup_archive_folder_create_archive_folder_creating_folder_email_provider,
+                            ),
+                        )
+
+                        else -> state
+                    }
+                }
+                logger.debug { "Started sync for ${event.serverId}" }
+            }
+
+            CreateArchiveFolderOutcome.Success.UpdatingSpecialFolders ->
+                updateState { state ->
+                    when (state) {
+                        is State.CreateArchiveFolder -> state.copy(
+                            syncingMessage = resourceManager.stringResource(
+                                R.string.setup_archive_folder_create_archive_folder_updating_special_folder_rules,
+                            ),
+                        )
+
+                        else -> state
+                    }
+                }
+        }
+    }
+
+    private fun onCreateArchiveFolderError(error: CreateArchiveFolderOutcome.Error) {
+        val errorMessage = when (error) {
+            CreateArchiveFolderOutcome.Error.AccountNotFound ->
+                resourceManager.stringResource(
+                    R.string.setup_archive_folder_create_archive_folder_account_not_found,
+                    accountUuid,
+                ).also {
+                    logger.error { it }
+                }
+
+            is CreateArchiveFolderOutcome.Error.SyncError.Failed ->
+                resourceManager.stringResource(
+                    R.string.setup_archive_folder_create_archive_folder_failed_sync_folder,
+                    error.serverId,
+                    error.message,
+                ).also {
+                    logger.error(
+                        throwable = error.exception,
+                        message = { it },
+                    )
+                }
+
+            is CreateArchiveFolderOutcome.Error.UnhandledError -> resourceManager.stringResource(
+                R.string.setup_archive_folder_unhandled_error,
+                error.throwable.message,
+            ).also {
+                logger.error(throwable = error.throwable, message = { it })
+            }
+
+            is CreateArchiveFolderOutcome.Error.InvalidFolderName -> when {
+                error.folderName.isBlank() -> resourceManager.stringResource(
+                    R.string.setup_archive_folder_create_archive_folder_error_folder_name_blank,
+                )
+
+                else -> resourceManager.stringResource(
+                    R.string.setup_archive_folder_create_archive_folder_invalid_folder_name,
+                    error.folderName,
+                )
+            }
+
+            is CreateArchiveFolderOutcome.Error.LocalFolderCreationError -> resourceManager.stringResource(
+                R.string.setup_archive_folder_create_archive_folder_failed_create_local_folder,
+                error.folderName,
+            )
+        }
+
+        updateState { state ->
+            when (state) {
+                is State.CreateArchiveFolder -> state.copy(
+                    errorMessage = errorMessage,
+                    syncingMessage = null,
+                )
+
+                else -> state
+            }
+        }
+    }
+
+    private fun onFolderSelected(folder: RemoteFolder) {
+        updateState { state ->
+            when (state) {
+                is State.ChooseArchiveFolder -> state.copy(selectedFolder = folder)
+                else -> state
+            }
+        }
+    }
+}
diff --git a/feature/mail/message/list/src/main/res/values/strings.xml b/feature/mail/message/list/src/main/res/values/strings.xml
new file mode 100644
index 00000000000..bd0f6deb737
--- /dev/null
+++ b/feature/mail/message/list/src/main/res/values/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <string name="setup_archive_folder_dialog_dont_show_again">Do not show this message again</string>
+    <string name="setup_archive_folder_dialog_email_can_not_be_archived">Email can not be archived</string>
+    <string name="setup_archive_folder_dialog_configure_archive_folder">Configure archive folder now</string>
+    <string name="setup_archive_folder_dialog_skip_for_now">Skip for now</string>
+    <string name="setup_archive_folder_dialog_set_archive_folder">Set archive folder</string>
+    <string name="setup_archive_folder_dialog_error_retrieve_folders">Oops, something went wrong while retrieving account folders!</string>
+    <string name="setup_archive_folder_dialog_error_retrieve_folders_detailed_message">Error details: %1$s</string>
+    <string name="setup_archive_folder_dialog_create_new_folder">Create new folder</string>
+    <string name="setup_archive_folder_dialog_done">Done</string>
+    <string name="setup_archive_folder_dialog_cancel">Cancel</string>
+    <string name="setup_archive_folder_dialog_create_and_set_new_folder"><![CDATA[Create & set new folder]]></string>
+    <string name="setup_archive_folder_set_archive_error_account_not_found">Couldn\'t find an account associated to the \'%1$s\' UUID</string>
+    <string name="setup_archive_folder_unhandled_error">Unhandled error: %1$s</string>
+    <string name="setup_archive_folder_create_archive_folder_syncing">Syncing…</string>
+    <string name="setup_archive_folder_create_archive_folder_local_folder_created">Local folder created. Starting synchronization…</string>
+    <string name="setup_archive_folder_create_archive_folder_remote_folder_created">Remote folder created.</string>
+    <string name="setup_archive_folder_create_archive_folder_creating_folder_email_provider">Creating folder on email provider…</string>
+    <string name="setup_archive_folder_create_archive_folder_updating_special_folder_rules">Updating special folder rules…</string>
+    <string name="setup_archive_folder_create_archive_folder_account_not_found">Account (%1$s) not found</string>
+    <string name="setup_archive_folder_create_archive_folder_failed_create_local_folder">Failed to create local folder \'%1$s\'.</string>
+    <string name="setup_archive_folder_create_archive_folder_failed_sync_folder">Failed sync folder \'%1$s\' with remote. Message: %2$s</string>
+    <string name="setup_archive_folder_create_archive_folder_error_folder_name_blank">Folder name cannot be blank</string>
+    <string name="setup_archive_folder_create_archive_folder_invalid_folder_name">Invalid folder name \'%1$s\'</string>
+</resources>
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 6bab66dfc6f..fa8acd6c610 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -93,6 +93,7 @@ mime4j = "0.8.12"
 minidns = "1.0.5"
 mockito = "5.18.0"
 mockitoKotlin = "5.4.0"
+mokkery = "2.8.0"
 moshi = "1.15.2"
 mozillaAndroidComponents = "139.0.4"
 okhttp = "4.12.0"
@@ -124,6 +125,7 @@ kotlin-parcelize = { id = "org.jetbrains.kotlin.plugin.parcelize", version.ref =
 kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlinBom" }
 ksp = { id = "com.google.devtools.ksp", version.ref = "kotlinKsp" }
 spotless = { id = "com.diffplug.spotless", version.ref = "spotlessPlugin" }
+dev-mokkery = { id = "dev.mokkery", version.ref = "mokkery" }
 
 [libraries]
 android-billing = { module = "com.android.billingclient:billing", version.ref = "androidBilling" }
diff --git a/legacy/core/src/main/java/com/fsck/k9/K9.kt b/legacy/core/src/main/java/com/fsck/k9/K9.kt
index dc5f2e4750c..10042f82c4c 100644
--- a/legacy/core/src/main/java/com/fsck/k9/K9.kt
+++ b/legacy/core/src/main/java/com/fsck/k9/K9.kt
@@ -4,6 +4,10 @@ import android.content.Context
 import android.content.SharedPreferences
 import app.k9mail.feature.telemetry.api.TelemetryManager
 import app.k9mail.legacy.di.DI
+import com.fsck.k9.K9.DATABASE_VERSION_CACHE
+import com.fsck.k9.K9.areDatabasesUpToDate
+import com.fsck.k9.K9.checkCachedDatabaseVersion
+import com.fsck.k9.K9.setDatabasesUpToDate
 import com.fsck.k9.core.BuildConfig
 import com.fsck.k9.mail.K9MailLib
 import com.fsck.k9.mailstore.LocalStore
@@ -11,6 +15,8 @@ import com.fsck.k9.preferences.RealGeneralSettingsManager
 import kotlinx.datetime.Clock
 import net.thunderbird.core.android.account.AccountDefaultsProvider
 import net.thunderbird.core.android.account.SortType
+import net.thunderbird.core.common.action.SwipeAction
+import net.thunderbird.core.common.action.SwipeActions
 import net.thunderbird.core.featureflag.FeatureFlagProvider
 import net.thunderbird.core.featureflag.toFeatureFlagKey
 import net.thunderbird.core.preference.storage.Storage
@@ -396,8 +402,14 @@ object K9 : KoinComponent {
 
         k9Language = storage.getStringOrDefault("language", "")
 
-        swipeRightAction = storage.getEnum("swipeRightAction", SwipeAction.ToggleSelection)
-        swipeLeftAction = storage.getEnum("swipeLeftAction", SwipeAction.ToggleRead)
+        swipeRightAction = storage.getEnum(
+            key = SwipeActions.KEY_SWIPE_ACTION_RIGHT,
+            defaultValue = SwipeAction.ToggleSelection,
+        )
+        swipeLeftAction = storage.getEnum(
+            key = SwipeActions.KEY_SWIPE_ACTION_LEFT,
+            defaultValue = SwipeAction.ToggleRead,
+        )
 
         if (telemetryManager.isTelemetryFeatureIncluded()) {
             isTelemetryEnabled = storage.getBoolean("enableTelemetry", true)
@@ -465,8 +477,8 @@ object K9 : KoinComponent {
         editor.putInt("pgpInlineDialogCounter", pgpInlineDialogCounter)
         editor.putInt("pgpSignOnlyDialogCounter", pgpSignOnlyDialogCounter)
 
-        editor.putEnum("swipeRightAction", swipeRightAction)
-        editor.putEnum("swipeLeftAction", swipeLeftAction)
+        editor.putEnum(key = SwipeActions.KEY_SWIPE_ACTION_RIGHT, value = swipeRightAction)
+        editor.putEnum(key = SwipeActions.KEY_SWIPE_ACTION_LEFT, value = swipeLeftAction)
 
         if (telemetryManager.isTelemetryFeatureIncluded()) {
             editor.putBoolean("enableTelemetry", isTelemetryEnabled)
diff --git a/legacy/core/src/main/java/com/fsck/k9/SwipeAction.kt b/legacy/core/src/main/java/com/fsck/k9/SwipeAction.kt
deleted file mode 100644
index 6a02a1fd83f..00000000000
--- a/legacy/core/src/main/java/com/fsck/k9/SwipeAction.kt
+++ /dev/null
@@ -1,12 +0,0 @@
-package com.fsck.k9
-
-enum class SwipeAction(val removesItem: Boolean) {
-    None(removesItem = false),
-    ToggleSelection(removesItem = false),
-    ToggleRead(removesItem = false),
-    ToggleStar(removesItem = false),
-    Archive(removesItem = true),
-    Delete(removesItem = true),
-    Spam(removesItem = true),
-    Move(removesItem = true),
-}
diff --git a/legacy/core/src/main/java/com/fsck/k9/mailstore/DefaultSpecialFolderUpdater.kt b/legacy/core/src/main/java/com/fsck/k9/mailstore/DefaultSpecialFolderUpdater.kt
index c428798627b..9d052579cb9 100644
--- a/legacy/core/src/main/java/com/fsck/k9/mailstore/DefaultSpecialFolderUpdater.kt
+++ b/legacy/core/src/main/java/com/fsck/k9/mailstore/DefaultSpecialFolderUpdater.kt
@@ -107,7 +107,7 @@ class DefaultSpecialFolderUpdater private constructor(
         else -> throw AssertionError("Unsupported: $type")
     }
 
-    private fun setSpecialFolder(type: FolderType, folderId: Long?, selection: SpecialFolderSelection) {
+    override fun setSpecialFolder(type: FolderType, folderId: Long?, selection: SpecialFolderSelection) {
         if (getSpecialFolderId(type) == folderId) return
 
         when (type) {
diff --git a/legacy/core/src/main/java/com/fsck/k9/mailstore/K9BackendStorage.kt b/legacy/core/src/main/java/com/fsck/k9/mailstore/K9BackendStorage.kt
index 31915ec5e0f..22c67e0cdf7 100644
--- a/legacy/core/src/main/java/com/fsck/k9/mailstore/K9BackendStorage.kt
+++ b/legacy/core/src/main/java/com/fsck/k9/mailstore/K9BackendStorage.kt
@@ -47,8 +47,8 @@ class K9BackendStorage(
             listeners.forEach { it.onBeforeFolderListRefresh() }
         }
 
-        override fun createFolders(folders: List<FolderInfo>) {
-            if (folders.isEmpty()) return
+        override fun createFolders(folders: List<FolderInfo>): Set<Long> {
+            if (folders.isEmpty()) return emptySet()
 
             val createFolderInfo = folders.map { folderInfo ->
                 CreateFolderInfo(
@@ -58,7 +58,7 @@ class K9BackendStorage(
                     settings = folderSettingsProvider.getFolderSettings(folderInfo.serverId),
                 )
             }
-            messageStore.createFolders(createFolderInfo)
+            return messageStore.createFolders(createFolderInfo)
         }
 
         override fun deleteFolders(folderServerIds: List<String>) {
diff --git a/legacy/core/src/main/java/com/fsck/k9/preferences/GeneralSettingsDescriptions.java b/legacy/core/src/main/java/com/fsck/k9/preferences/GeneralSettingsDescriptions.java
index edd14be18bf..57868ff45b7 100644
--- a/legacy/core/src/main/java/com/fsck/k9/preferences/GeneralSettingsDescriptions.java
+++ b/legacy/core/src/main/java/com/fsck/k9/preferences/GeneralSettingsDescriptions.java
@@ -18,7 +18,6 @@
 import com.fsck.k9.K9.PostMarkAsUnreadNavigation;
 import com.fsck.k9.K9.PostRemoveNavigation;
 import com.fsck.k9.K9.SplitViewMode;
-import com.fsck.k9.SwipeAction;
 import com.fsck.k9.UiDensity;
 import com.fsck.k9.core.R;
 import com.fsck.k9.preferences.Settings.BooleanSetting;
@@ -39,6 +38,7 @@
 import com.fsck.k9.preferences.upgrader.GeneralSettingsUpgraderTo89;
 import net.thunderbird.core.android.account.AccountDefaultsProvider;
 import net.thunderbird.core.android.account.SortType;
+import net.thunderbird.core.common.action.SwipeAction;
 import net.thunderbird.core.preference.AppTheme;
 import net.thunderbird.core.preference.storage.Storage;
 import net.thunderbird.core.preference.SubTheme;
@@ -308,6 +308,12 @@ class GeneralSettingsDescriptions {
             new V(90,
                 new EnumSetting<>(PostMarkAsUnreadNavigation.class, PostMarkAsUnreadNavigation.ReturnToMessageList))
         ));
+        s.put(
+            RealGeneralSettingsManagerKt.KEY_SHOULD_SHOW_SETUP_ARCHIVE_FOLDER_DIALOG,
+            Settings.versions(
+                new V(105, new BooleanSetting(true))
+            )
+        );
 
         // TODO: Add a way to properly support feature-specific settings.
         if (telemetryManager.isTelemetryFeatureIncluded()) {
diff --git a/legacy/core/src/main/java/com/fsck/k9/preferences/KoinModule.kt b/legacy/core/src/main/java/com/fsck/k9/preferences/KoinModule.kt
index 9f7ca6c82cf..a86704aef19 100644
--- a/legacy/core/src/main/java/com/fsck/k9/preferences/KoinModule.kt
+++ b/legacy/core/src/main/java/com/fsck/k9/preferences/KoinModule.kt
@@ -1,15 +1,16 @@
 package com.fsck.k9.preferences
 
 import com.fsck.k9.Preferences
-import net.thunderbird.core.android.account.AccountManager
 import net.thunderbird.core.preference.DefaultPreferenceChangeBroker
 import net.thunderbird.core.preference.GeneralSettingsManager
 import net.thunderbird.core.preference.PreferenceChangeBroker
 import net.thunderbird.core.preference.PreferenceChangePublisher
+import net.thunderbird.feature.mail.account.api.AccountManager
 import org.koin.core.qualifier.named
 import org.koin.dsl.bind
 import org.koin.dsl.binds
 import org.koin.dsl.module
+import net.thunderbird.core.android.account.AccountManager as LegacyAccountManager
 
 val preferencesModule = module {
     factory {
@@ -23,7 +24,8 @@ val preferencesModule = module {
         )
     }
     factory { FolderSettingsProvider(folderRepository = get()) }
-    factory<AccountManager> { get<Preferences>() }
+    factory<LegacyAccountManager> { get<Preferences>() }
+    factory<AccountManager<*>> { get<LegacyAccountManager>() }
     single {
         RealGeneralSettingsManager(
             preferences = get(),
diff --git a/legacy/core/src/main/java/com/fsck/k9/preferences/RealGeneralSettingsManager.kt b/legacy/core/src/main/java/com/fsck/k9/preferences/RealGeneralSettingsManager.kt
index 42df1287848..f37f0c0359d 100644
--- a/legacy/core/src/main/java/com/fsck/k9/preferences/RealGeneralSettingsManager.kt
+++ b/legacy/core/src/main/java/com/fsck/k9/preferences/RealGeneralSettingsManager.kt
@@ -23,6 +23,8 @@ import net.thunderbird.core.preference.storage.StorageEditor
 import net.thunderbird.core.preference.storage.getEnumOrDefault
 import net.thunderbird.core.preference.storage.putEnum
 
+internal const val KEY_SHOULD_SHOW_SETUP_ARCHIVE_FOLDER_DIALOG = "shouldShowSetupArchiveFolderDialog"
+
 /**
  * Retrieve and modify general settings.
  *
@@ -156,6 +158,11 @@ internal class RealGeneralSettingsManager(
         getSettings().copy(isShowCorrespondentNames = isShowCorrespondentNames).persist()
     }
 
+    @Synchronized
+    override fun setSetupArchiveShouldNotShowAgain(shouldShowSetupArchiveFolderDialog: Boolean) {
+        getSettings().copy(shouldShowSetupArchiveFolderDialog = shouldShowSetupArchiveFolderDialog).persist()
+    }
+
     private fun writeSettings(editor: StorageEditor, settings: GeneralSettings) {
         editor.putBoolean("showRecentChanges", settings.showRecentChanges)
         editor.putEnum("theme", settings.appTheme)
@@ -167,6 +174,7 @@ internal class RealGeneralSettingsManager(
         editor.putBoolean("messageListStars", settings.isShowMessageListStars)
         editor.putBoolean("animations", settings.isShowAnimations)
         editor.putBoolean("showCorrespondentNames", settings.isShowCorrespondentNames)
+        editor.putBoolean(KEY_SHOULD_SHOW_SETUP_ARCHIVE_FOLDER_DIALOG, settings.shouldShowSetupArchiveFolderDialog)
     }
 
     private fun loadGeneralSettings(): GeneralSettings {
@@ -190,6 +198,10 @@ internal class RealGeneralSettingsManager(
             isShowMessageListStars = storage.getBoolean("messageListStars", true),
             isShowAnimations = storage.getBoolean("animations", true),
             isShowCorrespondentNames = storage.getBoolean("showCorrespondentNames", true),
+            shouldShowSetupArchiveFolderDialog = storage.getBoolean(
+                key = KEY_SHOULD_SHOW_SETUP_ARCHIVE_FOLDER_DIALOG,
+                defValue = true,
+            ),
         )
 
         updateSettingsFlow(settings)
diff --git a/legacy/core/src/main/java/com/fsck/k9/preferences/Settings.java b/legacy/core/src/main/java/com/fsck/k9/preferences/Settings.java
index 8c92e16a03a..f17b3ea5673 100644
--- a/legacy/core/src/main/java/com/fsck/k9/preferences/Settings.java
+++ b/legacy/core/src/main/java/com/fsck/k9/preferences/Settings.java
@@ -34,7 +34,7 @@ class Settings {
      *
      * @see SettingsExporter
      */
-    public static final int VERSION = 104;
+    public static final int VERSION = 105;
 
     static Map<String, Object> validate(int version, Map<String, TreeMap<Integer, SettingsDescription<?>>> settings,
             Map<String, String> importedSettings, boolean useDefaultValues) {
diff --git a/legacy/mailstore/src/main/java/app/k9mail/legacy/mailstore/FolderRepository.kt b/legacy/mailstore/src/main/java/app/k9mail/legacy/mailstore/FolderRepository.kt
index 19b783b9982..3933bfb6e94 100644
--- a/legacy/mailstore/src/main/java/app/k9mail/legacy/mailstore/FolderRepository.kt
+++ b/legacy/mailstore/src/main/java/app/k9mail/legacy/mailstore/FolderRepository.kt
@@ -2,6 +2,7 @@ package app.k9mail.legacy.mailstore
 
 import app.k9mail.legacy.mailstore.FolderTypeMapper.folderTypeOf
 import app.k9mail.legacy.mailstore.RemoteFolderTypeMapper.toFolderType
+import com.fsck.k9.mail.MessagingException
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.channels.Channel
@@ -54,8 +55,9 @@ class FolderRepository(
         }
     }
 
-    fun getRemoteFolders(account: LegacyAccount): List<RemoteFolder> {
-        val messageStore = messageStoreManager.getMessageStore(account)
+    @Throws(MessagingException::class)
+    fun getRemoteFolders(accountUuid: String): List<RemoteFolder> {
+        val messageStore = messageStoreManager.getMessageStore(accountUuid)
         return messageStore.getFolders(excludeLocalOnly = true) { folder ->
             RemoteFolder(
                 id = folder.id,
@@ -66,6 +68,10 @@ class FolderRepository(
         }
     }
 
+    @Throws(MessagingException::class)
+    fun getRemoteFolders(account: LegacyAccount): List<RemoteFolder> =
+        getRemoteFolders(account.uuid)
+
     fun getRemoteFolderDetails(account: LegacyAccount): List<RemoteFolderDetails> {
         val messageStore = messageStoreManager.getMessageStore(account)
         return messageStore.getFolders(excludeLocalOnly = true) { folder ->
diff --git a/legacy/mailstore/src/main/java/app/k9mail/legacy/mailstore/ListenableMessageStore.kt b/legacy/mailstore/src/main/java/app/k9mail/legacy/mailstore/ListenableMessageStore.kt
index 34ebfc600b0..7bad2a5e057 100644
--- a/legacy/mailstore/src/main/java/app/k9mail/legacy/mailstore/ListenableMessageStore.kt
+++ b/legacy/mailstore/src/main/java/app/k9mail/legacy/mailstore/ListenableMessageStore.kt
@@ -7,9 +7,10 @@ import net.thunderbird.feature.mail.folder.api.FolderDetails
 class ListenableMessageStore(private val messageStore: MessageStore) : MessageStore by messageStore {
     private val folderSettingsListener = CopyOnWriteArraySet<FolderSettingsChangedListener>()
 
-    override fun createFolders(folders: List<CreateFolderInfo>) {
-        messageStore.createFolders(folders)
-        notifyFolderSettingsChanged()
+    override fun createFolders(folders: List<CreateFolderInfo>): Set<Long> {
+        return messageStore.createFolders(folders).also {
+            notifyFolderSettingsChanged()
+        }
     }
 
     override fun deleteFolders(folderServerIds: List<String>) {
diff --git a/legacy/mailstore/src/main/java/app/k9mail/legacy/mailstore/MessageStore.kt b/legacy/mailstore/src/main/java/app/k9mail/legacy/mailstore/MessageStore.kt
index 7feeccab9bf..983d826e67a 100644
--- a/legacy/mailstore/src/main/java/app/k9mail/legacy/mailstore/MessageStore.kt
+++ b/legacy/mailstore/src/main/java/app/k9mail/legacy/mailstore/MessageStore.kt
@@ -3,7 +3,9 @@ package app.k9mail.legacy.mailstore
 import com.fsck.k9.mail.Flag
 import com.fsck.k9.mail.FolderType
 import com.fsck.k9.mail.Header
+import com.fsck.k9.mail.MessagingException
 import java.util.Date
+import kotlin.jvm.Throws
 import net.thunderbird.feature.mail.folder.api.FolderDetails
 import net.thunderbird.feature.search.ConditionsTreeNode
 
@@ -174,7 +176,8 @@ interface MessageStore {
     /**
      * Create folders.
      */
-    fun createFolders(folders: List<CreateFolderInfo>)
+    @Throws(MessagingException::class)
+    fun createFolders(folders: List<CreateFolderInfo>): Set<Long>
 
     /**
      * Retrieve information about a folder.
@@ -198,6 +201,7 @@ interface MessageStore {
      * @param mapper A function to map the values read from the store to a domain-specific object.
      * @return A list of values returned by [mapper].
      */
+    @Throws(MessagingException::class)
     fun <T> getFolders(excludeLocalOnly: Boolean, mapper: FolderMapper<T>): List<T>
 
     /**
@@ -244,7 +248,10 @@ interface MessageStore {
 
     /**
      * Update a folder's name and type.
+     *
+     * @throws MessagingException in case it fails changing the folder in the local database.
      */
+    @Throws(MessagingException::class)
     fun changeFolder(folderServerId: String, name: String, type: FolderType)
 
     /**
diff --git a/legacy/storage/src/main/java/com/fsck/k9/storage/messages/CreateFolderOperations.kt b/legacy/storage/src/main/java/com/fsck/k9/storage/messages/CreateFolderOperations.kt
index f2e45b3aaee..ec8b5c0fa63 100644
--- a/legacy/storage/src/main/java/com/fsck/k9/storage/messages/CreateFolderOperations.kt
+++ b/legacy/storage/src/main/java/com/fsck/k9/storage/messages/CreateFolderOperations.kt
@@ -6,7 +6,7 @@ import com.fsck.k9.mailstore.LockableDatabase
 import com.fsck.k9.mailstore.toDatabaseFolderType
 
 internal class CreateFolderOperations(private val lockableDatabase: LockableDatabase) {
-    fun createFolders(folders: List<CreateFolderInfo>) {
+    fun createFolders(folders: List<CreateFolderInfo>): Set<Long> = buildSet {
         lockableDatabase.execute(true) { db ->
             for (folder in folders) {
                 val folderSettings = folder.settings
@@ -25,6 +25,8 @@ internal class CreateFolderOperations(private val lockableDatabase: LockableData
                 }
 
                 db.insert("folders", null, values)
+                    .takeIf { it != -1L }
+                    ?.let(::add)
             }
         }
     }
diff --git a/legacy/storage/src/main/java/com/fsck/k9/storage/messages/K9MessageStore.kt b/legacy/storage/src/main/java/com/fsck/k9/storage/messages/K9MessageStore.kt
index 56c268a01ca..2b3f5f4368a 100644
--- a/legacy/storage/src/main/java/com/fsck/k9/storage/messages/K9MessageStore.kt
+++ b/legacy/storage/src/main/java/com/fsck/k9/storage/messages/K9MessageStore.kt
@@ -9,6 +9,7 @@ import app.k9mail.legacy.mailstore.SaveMessageData
 import com.fsck.k9.mail.Flag
 import com.fsck.k9.mail.FolderType
 import com.fsck.k9.mail.Header
+import com.fsck.k9.mail.MessagingException
 import com.fsck.k9.mailstore.LockableDatabase
 import com.fsck.k9.mailstore.StorageFilesProvider
 import com.fsck.k9.message.extractors.BasicPartInfoExtractor
@@ -138,9 +139,9 @@ class K9MessageStore(
         deleteMessageOperations.destroyMessages(folderId, messageServerIds)
     }
 
-    override fun createFolders(folders: List<CreateFolderInfo>) {
+    @Throws(MessagingException::class)
+    override fun createFolders(folders: List<CreateFolderInfo>): Set<Long> =
         createFolderOperations.createFolders(folders)
-    }
 
     override fun <T> getFolder(folderId: Long, mapper: FolderMapper<T>): T? {
         return retrieveFolderOperations.getFolder(folderId, mapper)
diff --git a/legacy/ui/legacy/build.gradle.kts b/legacy/ui/legacy/build.gradle.kts
index c3a3f635ee3..fe8a85477e0 100644
--- a/legacy/ui/legacy/build.gradle.kts
+++ b/legacy/ui/legacy/build.gradle.kts
@@ -27,6 +27,7 @@ dependencies {
     implementation(projects.feature.funding.api)
     implementation(projects.feature.settings.import)
     implementation(projects.feature.telemetry.api)
+    implementation(projects.feature.mail.message.list)
 
     compileOnly(projects.mail.protocols.imap)
 
diff --git a/legacy/ui/legacy/src/main/java/com/fsck/k9/activity/MessageListActivityConfig.kt b/legacy/ui/legacy/src/main/java/com/fsck/k9/activity/MessageListActivityConfig.kt
index b6419b5f024..f0f5c43eb4a 100644
--- a/legacy/ui/legacy/src/main/java/com/fsck/k9/activity/MessageListActivityConfig.kt
+++ b/legacy/ui/legacy/src/main/java/com/fsck/k9/activity/MessageListActivityConfig.kt
@@ -1,8 +1,8 @@
 package com.fsck.k9.activity
 
 import com.fsck.k9.K9
-import com.fsck.k9.SwipeAction
 import com.fsck.k9.UiDensity
+import net.thunderbird.core.common.action.SwipeAction
 import net.thunderbird.core.preference.AppTheme
 import net.thunderbird.core.preference.GeneralSettingsManager
 import net.thunderbird.core.preference.SubTheme
diff --git a/legacy/ui/legacy/src/main/java/com/fsck/k9/ui/messagelist/KoinModule.kt b/legacy/ui/legacy/src/main/java/com/fsck/k9/ui/messagelist/KoinModule.kt
index 69936a62c54..4d4ac476fbb 100644
--- a/legacy/ui/legacy/src/main/java/com/fsck/k9/ui/messagelist/KoinModule.kt
+++ b/legacy/ui/legacy/src/main/java/com/fsck/k9/ui/messagelist/KoinModule.kt
@@ -8,7 +8,7 @@ import org.koin.dsl.module
 val messageListUiModule = module {
     includes(navigationDropDownDrawerModule, navigationSideRailDrawerModule)
 
-    viewModel { MessageListViewModel(get()) }
+    viewModel { MessageListViewModel(messageListLiveDataFactory = get(), logger = get()) }
     factory { DefaultFolderProvider() }
     factory {
         MessageListLoader(
diff --git a/legacy/ui/legacy/src/main/java/com/fsck/k9/ui/messagelist/MessageListFragment.kt b/legacy/ui/legacy/src/main/java/com/fsck/k9/ui/messagelist/MessageListFragment.kt
index c96f1019f5e..10662f018fa 100644
--- a/legacy/ui/legacy/src/main/java/com/fsck/k9/ui/messagelist/MessageListFragment.kt
+++ b/legacy/ui/legacy/src/main/java/com/fsck/k9/ui/messagelist/MessageListFragment.kt
@@ -28,6 +28,7 @@ import androidx.core.view.isVisible
 import androidx.core.view.setPadding
 import androidx.core.view.updateLayoutParams
 import androidx.fragment.app.Fragment
+import androidx.fragment.app.setFragmentResultListener
 import androidx.lifecycle.Observer
 import androidx.recyclerview.widget.RecyclerView
 import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
@@ -37,7 +38,7 @@ import app.k9mail.legacy.ui.folder.FolderNameFormatter
 import app.k9mail.ui.utils.itemtouchhelper.ItemTouchHelper
 import app.k9mail.ui.utils.linearlayoutmanager.LinearLayoutManager
 import com.fsck.k9.K9
-import com.fsck.k9.SwipeAction
+import com.fsck.k9.Preferences
 import com.fsck.k9.activity.FolderInfoHolder
 import com.fsck.k9.activity.Search
 import com.fsck.k9.activity.misc.ContactPicture
@@ -66,10 +67,16 @@ import net.jcip.annotations.GuardedBy
 import net.thunderbird.core.android.account.AccountManager
 import net.thunderbird.core.android.account.Expunge
 import net.thunderbird.core.android.account.LegacyAccount
+import net.thunderbird.core.android.account.LegacyAccountWrapper
 import net.thunderbird.core.android.account.SortType
 import net.thunderbird.core.android.network.ConnectivityManager
+import net.thunderbird.core.architecture.data.DataMapper
+import net.thunderbird.core.common.action.SwipeAction
 import net.thunderbird.core.logging.legacy.Log
 import net.thunderbird.core.preference.GeneralSettingsManager
+import net.thunderbird.feature.account.storage.legacy.mapper.DefaultLegacyAccountWrapperDataMapper
+import net.thunderbird.feature.mail.message.list.domain.DomainContract
+import net.thunderbird.feature.mail.message.list.ui.dialog.SetupArchiveFolderDialogFragmentFactory
 import net.thunderbird.feature.search.LocalSearch
 import net.thunderbird.feature.search.SearchAccount
 import org.koin.android.ext.android.inject
@@ -95,6 +102,15 @@ class MessageListFragment :
     private val accountManager: AccountManager by inject()
     private val connectivityManager: ConnectivityManager by inject()
     private val clock: Clock by inject()
+    private val setupArchiveFolderDialogFragmentFactory: SetupArchiveFolderDialogFragmentFactory by inject()
+    private val legacyAccountWrapperDataMapper: DataMapper<
+        LegacyAccountWrapper,
+        LegacyAccount,
+        > by inject<DefaultLegacyAccountWrapperDataMapper>()
+    private val preferences: Preferences by inject()
+    private val buildSwipeActions: DomainContract.UseCase.BuildSwipeActions<LegacyAccount> by inject {
+        parametersOf(preferences.storage)
+    }
 
     private val handler = MessageListHandler(this)
     private val activityListener = MessageListActivityListener()
@@ -124,6 +140,7 @@ class MessageListFragment :
     private lateinit var adapter: MessageListAdapter
 
     private lateinit var accountUuids: Array<String>
+    private var accounts: List<LegacyAccountWrapper> = emptyList()
     private var account: LegacyAccount? = null
     private var currentFolder: FolderInfoHolder? = null
     private var remoteSearchFuture: Future<*>? = null
@@ -170,6 +187,8 @@ class MessageListFragment :
 
     private var error: Error? = null
 
+    private var messageListSwipeCallback: MessageListSwipeCallback? = null
+
     /**
      * Set this to `true` when the fragment should be considered active. When active, the fragment adds its actions to
      * the toolbar. When inactive, the fragment won't add its actions to the toolbar, even it is still visible, e.g. as
@@ -237,7 +256,7 @@ class MessageListFragment :
         localSearch = BundleCompat.getParcelable(arguments, ARG_SEARCH, LocalSearch::class.java)!!
 
         allAccounts = localSearch.searchAllAccounts()
-        val searchAccounts = localSearch.getAccounts(accountManager)
+        val searchAccounts = localSearch.getAccounts(accountManager).also(::updateAccountList)
         if (searchAccounts.size == 1) {
             isSingleAccountMode = true
             val singleAccount = searchAccounts[0]
@@ -295,6 +314,17 @@ class MessageListFragment :
                             ),
                         ),
                     )
+
+                setFragmentResultListener(
+                    SetupArchiveFolderDialogFragmentFactory.RESULT_CODE_DISMISS_REQUEST_KEY,
+                ) { key, bundle ->
+                    Log.d(
+                        "SetupArchiveFolderDialogFragment fragment listener triggered with " +
+                            "key: $key and bundle: $bundle",
+                    )
+                    loadMessageList(forceUpdate = true)
+                    messageListSwipeCallback?.invalidateSwipeActions(accounts)
+                }
             }
         } else {
             inflater.inflate(R.layout.message_list_error, container, false)
@@ -410,14 +440,15 @@ class MessageListFragment :
 
         val itemTouchHelper = ItemTouchHelper(
             MessageListSwipeCallback(
-                requireContext(),
+                context = requireContext(),
                 resourceProvider = SwipeResourceProvider(requireContext()),
-                swipeActionSupportProvider,
-                swipeRightAction = K9.swipeRightAction,
-                swipeLeftAction = K9.swipeLeftAction,
-                adapter,
-                swipeListener,
-            ),
+                swipeActionSupportProvider = swipeActionSupportProvider,
+                buildSwipeActions = buildSwipeActions,
+                adapter = adapter,
+                listener = swipeListener,
+                accounts = accounts,
+                legacyAccountWrapperDataMapper = legacyAccountWrapperDataMapper,
+            ).also { messageListSwipeCallback = it },
         )
         itemTouchHelper.attachToRecyclerView(recyclerView)
 
@@ -476,7 +507,7 @@ class MessageListFragment :
         }
     }
 
-    private fun loadMessageList() {
+    private fun loadMessageList(forceUpdate: Boolean = false) {
         val config = MessageListConfig(
             localSearch,
             showingThreadedList,
@@ -486,7 +517,16 @@ class MessageListFragment :
             activeMessage,
             viewModel.messageSortOverrides.toMap(),
         )
-        viewModel.loadMessageList(config)
+
+        if (forceUpdate) {
+            updateAccountList(accounts = config.search.getAccounts(accountManager))
+        }
+
+        viewModel.loadMessageList(config, forceUpdate)
+    }
+
+    private fun updateAccountList(accounts: List<LegacyAccount>) {
+        this.accounts = accounts.map(legacyAccountWrapperDataMapper::toDomain)
     }
 
     fun folderLoading(folderId: Long, loading: Boolean) {
@@ -609,6 +649,7 @@ class MessageListFragment :
 
     override fun onDestroyView() {
         recyclerView = null
+        messageListSwipeCallback = null
         itemTouchHelper = null
         swipeRefreshLayout = null
         floatingActionButton = null
@@ -1737,6 +1778,20 @@ class MessageListFragment :
                     setFlag(item, Flag.FLAGGED, !item.isStarred)
                 }
 
+                SwipeAction.ArchiveDisabled ->
+                    Snackbar
+                        .make(
+                            requireNotNull(view),
+                            R.string.archiving_not_available_for_this_account,
+                            Snackbar.LENGTH_LONG,
+                        )
+                        .show()
+
+                SwipeAction.ArchiveSetupArchiveFolder -> setupArchiveFolderDialogFragmentFactory.show(
+                    accountUuid = item.account.uuid,
+                    fragmentManager = parentFragmentManager,
+                )
+
                 SwipeAction.Archive -> {
                     onArchive(item.messageReference)
                 }
@@ -1775,8 +1830,8 @@ class MessageListFragment :
             SwipeAction.ToggleSelection -> true
             SwipeAction.ToggleRead -> !isOutbox
             SwipeAction.ToggleStar -> !isOutbox
-            SwipeAction.Archive -> {
-                !isOutbox && item.account.hasArchiveFolder() && item.folderId != item.account.archiveFolderId
+            SwipeAction.Archive, SwipeAction.ArchiveDisabled, SwipeAction.ArchiveSetupArchiveFolder -> {
+                !isOutbox && item.folderId != item.account.archiveFolderId
             }
 
             SwipeAction.Delete -> true
diff --git a/legacy/ui/legacy/src/main/java/com/fsck/k9/ui/messagelist/MessageListSwipeCallback.kt b/legacy/ui/legacy/src/main/java/com/fsck/k9/ui/messagelist/MessageListSwipeCallback.kt
index 162305cb23e..f32e06d6f3a 100644
--- a/legacy/ui/legacy/src/main/java/com/fsck/k9/ui/messagelist/MessageListSwipeCallback.kt
+++ b/legacy/ui/legacy/src/main/java/com/fsck/k9/ui/messagelist/MessageListSwipeCallback.kt
@@ -10,25 +10,34 @@ import android.view.View
 import android.view.View.MeasureSpec
 import android.widget.ImageView
 import androidx.core.graphics.withTranslation
+import androidx.core.view.isVisible
 import androidx.recyclerview.widget.RecyclerView
 import androidx.recyclerview.widget.RecyclerView.ViewHolder
 import app.k9mail.ui.utils.itemtouchhelper.ItemTouchHelper
-import com.fsck.k9.SwipeAction
 import com.fsck.k9.ui.R
 import com.google.android.material.color.ColorRoles
 import com.google.android.material.textview.MaterialTextView
 import kotlin.math.abs
+import net.thunderbird.core.android.account.LegacyAccount
+import net.thunderbird.core.android.account.LegacyAccountWrapper
+import net.thunderbird.core.architecture.data.DataMapper
+import net.thunderbird.core.common.action.SwipeAction
+import net.thunderbird.core.common.action.SwipeActions
+import net.thunderbird.feature.mail.message.list.domain.DomainContract
 
 @SuppressLint("InflateParams")
+@Suppress("LongParameterList")
 class MessageListSwipeCallback(
     context: Context,
-    resourceProvider: SwipeResourceProvider,
+    private val resourceProvider: SwipeResourceProvider,
     private val swipeActionSupportProvider: SwipeActionSupportProvider,
-    private val swipeRightAction: SwipeAction,
-    private val swipeLeftAction: SwipeAction,
+    private val buildSwipeActions: DomainContract.UseCase.BuildSwipeActions<LegacyAccount>,
     private val adapter: MessageListAdapter,
     private val listener: MessageListSwipeListener,
+    accounts: List<LegacyAccountWrapper>,
+    private val legacyAccountWrapperDataMapper: DataMapper<LegacyAccountWrapper, LegacyAccount>,
 ) : ItemTouchHelper.Callback() {
+    private var swipeActions: Map<String, SwipeActions> = emptyMap()
     private val swipePadding = context.resources.getDimension(R.dimen.messageListSwipeIconPadding).toInt()
     private val swipeThreshold = context.resources.getDimension(R.dimen.messageListSwipeThreshold)
     private val backgroundColorPaint = Paint()
@@ -39,11 +48,12 @@ class MessageListSwipeCallback(
     private val swipeLeftLayout: View
     private val swipeLeftIcon: ImageView
     private val swipeLeftText: MaterialTextView
-    private val swipeRightConfig: SwipeActionConfig?
-    private val swipeLeftConfig: SwipeActionConfig?
+    private val swipeRightConfig: MutableMap<String, SwipeActionConfig> = mutableMapOf()
+    private val swipeLeftConfig: MutableMap<String, SwipeActionConfig> = mutableMapOf()
 
     private var maxSwipeRightDistance: Int = -1
     private var maxSwipeLeftDistance: Int = -1
+    private var activeSwipingMessageListItem: MessageListItem? = null
 
     init {
         val layoutInflater = LayoutInflater.from(context)
@@ -57,18 +67,26 @@ class MessageListSwipeCallback(
         swipeLeftIcon = swipeLeftLayout.findViewById(R.id.swipe_action_icon)
         swipeLeftText = swipeLeftLayout.findViewById(R.id.swipe_action_text)
 
-        swipeRightConfig = setupSwipeAction(swipeRightAction, resourceProvider)
-        swipeLeftConfig = setupSwipeAction(swipeLeftAction, resourceProvider)
+        invalidateSwipeActions(accounts)
     }
 
     override fun isFlingEnabled(): Boolean {
         return false
     }
 
+    private val MessageListItem.swipeActions: SwipeActions
+        get() {
+            val swipeActions = this@MessageListSwipeCallback.swipeActions
+            return requireNotNull(swipeActions[account.uuid]) {
+                "Could not find swipe actions for account ${account.uuid}. swipeActions = $swipeActions"
+            }
+        }
+
     override fun getMovementFlags(recyclerView: RecyclerView, viewHolder: ViewHolder): Int {
         if (viewHolder !is MessageViewHolder) return 0
 
         val item = adapter.getItemById(viewHolder.uniqueId) ?: return 0
+        val (swipeLeftAction, swipeRightAction) = item.swipeActions
 
         var swipeFlags = 0
         if (swipeActionSupportProvider.isActionSupported(item, swipeRightAction)) {
@@ -91,10 +109,12 @@ class MessageListSwipeCallback(
 
     override fun onSwipeStarted(viewHolder: ViewHolder, direction: Int) {
         val item = viewHolder.messageListItem ?: return
+        activeSwipingMessageListItem = item
 
         // Mark view to prevent MessageListItemAnimator from interfering with swipe animations
         viewHolder.markAsSwiped(true)
 
+        val (swipeLeftAction, swipeRightAction) = item.swipeActions
         val swipeAction = when (direction) {
             ItemTouchHelper.RIGHT -> swipeRightAction
             ItemTouchHelper.LEFT -> swipeLeftAction
@@ -107,6 +127,7 @@ class MessageListSwipeCallback(
     override fun onSwipeDirectionChanged(viewHolder: ViewHolder, direction: Int) {
         val item = viewHolder.messageListItem ?: return
 
+        val (swipeLeftAction, swipeRightAction) = item.swipeActions
         val swipeAction = when (direction) {
             ItemTouchHelper.RIGHT -> swipeRightAction
             ItemTouchHelper.LEFT -> swipeLeftAction
@@ -119,6 +140,7 @@ class MessageListSwipeCallback(
     override fun onSwiped(viewHolder: ViewHolder, direction: Int) {
         val item = viewHolder.messageListItem ?: return
 
+        val (swipeLeftAction, swipeRightAction) = item.swipeActions
         when (direction) {
             ItemTouchHelper.RIGHT -> listener.onSwipeAction(item, swipeRightAction)
             ItemTouchHelper.LEFT -> listener.onSwipeAction(item, swipeLeftAction)
@@ -130,6 +152,7 @@ class MessageListSwipeCallback(
         val item = viewHolder.messageListItem ?: return
 
         listener.onSwipeEnded(item)
+        activeSwipingMessageListItem = null
     }
 
     override fun clearView(recyclerView: RecyclerView, viewHolder: ViewHolder) {
@@ -157,12 +180,12 @@ class MessageListSwipeCallback(
 
         if (dX != 0F) {
             canvas.withTranslation(x = view.left.toFloat(), y = view.top.toFloat()) {
+                val holder = viewHolder as MessageViewHolder
+                val item = adapter.getItemById(holder.uniqueId) ?: return@withTranslation
                 if (isCurrentlyActive || !success) {
-                    val holder = viewHolder as MessageViewHolder
-                    val item = adapter.getItemById(holder.uniqueId) ?: return@withTranslation
                     drawLayout(dX, viewWidth, viewHeight, item)
                 } else {
-                    drawBackground(dX, viewWidth, viewHeight)
+                    drawBackground(dX, viewWidth, viewHeight, item)
                 }
             }
         }
@@ -170,13 +193,13 @@ class MessageListSwipeCallback(
         super.onChildDraw(canvas, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive, success)
     }
 
-    private fun Canvas.drawBackground(dX: Float, width: Int, height: Int) {
+    private fun Canvas.drawBackground(dX: Float, width: Int, height: Int, item: MessageListItem) {
         val swipeActionConfig = if (dX > 0) swipeRightConfig else swipeLeftConfig
-        if (swipeActionConfig == null) {
-            error("drawBackground() called despite swipeActionConfig == null")
+        if (swipeActionConfig[item.accountWrapper.uuid] == null) {
+            error("drawBackground() called despite swipeActionConfig[item.accountWrapper] == null")
         }
 
-        backgroundColorPaint.color = swipeActionConfig.backgroundColor
+        backgroundColorPaint.color = swipeActionConfig.getValue(item.accountWrapper.uuid).backgroundColor
         drawRect(
             0F,
             0F,
@@ -189,12 +212,14 @@ class MessageListSwipeCallback(
     private fun Canvas.drawLayout(dX: Float, width: Int, height: Int, item: MessageListItem) {
         val swipeRight = dX > 0
         val swipeThresholdReached = abs(dX) > swipeThreshold
+        val account = item.accountWrapper
 
-        val swipeActionConfig = if (swipeRight) swipeRightConfig else swipeLeftConfig
+        val swipeActionConfig = if (swipeRight) swipeRightConfig[account.uuid] else swipeLeftConfig[account.uuid]
         if (swipeActionConfig == null) {
             error("drawLayout() called despite swipeActionConfig == null")
         }
 
+        val (swipeLeftAction, swipeRightAction) = item.swipeActions
         val swipeLayout = if (swipeRight) swipeRightLayout else swipeLeftLayout
         val swipeAction = if (swipeRight) swipeRightAction else swipeLeftAction
         val swipeIcon = if (swipeRight) swipeRightIcon else swipeLeftIcon
@@ -206,9 +231,9 @@ class MessageListSwipeCallback(
         swipeLayout.setBackgroundColor(backgroundColor)
 
         val icon = getIcon(swipeAction, item, swipeActionConfig)
-        icon.setTint(foregroundColor)
-
+        icon?.setTint(foregroundColor)
         swipeIcon.setImageDrawable(icon)
+        swipeIcon.isVisible = icon != null
 
         val isSelected = adapter.isSelected(item)
         swipeText.text = getActionName(swipeAction, item, isSelected, swipeActionConfig)
@@ -245,7 +270,7 @@ class MessageListSwipeCallback(
         swipeAction: SwipeAction,
         item: MessageListItem,
         swipeActionConfig: SwipeActionConfig,
-    ) = if (isToggled(swipeAction, item)) {
+    ): Drawable? = if (isToggled(swipeAction, item)) {
         swipeActionConfig.iconToggled ?: error("action has no toggled icon")
     } else {
         swipeActionConfig.icon
@@ -278,11 +303,16 @@ class MessageListSwipeCallback(
     }
 
     override fun shouldAnimateOut(direction: Int): Boolean {
-        return when (direction) {
-            ItemTouchHelper.RIGHT -> swipeRightAction.removesItem
-            ItemTouchHelper.LEFT -> swipeLeftAction.removesItem
+        val swipeActions = activeSwipingMessageListItem
+            ?.let { swipeActions[it.account.uuid] } ?: return false
+
+        val swipeAction = when (direction) {
+            ItemTouchHelper.RIGHT -> swipeActions.rightAction
+            ItemTouchHelper.LEFT -> swipeActions.leftAction
             else -> error("Unsupported direction")
         }
+
+        return swipeAction.removesItem
     }
 
     override fun getAnimationDuration(
@@ -295,14 +325,40 @@ class MessageListSwipeCallback(
         return (super.getAnimationDuration(recyclerView, animationType, animateDx, animateDy) * percentage).toLong()
     }
 
-    private fun setupSwipeAction(
-        swipeAction: SwipeAction,
+    fun invalidateSwipeActions(accounts: List<LegacyAccountWrapper>) {
+        swipeActions = buildSwipeActions(accounts)
+        swipeLeftConfig.apply {
+            clear()
+            putAll(setupSwipeActionConfig(accounts = accounts, resourceProvider = resourceProvider, isLeft = true))
+        }
+        swipeRightConfig.apply {
+            clear()
+            putAll(setupSwipeActionConfig(accounts = accounts, resourceProvider = resourceProvider, isLeft = false))
+        }
+    }
+
+    fun buildSwipeActions(accounts: List<LegacyAccountWrapper>): Map<String, SwipeActions> {
+        return buildSwipeActions(
+            accountUuids = accounts.map { it.uuid }.toSet(),
+            isIncomingServerPop3 = { account ->
+                legacyAccountWrapperDataMapper.toDomain(account).isIncomingServerPop3()
+            },
+            hasArchiveFolder = { account -> legacyAccountWrapperDataMapper.toDomain(account).hasArchiveFolder() },
+        )
+    }
+
+    private fun setupSwipeActionConfig(
+        accounts: List<LegacyAccountWrapper>,
         resourceProvider: SwipeResourceProvider,
-    ): SwipeActionConfig? {
-        return if (swipeAction == SwipeAction.None) {
-            null
-        } else {
-            SwipeActionConfig(
+        isLeft: Boolean,
+    ): Map<String, SwipeActionConfig> {
+        return accounts.associate { account ->
+            val swipeAction = requireNotNull(swipeActions[account.uuid]) {
+                "No swipe actions found for account (${account.uuid})"
+            }.let { (swipeActionLeft, swipeActionRight) ->
+                if (isLeft) swipeActionLeft else swipeActionRight
+            }
+            account.uuid to SwipeActionConfig(
                 colorRoles = resourceProvider.getActionColorRoles(swipeAction),
                 icon = resourceProvider.getActionIcon(swipeAction),
                 iconToggled = resourceProvider.getActionIconToggled(swipeAction),
@@ -322,6 +378,9 @@ class MessageListSwipeCallback(
 
     private val ViewHolder.messageListItem: MessageListItem?
         get() = (this as? MessageViewHolder)?.uniqueId?.let { adapter.getItemById(it) }
+
+    private val MessageListItem.accountWrapper: LegacyAccountWrapper
+        get() = legacyAccountWrapperDataMapper.toDomain(account)
 }
 
 fun interface SwipeActionSupportProvider {
@@ -337,7 +396,7 @@ interface MessageListSwipeListener {
 
 private data class SwipeActionConfig(
     private val colorRoles: ColorRoles,
-    val icon: Drawable,
+    val icon: Drawable?,
     val iconToggled: Drawable? = null,
     val actionName: String,
     val actionNameToggled: String? = null,
diff --git a/legacy/ui/legacy/src/main/java/com/fsck/k9/ui/messagelist/MessageListViewModel.kt b/legacy/ui/legacy/src/main/java/com/fsck/k9/ui/messagelist/MessageListViewModel.kt
index 2859dc3c09c..e1d707893fd 100644
--- a/legacy/ui/legacy/src/main/java/com/fsck/k9/ui/messagelist/MessageListViewModel.kt
+++ b/legacy/ui/legacy/src/main/java/com/fsck/k9/ui/messagelist/MessageListViewModel.kt
@@ -6,8 +6,12 @@ import androidx.lifecycle.ViewModel
 import androidx.lifecycle.viewModelScope
 import app.k9mail.legacy.message.controller.MessageReference
 import java.util.LinkedList
+import net.thunderbird.core.logging.Logger
 
-class MessageListViewModel(private val messageListLiveDataFactory: MessageListLiveDataFactory) : ViewModel() {
+class MessageListViewModel(
+    private val messageListLiveDataFactory: MessageListLiveDataFactory,
+    private val logger: Logger,
+) : ViewModel() {
     private var currentMessageListLiveData: MessageListLiveData? = null
     private val messageListLiveData = MediatorLiveData<MessageListInfo>()
 
@@ -17,8 +21,9 @@ class MessageListViewModel(private val messageListLiveDataFactory: MessageListLi
         return messageListLiveData
     }
 
-    fun loadMessageList(config: MessageListConfig) {
-        if (currentMessageListLiveData?.config == config) return
+    fun loadMessageList(config: MessageListConfig, forceUpdate: Boolean = false) {
+        logger.debug { "loadMessageList() called with: config = $config, forceUpdate = $forceUpdate" }
+        if (!forceUpdate && currentMessageListLiveData?.config == config) return
 
         removeCurrentMessageListLiveData()
 
@@ -26,6 +31,7 @@ class MessageListViewModel(private val messageListLiveDataFactory: MessageListLi
         currentMessageListLiveData = liveData
 
         messageListLiveData.addSource(liveData) { items ->
+            logger.debug { "Received new MessageListInfo: $items" }
             messageListLiveData.value = items
         }
     }
diff --git a/legacy/ui/legacy/src/main/java/com/fsck/k9/ui/messagelist/SwipeResourceProvider.kt b/legacy/ui/legacy/src/main/java/com/fsck/k9/ui/messagelist/SwipeResourceProvider.kt
index 12b92c83469..09b7badd826 100644
--- a/legacy/ui/legacy/src/main/java/com/fsck/k9/ui/messagelist/SwipeResourceProvider.kt
+++ b/legacy/ui/legacy/src/main/java/com/fsck/k9/ui/messagelist/SwipeResourceProvider.kt
@@ -8,26 +8,27 @@ import androidx.annotation.DrawableRes
 import androidx.annotation.StringRes
 import androidx.core.content.res.ResourcesCompat
 import app.k9mail.core.ui.legacy.designsystem.atom.icon.Icons
-import com.fsck.k9.SwipeAction
 import com.fsck.k9.ui.R
 import com.google.android.material.color.ColorRoles
 import com.google.android.material.color.MaterialColors
+import net.thunderbird.core.common.action.SwipeAction
 
 class SwipeResourceProvider(private val context: Context) {
 
-    fun getActionIcon(action: SwipeAction): Drawable {
-        return context.loadDrawable(
-            when (action) {
-                SwipeAction.None -> error("action == SwipeAction.None")
-                SwipeAction.ToggleSelection -> Icons.Outlined.CheckCircle
-                SwipeAction.ToggleRead -> Icons.Outlined.MarkEmailRead
-                SwipeAction.ToggleStar -> Icons.Filled.Star
-                SwipeAction.Archive -> Icons.Outlined.Archive
-                SwipeAction.Delete -> Icons.Outlined.Delete
-                SwipeAction.Spam -> Icons.Outlined.Report
-                SwipeAction.Move -> Icons.Outlined.DriveFileMove
-            },
-        )
+    fun getActionIcon(action: SwipeAction): Drawable? {
+        val drawableId = when (action) {
+            SwipeAction.None -> error("action == SwipeAction.None")
+            SwipeAction.ToggleSelection -> Icons.Outlined.CheckCircle
+            SwipeAction.ToggleRead -> Icons.Outlined.MarkEmailRead
+            SwipeAction.ToggleStar -> Icons.Filled.Star
+            SwipeAction.ArchiveDisabled -> null
+            SwipeAction.Archive, SwipeAction.ArchiveSetupArchiveFolder -> Icons.Outlined.Archive
+            SwipeAction.Delete -> Icons.Outlined.Delete
+            SwipeAction.Spam -> Icons.Outlined.Report
+            SwipeAction.Move -> Icons.Outlined.DriveFileMove
+        }
+
+        return drawableId?.let(context::loadDrawable)
     }
 
     fun getActionIconToggled(action: SwipeAction): Drawable? {
@@ -54,6 +55,10 @@ class SwipeResourceProvider(private val context: Context) {
                 SwipeAction.ToggleRead -> R.attr.messageListSwipeToggleReadColor
                 SwipeAction.ToggleStar -> R.attr.messageListSwipeToggleStarColor
                 SwipeAction.Archive -> R.attr.messageListSwipeArchiveColor
+
+                SwipeAction.ArchiveDisabled, SwipeAction.ArchiveSetupArchiveFolder ->
+                    com.google.android.material.R.attr.colorSurfaceContainerLowest
+
                 SwipeAction.Delete -> R.attr.messageListSwipeDeleteColor
                 SwipeAction.Spam -> R.attr.messageListSwipeSpamColor
                 SwipeAction.Move -> R.attr.messageListSwipeMoveColor
@@ -69,6 +74,8 @@ class SwipeResourceProvider(private val context: Context) {
                 SwipeAction.ToggleRead -> R.string.swipe_action_mark_as_read
                 SwipeAction.ToggleStar -> R.string.swipe_action_add_star
                 SwipeAction.Archive -> R.string.swipe_action_archive
+                SwipeAction.ArchiveSetupArchiveFolder -> R.string.swipe_action_archive_folder_not_set
+                SwipeAction.ArchiveDisabled -> R.string.swipe_action_change_swipe_gestures
                 SwipeAction.Delete -> R.string.swipe_action_delete
                 SwipeAction.Spam -> R.string.swipe_action_spam
                 SwipeAction.Move -> R.string.swipe_action_move
@@ -98,7 +105,7 @@ fun Context.resolveColorAttribute(attrId: Int): Int {
 
     val found = theme.resolveAttribute(attrId, typedValue, true)
     if (!found) {
-        error("Couldn't resolve attribute ($attrId)")
+        error("Couldn't resolve attribute (${resources.getResourceName(attrId)}")
     }
 
     return typedValue.data
diff --git a/legacy/ui/legacy/src/main/java/com/fsck/k9/ui/settings/general/GeneralSettingsDataStore.kt b/legacy/ui/legacy/src/main/java/com/fsck/k9/ui/settings/general/GeneralSettingsDataStore.kt
index 395a89de76b..ea7013d2a12 100644
--- a/legacy/ui/legacy/src/main/java/com/fsck/k9/ui/settings/general/GeneralSettingsDataStore.kt
+++ b/legacy/ui/legacy/src/main/java/com/fsck/k9/ui/settings/general/GeneralSettingsDataStore.kt
@@ -5,10 +5,10 @@ import app.k9mail.feature.telemetry.api.TelemetryManager
 import com.fsck.k9.K9
 import com.fsck.k9.K9.PostMarkAsUnreadNavigation
 import com.fsck.k9.K9.PostRemoveNavigation
-import com.fsck.k9.SwipeAction
 import com.fsck.k9.UiDensity
 import com.fsck.k9.job.K9JobManager
 import com.fsck.k9.ui.base.AppLanguageManager
+import net.thunderbird.core.common.action.SwipeAction
 import net.thunderbird.core.preference.AppTheme
 import net.thunderbird.core.preference.GeneralSettingsManager
 import net.thunderbird.core.preference.SubTheme
@@ -326,7 +326,7 @@ class GeneralSettingsDataStore(
         SwipeAction.ToggleSelection -> "toggle_selection"
         SwipeAction.ToggleRead -> "toggle_read"
         SwipeAction.ToggleStar -> "toggle_star"
-        SwipeAction.Archive -> "archive"
+        SwipeAction.Archive, SwipeAction.ArchiveDisabled, SwipeAction.ArchiveSetupArchiveFolder -> "archive"
         SwipeAction.Delete -> "delete"
         SwipeAction.Spam -> "spam"
         SwipeAction.Move -> "move"
diff --git a/legacy/ui/legacy/src/main/res/layout/swipe_left_action.xml b/legacy/ui/legacy/src/main/res/layout/swipe_left_action.xml
index 3dfbf7ab9b3..9a2daa55e8a 100644
--- a/legacy/ui/legacy/src/main/res/layout/swipe_left_action.xml
+++ b/legacy/ui/legacy/src/main/res/layout/swipe_left_action.xml
@@ -35,8 +35,8 @@
         android:layout_marginRight="@dimen/messageListSwipeTextPadding"
         android:layout_marginBottom="16dp"
         android:ellipsize="end"
-        android:gravity="right"
-        android:maxLines="1"
+        android:gravity="center"
+        android:maxLines="2"
         android:textAppearance="?attr/textAppearanceBodyMedium"
         app:layout_constrainedWidth="true"
         app:layout_constraintBottom_toBottomOf="parent"
diff --git a/legacy/ui/legacy/src/main/res/layout/swipe_right_action.xml b/legacy/ui/legacy/src/main/res/layout/swipe_right_action.xml
index 8c62ff642e9..2aa8170c06a 100644
--- a/legacy/ui/legacy/src/main/res/layout/swipe_right_action.xml
+++ b/legacy/ui/legacy/src/main/res/layout/swipe_right_action.xml
@@ -35,8 +35,8 @@
         android:layout_marginRight="16dp"
         android:layout_marginBottom="16dp"
         android:ellipsize="end"
-        android:gravity="left"
-        android:maxLines="1"
+        android:gravity="center"
+        android:maxLines="2"
         android:textAppearance="?attr/textAppearanceBodyMedium"
         app:layout_constrainedWidth="true"
         app:layout_constraintBottom_toBottomOf="parent"
diff --git a/legacy/ui/legacy/src/main/res/values/ids.xml b/legacy/ui/legacy/src/main/res/values/ids.xml
index 8013a938af9..e690246eb94 100644
--- a/legacy/ui/legacy/src/main/res/values/ids.xml
+++ b/legacy/ui/legacy/src/main/res/values/ids.xml
@@ -7,6 +7,7 @@
     <item type="id" name="dialog_confirm_delete" />
     <item type="id" name="dialog_confirm_spam" />
     <item type="id" name="dialog_attachment_progress" />
+    <item type="id" name="dialog_setup_archive_folder" />
 
     <item type="id" name="settings_export_list_general_item" />
     <item type="id" name="settings_export_list_account_item" />
diff --git a/legacy/ui/legacy/src/main/res/values/strings.xml b/legacy/ui/legacy/src/main/res/values/strings.xml
index cb704fd97f0..f00bcad7a5e 100644
--- a/legacy/ui/legacy/src/main/res/values/strings.xml
+++ b/legacy/ui/legacy/src/main/res/values/strings.xml
@@ -1027,6 +1027,8 @@ You can keep this message and use it as a backup for your secret key. If you wan
     <string name="swipe_action_remove_star">Remove star</string>
     <!-- Name of the swipe action to archive a message. Try to keep it short. -->
     <string name="swipe_action_archive">Archive</string>
+    <string name="swipe_action_archive_folder_not_set">Set Up Archive Folder</string>
+    <string name="swipe_action_change_swipe_gestures">Change \nSwipe Gestures</string>
     <!-- Name of the swipe action to delete a message. Try to keep it short. -->
     <string name="swipe_action_delete">Delete</string>
     <!-- Name of the swipe action to move a message to the Spam folder. Try to keep it short. -->
@@ -1088,4 +1090,5 @@ You can keep this message and use it as a backup for your secret key. If you wan
     <string name="settings_ui_telemetry_title">Usage and technical data</string>
     <!-- Description of the "Usage and technical data" setting used in the "data collection" section of the general settings screen -->
     <string name="settings_ui_telemetry_description">Shares performance, usage, hardware and customization data about this app with Mozilla to help us make Thunderbird better</string>
+    <string name="archiving_not_available_for_this_account">Archiving is not available for this account.</string>
 </resources>
diff --git a/mail/protocols/imap/src/main/java/com/fsck/k9/mail/store/imap/Capabilities.kt b/mail/protocols/imap/src/main/java/com/fsck/k9/mail/store/imap/Capabilities.kt
index 6419cfd6b53..9762634dbb9 100644
--- a/mail/protocols/imap/src/main/java/com/fsck/k9/mail/store/imap/Capabilities.kt
+++ b/mail/protocols/imap/src/main/java/com/fsck/k9/mail/store/imap/Capabilities.kt
@@ -19,5 +19,6 @@ internal object Capabilities {
     const val LIST_EXTENDED: String = "LIST-EXTENDED"
     const val MOVE: String = "MOVE"
     const val ENABLE: String = "ENABLE"
+    const val CREATE_SPECIAL_USE: String = "CREATE-SPECIAL-USE"
     const val UTF8_ACCEPT: String = "UTF8=ACCEPT"
 }
diff --git a/mail/protocols/imap/src/main/java/com/fsck/k9/mail/store/imap/ImapFolder.kt b/mail/protocols/imap/src/main/java/com/fsck/k9/mail/store/imap/ImapFolder.kt
index 9b9eaf68207..9a7df317812 100644
--- a/mail/protocols/imap/src/main/java/com/fsck/k9/mail/store/imap/ImapFolder.kt
+++ b/mail/protocols/imap/src/main/java/com/fsck/k9/mail/store/imap/ImapFolder.kt
@@ -3,6 +3,7 @@ package com.fsck.k9.mail.store.imap
 import com.fsck.k9.mail.BodyFactory
 import com.fsck.k9.mail.FetchProfile
 import com.fsck.k9.mail.Flag
+import com.fsck.k9.mail.FolderType
 import com.fsck.k9.mail.Message
 import com.fsck.k9.mail.MessageRetrievalListener
 import com.fsck.k9.mail.MessagingException
@@ -99,7 +100,7 @@ interface ImapFolder {
      * @throws MessagingException when fails to create folder on IMAP server.
      */
     @Throws(MessagingException::class)
-    fun create(): Boolean
+    fun create(folderType: FolderType = FolderType.REGULAR): Boolean
 }
 
 interface FetchListener {
diff --git a/mail/protocols/imap/src/main/java/com/fsck/k9/mail/store/imap/RealImapFolder.kt b/mail/protocols/imap/src/main/java/com/fsck/k9/mail/store/imap/RealImapFolder.kt
index c2362c7c79b..01dcaf26a65 100644
--- a/mail/protocols/imap/src/main/java/com/fsck/k9/mail/store/imap/RealImapFolder.kt
+++ b/mail/protocols/imap/src/main/java/com/fsck/k9/mail/store/imap/RealImapFolder.kt
@@ -4,6 +4,7 @@ import com.fsck.k9.mail.Body
 import com.fsck.k9.mail.BodyFactory
 import com.fsck.k9.mail.FetchProfile
 import com.fsck.k9.mail.Flag
+import com.fsck.k9.mail.FolderType
 import com.fsck.k9.mail.K9MailLib
 import com.fsck.k9.mail.Message
 import com.fsck.k9.mail.MessageRetrievalListener
@@ -25,6 +26,7 @@ import java.util.Locale
 import kotlin.math.max
 import kotlin.math.min
 import net.thunderbird.core.logging.legacy.Log
+import net.thunderbird.protocols.imap.folder.attributeName
 
 internal class RealImapFolder(
     private val internalImapStore: InternalImapStore,
@@ -243,7 +245,7 @@ internal class RealImapFolder(
     }
 
     @Throws(MessagingException::class)
-    override fun create(): Boolean {
+    override fun create(folderType: FolderType): Boolean {
         /*
          * This method needs to operate in the unselected mode as well as the selected mode
          * so we must get the connection ourselves if it's not there. We are specifically
@@ -253,11 +255,25 @@ internal class RealImapFolder(
             this.connection ?: connectionManager.getConnection()
         }
 
+        val hasSpecialUseCapability = connection.hasCapability(Capabilities.CREATE_SPECIAL_USE)
+
         return try {
             val escapedFolderName = ImapUtility.encodeString(encodedName)
 
+            // https://www.rfc-editor.org/rfc/rfc6154.html#section-5.3
+            val specialUseAttribute = folderType
+                .takeIf {
+                    hasSpecialUseCapability &&
+                        folderType != FolderType.REGULAR &&
+                        folderType != FolderType.INBOX &&
+                        folderType != FolderType.OUTBOX
+                }
+                ?.let { "(USE (${folderType.attributeName}))" }
+                .orEmpty()
+
             // https://datatracker.ietf.org/doc/html/rfc3501#section-6.3.3
-            val responses = connection.executeSimpleCommand("CREATE $escapedFolderName")
+            val command = "CREATE $escapedFolderName $specialUseAttribute".trim()
+            val responses = connection.executeSimpleCommand(command)
             responses.any { ImapResponseParser.equalsIgnoreCase(it[0], Responses.OK) }
         } catch (e: NegativeImapResponseException) {
             Log.e(e, "Unable to create folder %s for %s", serverId, logId)
@@ -646,9 +662,11 @@ internal class RealImapFolder(
                                     val bodyStream: InputStream = literal.toByteArray().inputStream()
                                     message.parse(bodyStream)
                                 }
+
                                 is Int -> {
                                     // All the work was done in FetchBodyCallback.foundLiteral()
                                 }
+
                                 else -> {
                                     // This shouldn't happen
                                     throw MessagingException("Got FETCH response with bogus parameters")
@@ -717,6 +735,7 @@ internal class RealImapFolder(
                                 // Most of the work was done in FetchAttachmentCallback.foundLiteral()
                                 MimeMessageHelper.setBody(part, literal as Body?)
                             }
+
                             is String -> {
                                 val bodyStream: InputStream = literal.toByteArray().inputStream()
                                 val contentTransferEncoding =
@@ -725,6 +744,7 @@ internal class RealImapFolder(
                                 val body = bodyFactory.createBody(contentTransferEncoding, contentType, bodyStream)
                                 MimeMessageHelper.setBody(part, body)
                             }
+
                             else -> {
                                 // This shouldn't happen
                                 throw MessagingException("Got FETCH response with bogus parameters")
@@ -753,20 +773,25 @@ internal class RealImapFolder(
                         flag.equals("\\Deleted", ignoreCase = true) -> {
                             message.setFlag(Flag.DELETED, true)
                         }
+
                         flag.equals("\\Answered", ignoreCase = true) -> {
                             message.setFlag(Flag.ANSWERED, true)
                         }
+
                         flag.equals("\\Seen", ignoreCase = true) -> {
                             message.setFlag(Flag.SEEN, true)
                         }
+
                         flag.equals("\\Flagged", ignoreCase = true) -> {
                             message.setFlag(Flag.FLAGGED, true)
                         }
+
                         flag.equals("\$Forwarded", ignoreCase = true) -> {
                             message.setFlag(Flag.FORWARDED, true)
                             // a message contains FORWARDED FLAG -> so we can also create them
                             internalImapStore.getPermanentFlagsIndex().add(Flag.FORWARDED)
                         }
+
                         flag.equals("\\Draft", ignoreCase = true) -> {
                             message.setFlag(Flag.DRAFT, true)
                         }
diff --git a/mail/protocols/imap/src/main/kotlin/net/thunderbird/protocols/imap/folder/FolderTypeAttribute.kt b/mail/protocols/imap/src/main/kotlin/net/thunderbird/protocols/imap/folder/FolderTypeAttribute.kt
new file mode 100644
index 00000000000..6c8ba375a89
--- /dev/null
+++ b/mail/protocols/imap/src/main/kotlin/net/thunderbird/protocols/imap/folder/FolderTypeAttribute.kt
@@ -0,0 +1,17 @@
+package net.thunderbird.protocols.imap.folder
+
+import com.fsck.k9.mail.FolderType
+
+/**
+ * **See Also:**
+ * [RFC-6154: New Mailbox Attributes Identifying Special-Use Mailboxes](https://www.rfc-editor.org/rfc/rfc6154.html#section-2)
+ */
+val FolderType.attributeName: String
+    get() = when (this) {
+        FolderType.DRAFTS -> "\\Drafts"
+        FolderType.SENT -> "\\Sent"
+        FolderType.TRASH -> "\\Trash"
+        FolderType.SPAM -> "\\Junk"
+        FolderType.ARCHIVE -> "\\Archive"
+        else -> error("FolderType.$this doesn't have an attribute name")
+    }
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 713913edee4..87b66f7b02c 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -96,6 +96,7 @@ include(
 include(
     ":feature:mail:account:api",
     ":feature:mail:folder:api",
+    ":feature:mail:message:list",
 )
 
 include(


Test patch:
diff --git a/app-k9mail/src/test/kotlin/app/k9mail/DependencyInjectionTest.kt b/app-k9mail/src/test/kotlin/app/k9mail/DependencyInjectionTest.kt
index 0d6ad3db23e..ea2bc73753c 100644
--- a/app-k9mail/src/test/kotlin/app/k9mail/DependencyInjectionTest.kt
+++ b/app-k9mail/src/test/kotlin/app/k9mail/DependencyInjectionTest.kt
@@ -20,7 +20,9 @@ import com.fsck.k9.ui.changelog.ChangelogViewModel
 import com.fsck.k9.view.K9WebViewClient
 import com.fsck.k9.view.MessageWebView
 import net.openid.appauth.AppAuthConfiguration
+import net.thunderbird.core.preference.storage.Storage
 import net.thunderbird.feature.account.AccountId
+import net.thunderbird.feature.mail.message.list.ui.dialog.SetupArchiveFolderDialogContract
 import org.junit.Test
 import org.koin.core.annotation.KoinExperimentalAPI
 import org.koin.test.verify.definition
@@ -45,6 +47,7 @@ class DependencyInjectionTest {
                 InteractionMode::class,
                 NotificationManager::class,
                 Resources::class,
+                Storage::class,
             ),
             injections = injectedParameters(
                 definition<AccountRemoverWorker>(WorkerParameters::class),
@@ -53,6 +56,7 @@ class DependencyInjectionTest {
                 definition<K9WebViewClient>(AttachmentResolver::class, MessageWebView.OnPageFinishedListener::class),
                 definition<MailSyncWorker>(WorkerParameters::class),
                 definition<OpenPgpApiManager>(LifecycleOwner::class),
+                definition<SetupArchiveFolderDialogContract.ViewModel>(SetupArchiveFolderDialogContract.State::class),
             ),
         )
     }
diff --git a/app-thunderbird/src/test/kotlin/net/thunderbird/android/DependencyInjectionTest.kt b/app-thunderbird/src/test/kotlin/net/thunderbird/android/DependencyInjectionTest.kt
index 46f64715fa9..a8105413fbe 100644
--- a/app-thunderbird/src/test/kotlin/net/thunderbird/android/DependencyInjectionTest.kt
+++ b/app-thunderbird/src/test/kotlin/net/thunderbird/android/DependencyInjectionTest.kt
@@ -20,7 +20,9 @@ import com.fsck.k9.ui.changelog.ChangelogViewModel
 import com.fsck.k9.view.K9WebViewClient
 import com.fsck.k9.view.MessageWebView
 import net.openid.appauth.AppAuthConfiguration
+import net.thunderbird.core.preference.storage.Storage
 import net.thunderbird.feature.account.AccountId
+import net.thunderbird.feature.mail.message.list.ui.dialog.SetupArchiveFolderDialogContract
 import org.junit.Test
 import org.koin.core.annotation.KoinExperimentalAPI
 import org.koin.test.verify.definition
@@ -45,6 +47,7 @@ class DependencyInjectionTest {
                 InteractionMode::class,
                 NotificationManager::class,
                 Resources::class,
+                Storage::class,
             ),
             injections = injectedParameters(
                 definition<AccountRemoverWorker>(WorkerParameters::class),
@@ -53,6 +56,7 @@ class DependencyInjectionTest {
                 definition<K9WebViewClient>(AttachmentResolver::class, MessageWebView.OnPageFinishedListener::class),
                 definition<MailSyncWorker>(WorkerParameters::class),
                 definition<OpenPgpApiManager>(LifecycleOwner::class),
+                definition<SetupArchiveFolderDialogContract.ViewModel>(SetupArchiveFolderDialogContract.State::class),
             ),
         )
     }
diff --git a/backend/imap/src/test/java/com/fsck/k9/backend/imap/TestImapFolder.kt b/backend/imap/src/test/java/com/fsck/k9/backend/imap/TestImapFolder.kt
index aeb2287193f..670c8c39bf8 100644
--- a/backend/imap/src/test/java/com/fsck/k9/backend/imap/TestImapFolder.kt
+++ b/backend/imap/src/test/java/com/fsck/k9/backend/imap/TestImapFolder.kt
@@ -3,6 +3,7 @@ package com.fsck.k9.backend.imap
 import com.fsck.k9.mail.BodyFactory
 import com.fsck.k9.mail.FetchProfile
 import com.fsck.k9.mail.Flag
+import com.fsck.k9.mail.FolderType
 import com.fsck.k9.mail.Message
 import com.fsck.k9.mail.MessageRetrievalListener
 import com.fsck.k9.mail.Part
@@ -188,7 +189,7 @@ open class TestImapFolder(override val serverId: String) : ImapFolder {
         throw UnsupportedOperationException("not implemented")
     }
 
-    override fun create(): Boolean {
+    override fun create(folderType: FolderType): Boolean {
         throw UnsupportedOperationException("not implemented")
     }
 }
diff --git a/backend/imap/src/test/kotlin/net/thunderbird/backend/imap/ImapRemoteFolderCreatorTest.kt b/backend/imap/src/test/kotlin/net/thunderbird/backend/imap/ImapRemoteFolderCreatorTest.kt
index db83f90cee1..92c2f628cec 100644
--- a/backend/imap/src/test/kotlin/net/thunderbird/backend/imap/ImapRemoteFolderCreatorTest.kt
+++ b/backend/imap/src/test/kotlin/net/thunderbird/backend/imap/ImapRemoteFolderCreatorTest.kt
@@ -7,6 +7,7 @@ import assertk.assertions.isInstanceOf
 import assertk.assertions.isTrue
 import assertk.assertions.prop
 import com.fsck.k9.backend.imap.TestImapFolder
+import com.fsck.k9.mail.FolderType
 import com.fsck.k9.mail.folders.FolderServerId
 import com.fsck.k9.mail.store.imap.FolderListItem
 import com.fsck.k9.mail.store.imap.ImapFolder
@@ -73,7 +74,7 @@ class ImapRemoteFolderCreatorTest {
         val folderServerId = FolderServerId("New Folder")
         val fakeFolder = object : TestImapFolder(folderServerId.serverId) {
             override fun exists(): Boolean = false
-            override fun create(): Boolean = true
+            override fun create(folderType: FolderType): Boolean = true
         }
         val imapStore = FakeImapStore(fakeFolder)
         val sut = ImapRemoteFolderCreator(logger, imapStore)
@@ -97,7 +98,7 @@ class ImapRemoteFolderCreatorTest {
         val folderServerId = FolderServerId("New Folder")
         val fakeFolder = object : TestImapFolder(folderServerId.serverId) {
             override fun exists(): Boolean = false
-            override fun create(): Boolean = false
+            override fun create(folderType: FolderType): Boolean = false
         }
         val imapStore = FakeImapStore(fakeFolder)
         val sut = ImapRemoteFolderCreator(logger, imapStore)
diff --git a/backend/testing/src/main/java/app/k9mail/backend/testing/InMemoryBackendStorage.kt b/backend/testing/src/main/java/app/k9mail/backend/testing/InMemoryBackendStorage.kt
index 909477c0c1d..058f9efc65b 100644
--- a/backend/testing/src/main/java/app/k9mail/backend/testing/InMemoryBackendStorage.kt
+++ b/backend/testing/src/main/java/app/k9mail/backend/testing/InMemoryBackendStorage.kt
@@ -35,13 +35,20 @@ class InMemoryBackendStorage : BackendStorage {
     }
 
     private inner class InMemoryBackendFolderUpdater : BackendFolderUpdater {
-        override fun createFolders(folders: List<FolderInfo>) {
-            folders.forEach { folder ->
-                if (this@InMemoryBackendStorage.folders.containsKey(folder.serverId)) {
-                    error("Folder ${folder.serverId} already present")
-                }
+        override fun createFolders(folders: List<FolderInfo>): Set<Long> {
+            var count = this@InMemoryBackendStorage.folders.size.toLong()
+            return buildSet {
+                folders.forEach { folder ->
+                    if (this@InMemoryBackendStorage.folders.containsKey(folder.serverId)) {
+                        error("Folder ${folder.serverId} already present")
+                    }
 
-                this@InMemoryBackendStorage.folders[folder.serverId] = InMemoryBackendFolder(folder.name, folder.type)
+                    this@InMemoryBackendStorage.folders[folder.serverId] = InMemoryBackendFolder(
+                        name = folder.name,
+                        type = folder.type,
+                    )
+                    add(count++)
+                }
             }
         }
 
diff --git a/feature/mail/message/list/src/test/kotlin/net/thunderbird/feature/mail/message/list/FeatureMessageListModuleKtTest.kt b/feature/mail/message/list/src/test/kotlin/net/thunderbird/feature/mail/message/list/FeatureMessageListModuleKtTest.kt
new file mode 100644
index 00000000000..89e9717010a
--- /dev/null
+++ b/feature/mail/message/list/src/test/kotlin/net/thunderbird/feature/mail/message/list/FeatureMessageListModuleKtTest.kt
@@ -0,0 +1,28 @@
+package net.thunderbird.feature.mail.message.list
+
+import kotlin.test.Test
+import net.thunderbird.core.common.resources.StringsResourceManager
+import net.thunderbird.core.logging.Logger
+import net.thunderbird.core.preference.GeneralSettingsManager
+import net.thunderbird.feature.mail.message.list.ui.dialog.SetupArchiveFolderDialogContract
+import org.koin.core.annotation.KoinExperimentalAPI
+import org.koin.test.KoinTest
+import org.koin.test.verify.definition
+import org.koin.test.verify.verify
+
+@OptIn(KoinExperimentalAPI::class)
+class FeatureMessageListModuleKtTest : KoinTest {
+    @Test
+    fun `should have a valid di module`() {
+        featureMessageListModule.verify(
+            extraTypes = listOf(
+                Logger::class,
+                StringsResourceManager::class,
+                GeneralSettingsManager::class,
+            ),
+            injections = listOf(
+                definition<SetupArchiveFolderDialogContract.ViewModel>(SetupArchiveFolderDialogContract.State::class),
+            ),
+        )
+    }
+}
diff --git a/feature/mail/message/list/src/test/kotlin/net/thunderbird/feature/mail/message/list/domain/usecase/BuildSwipeActionsTest.kt b/feature/mail/message/list/src/test/kotlin/net/thunderbird/feature/mail/message/list/domain/usecase/BuildSwipeActionsTest.kt
new file mode 100644
index 00000000000..dba5fefdd91
--- /dev/null
+++ b/feature/mail/message/list/src/test/kotlin/net/thunderbird/feature/mail/message/list/domain/usecase/BuildSwipeActionsTest.kt
@@ -0,0 +1,414 @@
+package net.thunderbird.feature.mail.message.list.domain.usecase
+
+import assertk.all
+import assertk.assertThat
+import assertk.assertions.containsOnly
+import assertk.assertions.hasSize
+import assertk.assertions.isEmpty
+import kotlin.random.Random
+import kotlin.test.Test
+import kotlin.uuid.ExperimentalUuidApi
+import kotlin.uuid.Uuid
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.update
+import net.thunderbird.core.common.action.SwipeAction
+import net.thunderbird.core.common.action.SwipeActions
+import net.thunderbird.core.preference.AppTheme
+import net.thunderbird.core.preference.BackgroundSync
+import net.thunderbird.core.preference.GeneralSettings
+import net.thunderbird.core.preference.GeneralSettingsManager
+import net.thunderbird.core.preference.SubTheme
+import net.thunderbird.core.preference.storage.Storage
+import net.thunderbird.feature.mail.message.list.fakes.FakeAccount
+import net.thunderbird.feature.mail.message.list.fakes.FakeAccountManager
+
+@OptIn(ExperimentalUuidApi::class)
+@Suppress("MaxLineLength")
+class BuildSwipeActionsTest {
+    private val defaultGeneralSettings
+        get() = GeneralSettings(
+            backgroundSync = BackgroundSync.NEVER,
+            showRecentChanges = false,
+            appTheme = AppTheme.FOLLOW_SYSTEM,
+            messageViewTheme = SubTheme.USE_GLOBAL,
+            messageComposeTheme = SubTheme.USE_GLOBAL,
+            fixedMessageViewTheme = false,
+            isShowUnifiedInbox = false,
+            isShowStarredCount = false,
+            isShowMessageListStars = false,
+            isShowAnimations = false,
+            isShowCorrespondentNames = false,
+            shouldShowSetupArchiveFolderDialog = false,
+        )
+
+    @Test
+    fun `invoke should return empty map when empty account uuids is provided`() {
+        // Arrange
+        val uuids = setOf<String>()
+        val testSubject = createTestSubject(
+            accountsUuids = List(size = 10) { Uuid.random().toHexString() },
+        )
+
+        // Act
+        val actions = testSubject(
+            accountUuids = uuids,
+            isIncomingServerPop3 = { false },
+            hasArchiveFolder = { false },
+        )
+
+        // Assert
+        assertThat(actions).isEmpty()
+    }
+
+    @Test
+    fun `invoke should return map with SwipeActions(ToggleRead, ToggleRead) when no user preference is stored`() {
+        // Arrange
+        val uuid = Uuid.random().toHexString()
+        val uuids = setOf(uuid)
+        val testSubject = createTestSubject(
+            accountsUuids = uuids.toList(),
+        )
+
+        // Act
+        val actions = testSubject(
+            accountUuids = uuids,
+            isIncomingServerPop3 = { false },
+            hasArchiveFolder = { false },
+        )
+
+        // Assert
+        assertThat(actions).all {
+            hasSize(size = 1)
+            containsOnly(
+                uuid to SwipeActions(
+                    leftAction = SwipeAction.ToggleRead,
+                    rightAction = SwipeAction.ToggleRead,
+                ),
+            )
+        }
+    }
+
+    @Test
+    fun `invoke should return map with multiple keys when multiple accounts`() {
+        // Arrange
+        val accountsSize = 10
+        val uuids = List(size = accountsSize) { Uuid.random().toHexString() }
+        val testSubject = createTestSubject(
+            accountsUuids = uuids.toList(),
+        )
+
+        // Act
+        val actions = testSubject(
+            accountUuids = uuids.toSet(),
+            isIncomingServerPop3 = { false },
+            hasArchiveFolder = { false },
+        )
+
+        // Assert
+        assertThat(actions).all {
+            hasSize(accountsSize)
+            containsOnly(
+                elements = uuids
+                    .associateWith {
+                        SwipeActions(
+                            leftAction = SwipeAction.ToggleRead,
+                            rightAction = SwipeAction.ToggleRead,
+                        )
+                    }
+                    .map { it.key to it.value }
+                    .toTypedArray(),
+            )
+        }
+    }
+
+    @Test
+    fun `invoke should return map with SwipeActions(None, ToggleRead) when left action is stored as None but right is not`() {
+        // Arrange
+        val uuid = Uuid.random().toHexString()
+        val uuids = setOf(uuid)
+        val testSubject = createTestSubject(
+            accountsUuids = uuids.toList(),
+            storageValues = mapOf(
+                SwipeActions.KEY_SWIPE_ACTION_LEFT to SwipeAction.None.name,
+            ),
+        )
+
+        // Act
+        val actions = testSubject(
+            accountUuids = uuids,
+            isIncomingServerPop3 = { false },
+            hasArchiveFolder = { false },
+        )
+
+        // Assert
+        assertThat(actions).all {
+            hasSize(size = 1)
+            containsOnly(
+                uuid to SwipeActions(
+                    leftAction = SwipeAction.None,
+                    rightAction = SwipeAction.ToggleRead,
+                ),
+            )
+        }
+    }
+
+    @Test
+    fun `invoke should return map with SwipeActions(ToggleRead, Delete) when left action is not stored but right is stored as Delete`() {
+        // Arrange
+        val uuid = Uuid.random().toHexString()
+        val uuids = setOf(uuid)
+        val testSubject = createTestSubject(
+            accountsUuids = uuids.toList(),
+            storageValues = mapOf(
+                SwipeActions.KEY_SWIPE_ACTION_RIGHT to SwipeAction.Delete.name,
+            ),
+        )
+
+        // Act
+        val actions = testSubject(
+            accountUuids = uuids,
+            isIncomingServerPop3 = { false },
+            hasArchiveFolder = { false },
+        )
+
+        // Assert
+        assertThat(actions).all {
+            hasSize(size = 1)
+            containsOnly(
+                uuid to SwipeActions(
+                    leftAction = SwipeAction.ToggleRead,
+                    rightAction = SwipeAction.Delete,
+                ),
+            )
+        }
+    }
+
+    @Test
+    fun `invoke should return map with SwipeActions(Archive, Archive) when both stored actions are Archive, account isn't pop3 and has archive folder`() {
+        // Arrange
+        val uuid = Uuid.random().toHexString()
+        val uuids = setOf(uuid)
+        val testSubject = createTestSubject(
+            accountsUuids = uuids.toList(),
+            storageValues = mapOf(
+                SwipeActions.KEY_SWIPE_ACTION_LEFT to SwipeAction.Archive.name,
+                SwipeActions.KEY_SWIPE_ACTION_RIGHT to SwipeAction.Archive.name,
+            ),
+        )
+
+        // Act
+        val actions = testSubject(
+            accountUuids = uuids,
+            isIncomingServerPop3 = { false },
+            hasArchiveFolder = { true },
+        )
+
+        // Assert
+        assertThat(actions).all {
+            hasSize(size = 1)
+            containsOnly(
+                uuid to SwipeActions(
+                    leftAction = SwipeAction.Archive,
+                    rightAction = SwipeAction.Archive,
+                ),
+            )
+        }
+    }
+
+    @Test
+    fun `invoke should return map with SwipeActions(ArchiveDisabled, ArchiveDisabled) when both stored actions are Archive, account is pop3`() {
+        // Arrange
+        val uuid = Uuid.random().toHexString()
+        val uuids = setOf(uuid)
+        val testSubject = createTestSubject(
+            accountsUuids = uuids.toList(),
+            storageValues = mapOf(
+                SwipeActions.KEY_SWIPE_ACTION_LEFT to SwipeAction.Archive.name,
+                SwipeActions.KEY_SWIPE_ACTION_RIGHT to SwipeAction.Archive.name,
+            ),
+        )
+
+        // Act
+        val actions = testSubject(
+            accountUuids = uuids,
+            isIncomingServerPop3 = { true },
+            hasArchiveFolder = { true },
+        )
+
+        // Assert
+        assertThat(actions).all {
+            hasSize(size = 1)
+            containsOnly(
+                uuid to SwipeActions(
+                    leftAction = SwipeAction.ArchiveDisabled,
+                    rightAction = SwipeAction.ArchiveDisabled,
+                ),
+            )
+        }
+    }
+
+    @Test
+    fun `invoke should return map with SwipeActions(ArchiveSetupArchiveFolder, ArchiveSetupArchiveFolder) when both stored actions are Archive, account isn't pop3, has not archive folder and shouldShowSetupArchiveFolderDialog is true`() {
+        // Arrange
+        val uuid = Uuid.random().toHexString()
+        val uuids = setOf(uuid)
+        val testSubject = createTestSubject(
+            initialGeneralSettings = defaultGeneralSettings.copy(shouldShowSetupArchiveFolderDialog = true),
+            accountsUuids = uuids.toList(),
+            storageValues = mapOf(
+                SwipeActions.KEY_SWIPE_ACTION_LEFT to SwipeAction.Archive.name,
+                SwipeActions.KEY_SWIPE_ACTION_RIGHT to SwipeAction.Archive.name,
+            ),
+        )
+
+        // Act
+        val actions = testSubject(
+            accountUuids = uuids,
+            isIncomingServerPop3 = { false },
+            hasArchiveFolder = { false },
+        )
+
+        // Assert
+        assertThat(actions).all {
+            hasSize(size = 1)
+            containsOnly(
+                uuid to SwipeActions(
+                    leftAction = SwipeAction.ArchiveSetupArchiveFolder,
+                    rightAction = SwipeAction.ArchiveSetupArchiveFolder,
+                ),
+            )
+        }
+    }
+
+    @Test
+    fun `invoke should return map with different SwipeAction Archive when multiple accounts that includes pop3 accounts or accounts without archive folder`() {
+        // Arrange
+        val uuidPop3 = "pop3-account"
+        val uuidWithoutArchiveFolder = "no-archive-folder-account"
+        val uuidWithArchiveFolder = "archive-folder-account"
+        val uuids = setOf(
+            uuidPop3,
+            uuidWithoutArchiveFolder,
+            uuidWithArchiveFolder,
+        )
+        val testSubject = createTestSubject(
+            initialGeneralSettings = defaultGeneralSettings.copy(shouldShowSetupArchiveFolderDialog = true),
+            accountsUuids = uuids.toList(),
+            storageValues = mapOf(
+                SwipeActions.KEY_SWIPE_ACTION_LEFT to SwipeAction.Archive.name,
+                SwipeActions.KEY_SWIPE_ACTION_RIGHT to SwipeAction.Archive.name,
+            ),
+        )
+
+        // Act
+        val actions = testSubject(
+            accountUuids = uuids,
+            isIncomingServerPop3 = { it.uuid == uuidPop3 },
+            hasArchiveFolder = { it.uuid == uuidWithArchiveFolder },
+        )
+
+        // Assert
+        assertThat(actions).all {
+            hasSize(size = 3)
+            containsOnly(
+                uuidPop3 to SwipeActions(
+                    leftAction = SwipeAction.ArchiveDisabled,
+                    rightAction = SwipeAction.ArchiveDisabled,
+                ),
+                uuidWithoutArchiveFolder to SwipeActions(
+                    leftAction = SwipeAction.ArchiveSetupArchiveFolder,
+                    rightAction = SwipeAction.ArchiveSetupArchiveFolder,
+                ),
+                uuidWithArchiveFolder to SwipeActions(
+                    leftAction = SwipeAction.Archive,
+                    rightAction = SwipeAction.Archive,
+                ),
+            )
+        }
+    }
+
+    @Test
+    fun `invoke should return empty map when account uuid doesn't exists in AccountManager`() {
+        // Arrange
+        val uuids = List(size = Random.nextInt(from = 1, until = 100)) { Uuid.random().toHexString() }
+        val accountManagerUuids =
+            List(size = Random.nextInt(from = 1, until = 100)) { Uuid.random().toHexString() } - uuids
+        val testSubject = createTestSubject(accountsUuids = accountManagerUuids)
+
+        // Act
+        val actions = testSubject(
+            accountUuids = uuids.toSet(),
+            isIncomingServerPop3 = { false },
+            hasArchiveFolder = { false },
+        )
+
+        // Assert
+        assertThat(actions).isEmpty()
+    }
+
+    private fun createTestSubject(
+        initialGeneralSettings: GeneralSettings = defaultGeneralSettings,
+        accountsUuids: List<String>,
+        storageValues: Map<String, String> = mapOf(),
+    ): BuildSwipeActions = BuildSwipeActions(
+        generalSettingsManager = FakeGeneralSettingsManager(initialGeneralSettings),
+        accountManager = FakeAccountManager(accounts = accountsUuids.map { FakeAccount(uuid = it) }),
+        storage = FakeStorage(storageValues),
+    )
+}
+
+private class FakeGeneralSettingsManager(
+    initialGeneralSettings: GeneralSettings,
+) : GeneralSettingsManager {
+    private val generalSettings = MutableStateFlow(initialGeneralSettings)
+    override fun getSettings(): GeneralSettings = generalSettings.value
+
+    override fun getSettingsFlow(): Flow<GeneralSettings> = generalSettings
+
+    override fun setShowRecentChanges(showRecentChanges: Boolean) = error("not implemented")
+
+    override fun setAppTheme(appTheme: AppTheme) = error("not implemented")
+
+    override fun setMessageViewTheme(subTheme: SubTheme) = error("not implemented")
+
+    override fun setMessageComposeTheme(subTheme: SubTheme) = error("not implemented")
+
+    override fun setFixedMessageViewTheme(fixedMessageViewTheme: Boolean) = error("not implemented")
+
+    override fun setIsShowUnifiedInbox(isShowUnifiedInbox: Boolean) = error("not implemented")
+
+    override fun setIsShowStarredCount(isShowStarredCount: Boolean) = error("not implemented")
+
+    override fun setIsShowMessageListStars(isShowMessageListStars: Boolean) = error("not implemented")
+
+    override fun setIsShowAnimations(isShowAnimations: Boolean) = error("not implemented")
+
+    override fun setIsShowCorrespondentNames(isShowCorrespondentNames: Boolean) = error("not implemented")
+
+    override fun setSetupArchiveShouldNotShowAgain(shouldShowSetupArchiveFolderDialog: Boolean) {
+        generalSettings.update { it.copy(shouldShowSetupArchiveFolderDialog = shouldShowSetupArchiveFolderDialog) }
+    }
+}
+
+private class FakeStorage(
+    private val values: Map<String, String>,
+) : Storage {
+    override fun isEmpty(): Boolean = error("not implemented")
+
+    override fun contains(key: String): Boolean = error("not implemented")
+
+    override fun getAll(): Map<String, String> = error("not implemented")
+
+    override fun getBoolean(key: String, defValue: Boolean): Boolean = error("not implemented")
+
+    override fun getInt(key: String, defValue: Int): Int = error("not implemented")
+
+    override fun getLong(key: String, defValue: Long): Long = error("not implemented")
+
+    override fun getString(key: String): String = error("not implemented")
+
+    override fun getStringOrDefault(key: String, defValue: String): String = error("not implemented")
+
+    override fun getStringOrNull(key: String): String? = values[key]
+}
diff --git a/feature/mail/message/list/src/test/kotlin/net/thunderbird/feature/mail/message/list/domain/usecase/CreateArchiveFolderTest.kt b/feature/mail/message/list/src/test/kotlin/net/thunderbird/feature/mail/message/list/domain/usecase/CreateArchiveFolderTest.kt
new file mode 100644
index 00000000000..3ea6e13a1b6
--- /dev/null
+++ b/feature/mail/message/list/src/test/kotlin/net/thunderbird/feature/mail/message/list/domain/usecase/CreateArchiveFolderTest.kt
@@ -0,0 +1,446 @@
+package net.thunderbird.feature.mail.message.list.domain.usecase
+
+import app.cash.turbine.test
+import assertk.assertThat
+import assertk.assertions.hasMessage
+import assertk.assertions.isEqualTo
+import assertk.assertions.isInstanceOf
+import assertk.assertions.prop
+import com.fsck.k9.backend.api.FolderInfo
+import com.fsck.k9.mail.MessagingException
+import com.fsck.k9.mail.folders.FolderServerId
+import dev.mokkery.matcher.any
+import dev.mokkery.matcher.eq
+import dev.mokkery.spy
+import dev.mokkery.verify
+import dev.mokkery.verify.VerifyMode.Companion.exactly
+import dev.mokkery.verifySuspend
+import kotlin.test.Test
+import kotlin.uuid.ExperimentalUuidApi
+import kotlin.uuid.Uuid
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import net.thunderbird.backend.api.folder.RemoteFolderCreationOutcome
+import net.thunderbird.backend.api.folder.RemoteFolderCreator
+import net.thunderbird.core.outcome.Outcome
+import net.thunderbird.feature.mail.account.api.BaseAccount
+import net.thunderbird.feature.mail.folder.api.FolderType
+import net.thunderbird.feature.mail.folder.api.SpecialFolderSelection
+import net.thunderbird.feature.mail.message.list.domain.CreateArchiveFolderOutcome
+import net.thunderbird.feature.mail.message.list.fakes.FakeAccount
+import net.thunderbird.feature.mail.message.list.fakes.FakeAccountManager
+import net.thunderbird.feature.mail.message.list.fakes.FakeBackendFolderUpdater
+import net.thunderbird.feature.mail.message.list.fakes.FakeBackendStorageFactory
+import net.thunderbird.feature.mail.message.list.fakes.FakeSpecialFolderUpdaterFactory
+import com.fsck.k9.mail.FolderType as LegacyFolderType
+
+@OptIn(ExperimentalUuidApi::class)
+@Suppress("MaxLineLength")
+class CreateArchiveFolderTest {
+    @Test
+    fun `invoke should emit InvalidFolderName and complete flow when folderName is invalid`() = runTest {
+        // Arrange
+        val accountUuid = Uuid.random().toHexString()
+        val accounts = createAccountList(accountUuid = accountUuid)
+        val accountManager = spy(FakeAccountManager(accounts))
+        val testSubject = createTestSubject(accountManager = accountManager)
+        val folderName = ""
+
+        // Act
+        testSubject(accountUuid, folderName).test {
+            // Assert
+            val outcome = awaitItem()
+            assertThat(outcome)
+                .isInstanceOf<Outcome.Failure<CreateArchiveFolderOutcome.Error>>()
+                .prop("error") { it.error }
+                .isInstanceOf<CreateArchiveFolderOutcome.Error.InvalidFolderName>()
+                .prop("folderName") { it.folderName }
+                .isEqualTo(folderName)
+
+            verify(exactly(0)) { accountManager.getAccount(accountUuid = any()) }
+
+            awaitComplete()
+        }
+    }
+
+    @Test
+    fun `invoke should emit AccountNotFound and complete flow when no account uuid matches with account list`() =
+        runTest {
+            // Arrange
+            val accountUuid = "any-non-expected-account-uuid"
+            val accounts = createAccountList()
+            val accountManager = spy(FakeAccountManager(accounts))
+            val testSubject = createTestSubject(accountManager = accountManager)
+            val folderName = "TheFolder"
+
+            // Act
+            testSubject(accountUuid, folderName).test {
+                // Assert
+                val outcome = awaitItem()
+                assertThat(outcome)
+                    .isInstanceOf<Outcome.Failure<CreateArchiveFolderOutcome.Error>>()
+                    .prop("error") { it.error }
+                    .isEqualTo(CreateArchiveFolderOutcome.Error.AccountNotFound)
+
+                verify(exactly(1)) { accountManager.getAccount(accountUuid) }
+                awaitComplete()
+            }
+        }
+
+    @Test
+    fun `invoke should emit UnhandledError and complete flow when BackendStorage createFolder throws MessagingException`() =
+        runTest {
+            // Arrange
+            val accountUuid = Uuid.random().toHexString()
+            val accounts = createAccountList(accountUuid)
+            val exception = MessagingException("this is an error")
+            val backendFolderUpdater = FakeBackendFolderUpdater(exception)
+            val remoteFolderCreatorFactory = spy(FakeRemoteFolderCreatorFactory(outcome = null))
+            val testSubject = createTestSubject(
+                accounts = accounts,
+                backendStorageFactory = FakeBackendStorageFactory(backendFolderUpdater),
+                remoteFolderCreatorFactory = remoteFolderCreatorFactory,
+            )
+            val folderName = "TheFolder"
+
+            // Act
+            testSubject(accountUuid, folderName).test {
+                // Assert
+                val outcome = awaitItem()
+                assertThat(outcome)
+                    .isInstanceOf<Outcome.Failure<CreateArchiveFolderOutcome.Error>>()
+                    .prop("error") { it.error }
+                    .isInstanceOf<CreateArchiveFolderOutcome.Error.UnhandledError>()
+                    .prop("throwable") { it.throwable }
+                    .hasMessage(exception.message)
+
+                verify(exactly(0)) { remoteFolderCreatorFactory.create(account = any()) }
+
+                awaitComplete()
+            }
+        }
+
+    @Test
+    fun `invoke should emit LocalFolderCreationError and complete flow when BackendStorage createFolder returns null`() =
+        runTest {
+            // Arrange
+            val accountUuid = Uuid.random().toHexString()
+            val accounts = createAccountList(accountUuid)
+            val backendStorageFactory = FakeBackendStorageFactory(
+                FakeBackendFolderUpdater(
+                    returnEmptySetWhenCreatingFolders = true,
+                ),
+            )
+            val remoteFolderCreatorFactory = spy(FakeRemoteFolderCreatorFactory(outcome = null))
+            val testSubject = createTestSubject(
+                accounts = accounts,
+                backendStorageFactory = backendStorageFactory,
+            )
+            val folderName = "TheFolder"
+
+            // Act
+            testSubject(accountUuid, folderName).test {
+                // Assert
+                val outcome = awaitItem()
+                assertThat(outcome)
+                    .isInstanceOf<Outcome.Failure<CreateArchiveFolderOutcome.Error>>()
+                    .prop("error") { it.error }
+                    .isInstanceOf<CreateArchiveFolderOutcome.Error.LocalFolderCreationError>()
+                    .prop("folderName") { it.folderName }
+                    .isEqualTo(folderName)
+
+                verify(exactly(1)) {
+                    // verify doesn't support verifying the extension function `createFolder`,
+                    // thus we verify the call of `createFolders(list)` instead.
+                    backendStorageFactory.backendFolderUpdater.createFolders(
+                        eq(
+                            listOf(
+                                FolderInfo(
+                                    serverId = folderName,
+                                    name = folderName,
+                                    type = LegacyFolderType.ARCHIVE,
+                                ),
+                            ),
+                        ),
+                    )
+                }
+                verify(exactly(0)) { remoteFolderCreatorFactory.create(account = any()) }
+                awaitComplete()
+            }
+        }
+
+    @Test
+    fun `invoke should emit LocalFolderCreated when BackendStorage createFolder returns folderId`() = runTest {
+        // Arrange
+        val accountUuid = Uuid.random().toHexString()
+        val accounts = createAccountList(accountUuid = accountUuid)
+        val backendStorageFactory = FakeBackendStorageFactory(
+            FakeBackendFolderUpdater(),
+        )
+        val testSubject = createTestSubject(
+            accounts = accounts,
+            remoteFolderCreatorOutcome = Outcome.success(RemoteFolderCreationOutcome.Success.Created),
+            backendStorageFactory = backendStorageFactory,
+        )
+        val folderName = "TheFolder"
+
+        // Act
+        testSubject(accountUuid, folderName).test {
+            // Assert
+            val outcome = awaitItem()
+            assertThat(outcome)
+                .isInstanceOf<Outcome.Success<CreateArchiveFolderOutcome.Success>>()
+                .prop("data") { it.data }
+                .isEqualTo(CreateArchiveFolderOutcome.Success.LocalFolderCreated)
+
+            verify(exactly(1)) {
+                // verify doesn't support verifying the extension function `createFolder`,
+                // thus we verify the call of `createFolders(list)` instead.
+                backendStorageFactory.backendFolderUpdater.createFolders(
+                    eq(
+                        listOf(
+                            FolderInfo(
+                                serverId = folderName,
+                                name = folderName,
+                                type = LegacyFolderType.ARCHIVE,
+                            ),
+                        ),
+                    ),
+                )
+            }
+
+            cancelAndIgnoreRemainingEvents()
+        }
+    }
+
+    @Test
+    fun `invoke should emit SyncStarted when local folder synchronization with remote starts`() = runTest {
+        // Arrange
+        val accountUuid = Uuid.random().toHexString()
+        val accounts = createAccountList(accountUuid)
+        val backendStorageFactory = FakeBackendStorageFactory(
+            FakeBackendFolderUpdater(),
+        )
+        val testSubject = createTestSubject(
+            accounts = accounts,
+            remoteFolderCreatorOutcome = Outcome.success(RemoteFolderCreationOutcome.Success.Created),
+            backendStorageFactory = backendStorageFactory,
+        )
+        val folderName = "TheFolder"
+
+        // Act
+        testSubject(accountUuid, folderName).test {
+            // Assert
+            skipItems(count = 1) // Skip LocalFolderCreated event.
+            val outcome = awaitItem()
+            assertThat(outcome)
+                .isInstanceOf<Outcome.Success<CreateArchiveFolderOutcome.Success>>()
+                .prop("data") { it.data }
+                .isInstanceOf<CreateArchiveFolderOutcome.Success.SyncStarted>()
+                .prop("serverId") { it.serverId }
+                .isEqualTo(FolderServerId(folderName))
+
+            verify(exactly(1)) {
+                // verify doesn't support verifying the extension function `createFolder`,
+                // thus we verify the call of `createFolders(list)` instead.
+                backendStorageFactory.backendFolderUpdater.createFolders(
+                    eq(
+                        listOf(
+                            FolderInfo(
+                                serverId = folderName,
+                                name = folderName,
+                                type = LegacyFolderType.ARCHIVE,
+                            ),
+                        ),
+                    ),
+                )
+            }
+
+            cancelAndIgnoreRemainingEvents()
+        }
+    }
+
+    @Test
+    fun `invoke should emit SyncError when remote folder creation fails for any reason`() = runTest {
+        // Arrange
+        val accountUuid = Uuid.random().toHexString()
+        val accounts = createAccountList(accountUuid)
+        val backendStorageFactory = FakeBackendStorageFactory(
+            FakeBackendFolderUpdater(),
+        )
+        val error = RemoteFolderCreationOutcome.Error.AlreadyExists
+        val testSubject = createTestSubject(
+            accounts = accounts,
+            remoteFolderCreatorOutcome = Outcome.failure(error),
+            backendStorageFactory = backendStorageFactory,
+        )
+        val folderName = "TheFolder"
+
+        // Act
+        testSubject(accountUuid, folderName).test {
+            // Assert
+            skipItems(count = 2) // Skip LocalFolderCreated and SyncStarted event.
+            val outcome = awaitItem()
+            assertThat(outcome)
+                .isInstanceOf<Outcome.Failure<CreateArchiveFolderOutcome.Error>>()
+                .prop("error") { it.error }
+                .isInstanceOf<CreateArchiveFolderOutcome.Error.SyncError.Failed>()
+                .isEqualTo(
+                    CreateArchiveFolderOutcome.Error.SyncError.Failed(
+                        serverId = FolderServerId(folderName),
+                        message = error.toString(),
+                        exception = null,
+                    ),
+                )
+
+            verify(exactly(1)) {
+                // verify doesn't support verifying the extension function `createFolder`,
+                // thus we verify the call of `createFolders(list)` instead.
+                backendStorageFactory.backendFolderUpdater.createFolders(
+                    eq(
+                        listOf(
+                            FolderInfo(
+                                serverId = folderName,
+                                name = folderName,
+                                type = LegacyFolderType.ARCHIVE,
+                            ),
+                        ),
+                    ),
+                )
+            }
+
+            cancelAndIgnoreRemainingEvents()
+        }
+    }
+
+    @Test
+    @Suppress("LongMethod")
+    fun `invoke should emit Success when local and remote folder creation succeed`() = runTest {
+        // Arrange
+        val accountUuid = Uuid.random().toHexString()
+        val accounts = createAccountList(accountUuid)
+        val accountManager = spy(FakeAccountManager(accounts))
+        val backendStorageFactory = FakeBackendStorageFactory(
+            FakeBackendFolderUpdater(),
+        )
+        val specialFolderUpdaterFactory = FakeSpecialFolderUpdaterFactory()
+        val remoteFolderCreatorFactory = FakeRemoteFolderCreatorFactory(
+            Outcome.success(RemoteFolderCreationOutcome.Success.Created),
+        )
+        val testSubject = createTestSubject(
+            accountManager = accountManager,
+            remoteFolderCreatorFactory = remoteFolderCreatorFactory,
+            backendStorageFactory = backendStorageFactory,
+            specialFolderUpdaterFactory = specialFolderUpdaterFactory,
+        )
+        val folderName = "TheFolder"
+
+        // Act
+        testSubject(accountUuid, folderName).test {
+            // Assert
+            skipItems(count = 2) // Skip LocalFolderCreated and SyncStarted event.
+            var outcome = awaitItem()
+            assertThat(outcome)
+                .isInstanceOf<Outcome.Success<CreateArchiveFolderOutcome.Success>>()
+                .prop("data") { it.data }
+                .isEqualTo(CreateArchiveFolderOutcome.Success.UpdatingSpecialFolders)
+
+            outcome = awaitItem()
+            assertThat(outcome)
+                .isInstanceOf<Outcome.Success<CreateArchiveFolderOutcome.Success>>()
+                .prop("data") { it.data }
+                .isEqualTo(CreateArchiveFolderOutcome.Success.Created)
+
+            verify(exactly(1)) { accountManager.getAccount(accountUuid) }
+            verify(exactly(1)) {
+                // verify doesn't support verifying the extension function `createFolder`,
+                // thus we verify the call of `createFolders(list)` instead.
+                backendStorageFactory.backendFolderUpdater.createFolders(
+                    eq(
+                        listOf(
+                            FolderInfo(
+                                serverId = folderName,
+                                name = folderName,
+                                type = LegacyFolderType.ARCHIVE,
+                            ),
+                        ),
+                    ),
+                )
+            }
+
+            verifySuspend(exactly(1)) {
+                remoteFolderCreatorFactory.instance.create(
+                    folderServerId = FolderServerId(folderName),
+                    mustCreate = false,
+                    folderType = LegacyFolderType.ARCHIVE,
+                )
+            }
+
+            verify(exactly(1)) {
+                specialFolderUpdaterFactory.specialFolderUpdater.setSpecialFolder(
+                    type = FolderType.ARCHIVE,
+                    folderId = any(),
+                    selection = SpecialFolderSelection.MANUAL,
+                )
+            }
+
+            verify(exactly(1)) {
+                specialFolderUpdaterFactory.specialFolderUpdater.updateSpecialFolders()
+            }
+
+            verify(exactly(1)) {
+                accountManager.saveAccount(account = any())
+            }
+
+            awaitComplete()
+        }
+    }
+
+    @OptIn(ExperimentalCoroutinesApi::class)
+    private fun createTestSubject(
+        accounts: List<BaseAccount> = emptyList(),
+        accountManager: FakeAccountManager = FakeAccountManager(accounts),
+        backendStorageFactory: FakeBackendStorageFactory = FakeBackendStorageFactory(),
+        remoteFolderCreatorOutcome: Outcome<
+            RemoteFolderCreationOutcome.Success,
+            RemoteFolderCreationOutcome.Error,
+            >? = null,
+        remoteFolderCreatorFactory: FakeRemoteFolderCreatorFactory = FakeRemoteFolderCreatorFactory(
+            outcome = remoteFolderCreatorOutcome,
+        ),
+        specialFolderUpdaterFactory: FakeSpecialFolderUpdaterFactory = FakeSpecialFolderUpdaterFactory(),
+    ): CreateArchiveFolder =
+        CreateArchiveFolder(
+            accountManager = accountManager,
+            backendStorageFactory = backendStorageFactory,
+            remoteFolderCreatorFactory = remoteFolderCreatorFactory,
+            specialFolderUpdaterFactory = specialFolderUpdaterFactory,
+            ioDispatcher = UnconfinedTestDispatcher(),
+        )
+
+    private fun createAccountList(
+        accountUuid: String = Uuid.random().toHexString(),
+        size: Int = 10,
+    ) = List(size = size) {
+        FakeAccount(uuid = if (it == 0) accountUuid else Uuid.random().toHexString())
+    }
+}
+
+private open class FakeRemoteFolderCreatorFactory(
+    protected open val outcome: Outcome<RemoteFolderCreationOutcome.Success, RemoteFolderCreationOutcome.Error>?,
+) : RemoteFolderCreator.Factory {
+    open var instance: RemoteFolderCreator = spy<RemoteFolderCreator>(FakeRemoteFolderCreator())
+        protected set
+
+    override fun create(account: BaseAccount): RemoteFolderCreator = instance
+
+    private open inner class FakeRemoteFolderCreator : RemoteFolderCreator {
+        override suspend fun create(
+            folderServerId: FolderServerId,
+            mustCreate: Boolean,
+            folderType: LegacyFolderType,
+        ): Outcome<RemoteFolderCreationOutcome.Success, RemoteFolderCreationOutcome.Error> =
+            outcome ?: error("Not expected to be called in this context.")
+    }
+}
diff --git a/feature/mail/message/list/src/test/kotlin/net/thunderbird/feature/mail/message/list/domain/usecase/GetAccountFoldersTest.kt b/feature/mail/message/list/src/test/kotlin/net/thunderbird/feature/mail/message/list/domain/usecase/GetAccountFoldersTest.kt
new file mode 100644
index 00000000000..0ab9c7e1ad7
--- /dev/null
+++ b/feature/mail/message/list/src/test/kotlin/net/thunderbird/feature/mail/message/list/domain/usecase/GetAccountFoldersTest.kt
@@ -0,0 +1,274 @@
+package net.thunderbird.feature.mail.message.list.domain.usecase
+
+import app.k9mail.legacy.mailstore.FolderRepository
+import assertk.all
+import assertk.assertFailure
+import assertk.assertThat
+import assertk.assertions.containsOnly
+import assertk.assertions.hasMessage
+import assertk.assertions.hasSize
+import assertk.assertions.isEmpty
+import assertk.assertions.isInstanceOf
+import assertk.assertions.prop
+import com.fsck.k9.mail.MessagingException
+import kotlin.random.Random
+import kotlin.test.Test
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import net.thunderbird.core.outcome.Outcome
+import net.thunderbird.feature.mail.folder.api.FolderType
+import net.thunderbird.feature.mail.folder.api.RemoteFolder
+import net.thunderbird.feature.mail.message.list.domain.AccountFolderError
+import org.mockito.Mockito.`when`
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+
+private const val VALID_ACCOUNT_UUID = "valid_account_uuid"
+private const val INVALID_ACCOUNT_UUID = "invalid_account_uuid"
+
+@Suppress("MaxLineLength")
+class GetAccountFoldersTest {
+
+    @Test
+    fun `invoke should return REGULAR and ARCHIVE folders when repository returns a list of folders`() = runTest {
+        // Arrange
+        val accountUuid = VALID_ACCOUNT_UUID
+        val regularFoldersSize = 10
+        val remoteFolders = createRemoteFolders(
+            regularFoldersSize = regularFoldersSize,
+            addInboxFolder = true,
+            addOutboxFolder = true,
+            addSentFolder = true,
+            addTrashFolder = true,
+            addArchiveFolder = true,
+            addSpamFolder = true,
+        )
+        val testSubject = createTestSubject(accountUuid, remoteFolders)
+
+        // Act
+        val folders = testSubject(accountUuid)
+
+        // Assert
+        assertThat(folders)
+            .isInstanceOf<Outcome.Success<List<RemoteFolder>>>()
+            .prop(name = "data") { it.data }
+            .all {
+                hasSize(regularFoldersSize + 1) // +1 counting Archive folder.
+                transform { remoteFolders -> remoteFolders.map { it.type } }
+                    .containsOnly(FolderType.REGULAR, FolderType.ARCHIVE)
+            }
+    }
+
+    @Test
+    fun `invoke should return only REGULAR folders when repository returns only REGULAR folders`() = runTest {
+        // Arrange
+        val accountUuid = VALID_ACCOUNT_UUID
+        val regularFoldersSize = Random.nextInt(from = 1, until = 100)
+        val remoteFolders = createRemoteFolders(
+            regularFoldersSize = regularFoldersSize,
+        )
+        val testSubject = createTestSubject(accountUuid, remoteFolders)
+
+        // Act
+        val folders = testSubject(accountUuid)
+
+        // Assert
+        assertThat(folders)
+            .isInstanceOf<Outcome.Success<List<RemoteFolder>>>()
+            .prop(name = "data") { it.data }
+            .all {
+                hasSize(regularFoldersSize)
+                transform { remoteFolders -> remoteFolders.map { it.type } }
+                    .containsOnly(FolderType.REGULAR)
+            }
+    }
+
+    @Test
+    fun `invoke should return only ARCHIVE folder when repository returns only ARCHIVE folder`() = runTest {
+        // Arrange
+        val accountUuid = VALID_ACCOUNT_UUID
+        val remoteFolders = createRemoteFolders(
+            regularFoldersSize = 0,
+            addArchiveFolder = true,
+        )
+        val testSubject = createTestSubject(accountUuid, remoteFolders)
+
+        // Act
+        val folders = testSubject(accountUuid)
+
+        // Assert
+        assertThat(folders)
+            .isInstanceOf<Outcome.Success<List<RemoteFolder>>>()
+            .prop(name = "data") { it.data }
+            .all {
+                hasSize(1)
+                transform { remoteFolders -> remoteFolders.map { it.type } }
+                    .containsOnly(FolderType.ARCHIVE)
+            }
+    }
+
+    @Test
+    fun `invoke should return an empty list when repository returns no REGULAR or ARCHIVE folders`() = runTest {
+        // Arrange
+        val accountUuid = VALID_ACCOUNT_UUID
+        val remoteFolders = createRemoteFolders(
+            regularFoldersSize = 0,
+            addInboxFolder = true,
+            addOutboxFolder = true,
+            addSentFolder = true,
+            addTrashFolder = true,
+            addArchiveFolder = false,
+            addSpamFolder = true,
+        )
+        val testSubject = createTestSubject(accountUuid, remoteFolders)
+
+        // Act
+        val folders = testSubject(accountUuid)
+
+        // Assert
+        assertThat(folders)
+            .isInstanceOf<Outcome.Success<List<RemoteFolder>>>()
+            .prop(name = "data") { it.data }
+            .isEmpty()
+    }
+
+    @Test
+    fun `invoke should return failure when repository throws MessagingException`() = runTest {
+        // Arrange
+        val accountUuid = VALID_ACCOUNT_UUID
+        val errorMessage = "this is an error"
+        val messagingException = MessagingException(errorMessage)
+        val remoteFolders = listOf<RemoteFolder>()
+        val testSubject = createTestSubject(
+            accountUuid = accountUuid,
+            folders = remoteFolders,
+            exception = messagingException,
+        )
+
+        // Act
+        val folders = testSubject(accountUuid)
+
+        // Assert
+        assertThat(folders)
+            .isInstanceOf<Outcome.Failure<AccountFolderError>>()
+            .prop("error") { it.error }
+            .prop(AccountFolderError::exception)
+            .isInstanceOf<MessagingException>()
+            .hasMessage(errorMessage)
+    }
+
+    @Test
+    fun `invoke should propagate exception when repository throws other types of exceptions`() = runTest {
+        // Arrange
+        val accountUuid = VALID_ACCOUNT_UUID
+        val errorMessage = "not handled exception"
+        val messagingException = RuntimeException(errorMessage)
+        val remoteFolders = listOf<RemoteFolder>()
+        val testSubject = createTestSubject(
+            accountUuid = accountUuid,
+            folders = remoteFolders,
+            exception = messagingException,
+        )
+
+        // Act & Assert
+        assertFailure { testSubject(accountUuid) }
+            .isInstanceOf<RuntimeException>()
+            .hasMessage(errorMessage)
+    }
+
+    @Test
+    fun `invoke should handle invalid or non-existent account UUID`() = runTest {
+        // Arrange
+        val accountUuid = INVALID_ACCOUNT_UUID
+        val remoteFolders = createRemoteFolders(
+            regularFoldersSize = 100,
+            addInboxFolder = true,
+            addOutboxFolder = true,
+            addSentFolder = true,
+            addTrashFolder = true,
+            addArchiveFolder = true,
+            addSpamFolder = true,
+        )
+        val testSubject = createTestSubject(accountUuid, remoteFolders)
+
+        // Act
+        val folders = testSubject(accountUuid)
+
+        // Assert
+        assertThat(folders)
+            .isInstanceOf<Outcome.Success<List<RemoteFolder>>>()
+            .prop(name = "data") { it.data }
+            .isEmpty()
+    }
+
+    private fun createRemoteFolders(
+        regularFoldersSize: Int,
+        addInboxFolder: Boolean = false,
+        addOutboxFolder: Boolean = false,
+        addSentFolder: Boolean = false,
+        addTrashFolder: Boolean = false,
+        addArchiveFolder: Boolean = false,
+        addSpamFolder: Boolean = false,
+    ): List<RemoteFolder> {
+        fun createRemoteFolder(id: Long, type: FolderType) = RemoteFolder(
+            id = id,
+            name = "${type.name}-$id",
+            serverId = "${type.name}-$id",
+            type = type,
+        )
+        return buildList {
+            var id = 1L
+            if (addInboxFolder) {
+                add(createRemoteFolder(id = id++, type = FolderType.INBOX))
+            }
+            if (addOutboxFolder) {
+                add(createRemoteFolder(id = id++, type = FolderType.OUTBOX))
+            }
+            if (addSentFolder) {
+                add(createRemoteFolder(id = id++, type = FolderType.SENT))
+            }
+            if (addTrashFolder) {
+                add(createRemoteFolder(id = id++, type = FolderType.TRASH))
+            }
+            if (addArchiveFolder) {
+                add(createRemoteFolder(id = id++, type = FolderType.ARCHIVE))
+            }
+            if (addSpamFolder) {
+                add(createRemoteFolder(id = id++, type = FolderType.SPAM))
+            }
+            if (regularFoldersSize > 0) {
+                addAll(
+                    elements = List(size = regularFoldersSize) { index ->
+                        createRemoteFolder(id = id + index, type = FolderType.REGULAR)
+                    },
+                )
+            }
+        }
+    }
+
+    @OptIn(ExperimentalCoroutinesApi::class)
+    private fun createTestSubject(
+        accountUuid: String,
+        folders: List<RemoteFolder>,
+        exception: Exception? = null,
+    ): GetAccountFolders {
+        val folderRepository = mock<FolderRepository>()
+        when {
+            exception != null -> {
+                `when`(folderRepository.getRemoteFolders(eq(accountUuid)))
+                    .thenThrow(exception)
+            }
+
+            accountUuid == VALID_ACCOUNT_UUID -> {
+                `when`(folderRepository.getRemoteFolders(eq(accountUuid)))
+                    .thenReturn(folders)
+            }
+
+            accountUuid == INVALID_ACCOUNT_UUID ->
+                `when`(folderRepository.getRemoteFolders(eq(accountUuid)))
+                    .thenReturn(emptyList())
+        }
+        return GetAccountFolders(folderRepository, ioDispatcher = UnconfinedTestDispatcher())
+    }
+}
diff --git a/feature/mail/message/list/src/test/kotlin/net/thunderbird/feature/mail/message/list/domain/usecase/SetArchiveFolderTest.kt b/feature/mail/message/list/src/test/kotlin/net/thunderbird/feature/mail/message/list/domain/usecase/SetArchiveFolderTest.kt
new file mode 100644
index 00000000000..02a31e4598e
--- /dev/null
+++ b/feature/mail/message/list/src/test/kotlin/net/thunderbird/feature/mail/message/list/domain/usecase/SetArchiveFolderTest.kt
@@ -0,0 +1,186 @@
+package net.thunderbird.feature.mail.message.list.domain.usecase
+
+import assertk.assertThat
+import assertk.assertions.hasMessage
+import assertk.assertions.isEqualTo
+import assertk.assertions.isInstanceOf
+import assertk.assertions.prop
+import com.fsck.k9.mail.MessagingException
+import dev.mokkery.matcher.any
+import dev.mokkery.matcher.matching
+import dev.mokkery.spy
+import dev.mokkery.verify
+import dev.mokkery.verify.VerifyMode.Companion.exactly
+import kotlin.random.Random
+import kotlin.uuid.ExperimentalUuidApi
+import kotlin.uuid.Uuid
+import kotlinx.coroutines.test.runTest
+import net.thunderbird.core.outcome.Outcome
+import net.thunderbird.feature.mail.account.api.BaseAccount
+import net.thunderbird.feature.mail.folder.api.FolderType
+import net.thunderbird.feature.mail.folder.api.RemoteFolder
+import net.thunderbird.feature.mail.folder.api.SpecialFolderSelection
+import net.thunderbird.feature.mail.message.list.domain.SetAccountFolderOutcome
+import net.thunderbird.feature.mail.message.list.fakes.FakeAccount
+import net.thunderbird.feature.mail.message.list.fakes.FakeAccountManager
+import net.thunderbird.feature.mail.message.list.fakes.FakeBackendFolderUpdater
+import net.thunderbird.feature.mail.message.list.fakes.FakeBackendStorageFactory
+import net.thunderbird.feature.mail.message.list.fakes.FakeSpecialFolderUpdaterFactory
+import org.junit.Test
+import com.fsck.k9.mail.FolderType as LegacyFolderType
+
+@OptIn(ExperimentalUuidApi::class)
+@Suppress("MaxLineLength")
+class SetArchiveFolderTest {
+    @Test
+    fun `invoke should successfully create folder and update account when given valid input`() = runTest {
+        // Arrange
+        val accountUuid = Uuid.random().toHexString()
+        val accounts = listOf(FakeAccount(uuid = accountUuid))
+
+        val fakeBackendStorageFactory = FakeBackendStorageFactory()
+        val fakeAccountManager = spy(FakeAccountManager(accounts))
+        val fakeSpecialFolderUpdaterFactory = FakeSpecialFolderUpdaterFactory()
+        val testSubject =
+            createTestSubject(fakeAccountManager, fakeBackendStorageFactory, fakeSpecialFolderUpdaterFactory)
+        val folder = createRemoteFolder()
+
+        // Act
+        val outcome = testSubject(accountUuid, folder)
+
+        // Assert
+        assertThat(outcome)
+            .isInstanceOf<Outcome.Success<SetAccountFolderOutcome.Success>>()
+            .prop(name = "data") { it.data }
+            .isEqualTo(SetAccountFolderOutcome.Success)
+
+        verify(exactly(1)) {
+            fakeBackendStorageFactory.backendFolderUpdater.changeFolder(
+                folderServerId = folder.serverId,
+                name = folder.name,
+                type = LegacyFolderType.ARCHIVE,
+            )
+        }
+        verify(exactly(1)) { fakeBackendStorageFactory.backendFolderUpdater.close() }
+        verify(exactly(1)) {
+            fakeSpecialFolderUpdaterFactory.specialFolderUpdater.setSpecialFolder(
+                type = FolderType.ARCHIVE,
+                folderId = folder.id,
+                selection = SpecialFolderSelection.MANUAL,
+            )
+        }
+        verify(exactly(1)) {
+            fakeSpecialFolderUpdaterFactory.specialFolderUpdater.updateSpecialFolders()
+        }
+        verify(exactly(1)) {
+            fakeAccountManager.saveAccount(
+                account = matching {
+                    it.uuid == accountUuid
+                },
+            )
+        }
+    }
+
+    @Test
+    fun `invoke should return AccountNotFound when account is not found`() = runTest {
+        // Arrange
+        val accounts = listOf<FakeAccount>()
+        val testSubject = createTestSubject(accounts)
+        val accountUuid = Uuid.random().toHexString()
+        val folder = createRemoteFolder()
+
+        // Act
+        val outcome = testSubject(accountUuid, folder)
+
+        // Assert
+        assertThat(outcome)
+            .isInstanceOf<Outcome.Failure<SetAccountFolderOutcome.Error>>()
+            .prop(name = "error") { it.error }
+            .isEqualTo(SetAccountFolderOutcome.Error.AccountNotFound)
+    }
+
+    @Test
+    fun `invoke should return UnhandledError when changeFolder throws MessagingException`() = runTest {
+        // Arrange
+        val accountUuid = Uuid.random().toHexString()
+        val accounts = listOf(FakeAccount(uuid = accountUuid))
+
+        val exception = MessagingException("this is an error")
+        val fakeBackendStorageFactory = FakeBackendStorageFactory(
+            backendFolderUpdater = FakeBackendFolderUpdater(exception = exception),
+        )
+        val fakeAccountManager = spy(FakeAccountManager(accounts))
+        val fakeSpecialFolderUpdaterFactory = FakeSpecialFolderUpdaterFactory()
+        val testSubject =
+            createTestSubject(fakeAccountManager, fakeBackendStorageFactory, fakeSpecialFolderUpdaterFactory)
+        val folder = createRemoteFolder()
+
+        // Act
+        val outcome = testSubject(accountUuid, folder)
+
+        // Assert
+        assertThat(outcome)
+            .isInstanceOf<Outcome.Failure<SetAccountFolderOutcome.Error>>()
+            .prop(name = "error") { it.error }
+            .isInstanceOf<SetAccountFolderOutcome.Error.UnhandledError>()
+            .prop("throwable") { it.throwable }
+            .hasMessage(exception.message)
+
+        verify(exactly(1)) {
+            fakeBackendStorageFactory.backendFolderUpdater.changeFolder(
+                folderServerId = folder.serverId,
+                name = folder.name,
+                type = LegacyFolderType.ARCHIVE,
+            )
+        }
+
+        verify(exactly(1)) { fakeBackendStorageFactory.backendFolderUpdater.close() }
+
+        verify(exactly(0)) {
+            fakeSpecialFolderUpdaterFactory.specialFolderUpdater.setSpecialFolder(
+                type = any(),
+                folderId = any(),
+                selection = any(),
+            )
+        }
+        verify(exactly(0)) {
+            fakeSpecialFolderUpdaterFactory.specialFolderUpdater.updateSpecialFolders()
+        }
+        verify(exactly(0)) {
+            fakeAccountManager.saveAccount(account = any())
+        }
+    }
+
+    private fun createTestSubject(
+        accounts: List<BaseAccount>,
+        backendStorageFactory: FakeBackendStorageFactory = FakeBackendStorageFactory(),
+        specialFolderUpdaterFactory: FakeSpecialFolderUpdaterFactory = FakeSpecialFolderUpdaterFactory(),
+    ): SetArchiveFolder = createTestSubject(
+        accountManager = FakeAccountManager(accounts),
+        backendStorageFactory = backendStorageFactory,
+        specialFolderUpdaterFactory = specialFolderUpdaterFactory,
+    )
+
+    private fun createTestSubject(
+        accountManager: FakeAccountManager,
+        backendStorageFactory: FakeBackendStorageFactory = FakeBackendStorageFactory(),
+        specialFolderUpdaterFactory: FakeSpecialFolderUpdaterFactory = FakeSpecialFolderUpdaterFactory(),
+    ): SetArchiveFolder {
+        return SetArchiveFolder(
+            accountManager = accountManager,
+            backendStorageFactory = backendStorageFactory,
+            specialFolderUpdaterFactory = specialFolderUpdaterFactory,
+        )
+    }
+
+    private fun createRemoteFolder(
+        id: Long = Random.nextLong(),
+        serverId: String = "remote_folder_$id",
+        name: String = serverId,
+    ): RemoteFolder = RemoteFolder(
+        id = id,
+        serverId = serverId,
+        name = name,
+        type = FolderType.ARCHIVE,
+    )
+}
diff --git a/feature/mail/message/list/src/test/kotlin/net/thunderbird/feature/mail/message/list/fakes/FakeAccount.kt b/feature/mail/message/list/src/test/kotlin/net/thunderbird/feature/mail/message/list/fakes/FakeAccount.kt
new file mode 100644
index 00000000000..e9f87fde9f3
--- /dev/null
+++ b/feature/mail/message/list/src/test/kotlin/net/thunderbird/feature/mail/message/list/fakes/FakeAccount.kt
@@ -0,0 +1,11 @@
+package net.thunderbird.feature.mail.message.list.fakes
+
+import net.thunderbird.feature.mail.account.api.BaseAccount
+
+internal data class FakeAccount(
+    override val uuid: String,
+    override val email: String = "fake@mail.com",
+) : BaseAccount {
+    override val name: String?
+        get() = email
+}
diff --git a/feature/mail/message/list/src/test/kotlin/net/thunderbird/feature/mail/message/list/fakes/FakeAccountManager.kt b/feature/mail/message/list/src/test/kotlin/net/thunderbird/feature/mail/message/list/fakes/FakeAccountManager.kt
new file mode 100644
index 00000000000..c1442bffa0e
--- /dev/null
+++ b/feature/mail/message/list/src/test/kotlin/net/thunderbird/feature/mail/message/list/fakes/FakeAccountManager.kt
@@ -0,0 +1,25 @@
+package net.thunderbird.feature.mail.message.list.fakes
+
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
+import net.thunderbird.feature.mail.account.api.AccountManager
+import net.thunderbird.feature.mail.account.api.BaseAccount
+
+internal open class FakeAccountManager(
+    private val accounts: List<BaseAccount>,
+) : AccountManager<BaseAccount> {
+    override fun getAccounts(): List<BaseAccount> = accounts
+
+    override fun getAccountsFlow(): Flow<List<BaseAccount>> = flowOf(accounts)
+
+    override fun getAccount(accountUuid: String): BaseAccount? = accounts.firstOrNull { it.uuid == accountUuid }
+
+    override fun getAccountFlow(accountUuid: String): Flow<BaseAccount?> = flowOf(getAccount(accountUuid))
+
+    override fun moveAccount(
+        account: BaseAccount,
+        newPosition: Int,
+    ) = error("not implemented.")
+
+    override fun saveAccount(account: BaseAccount) = Unit
+}
diff --git a/feature/mail/message/list/src/test/kotlin/net/thunderbird/feature/mail/message/list/fakes/FakeBackendFolderUpdater.kt b/feature/mail/message/list/src/test/kotlin/net/thunderbird/feature/mail/message/list/fakes/FakeBackendFolderUpdater.kt
new file mode 100644
index 00000000000..f1413dfa1ff
--- /dev/null
+++ b/feature/mail/message/list/src/test/kotlin/net/thunderbird/feature/mail/message/list/fakes/FakeBackendFolderUpdater.kt
@@ -0,0 +1,34 @@
+package net.thunderbird.feature.mail.message.list.fakes
+
+import com.fsck.k9.backend.api.BackendFolderUpdater
+import com.fsck.k9.backend.api.FolderInfo
+import com.fsck.k9.mail.FolderType
+
+internal open class FakeBackendFolderUpdater(
+    private val exception: Exception? = null,
+    private val returnEmptySetWhenCreatingFolders: Boolean = false,
+) : BackendFolderUpdater {
+    private val ids = mutableSetOf<Long>()
+    override fun createFolders(folders: List<FolderInfo>): Set<Long> {
+        return when {
+            exception != null -> throw exception
+            returnEmptySetWhenCreatingFolders -> emptySet()
+            else -> ids.apply {
+                var last = ids.lastOrNull() ?: 0
+                addAll(folders.map { ++last })
+            }
+        }
+    }
+
+    override fun deleteFolders(folderServerIds: List<String>) {
+        if (exception != null) throw exception
+    }
+
+    override fun changeFolder(folderServerId: String, name: String, type: FolderType) {
+        if (exception != null) throw exception
+    }
+
+    override fun close() {
+        if (exception != null) throw exception
+    }
+}
diff --git a/feature/mail/message/list/src/test/kotlin/net/thunderbird/feature/mail/message/list/fakes/FakeBackendStorageFactory.kt b/feature/mail/message/list/src/test/kotlin/net/thunderbird/feature/mail/message/list/fakes/FakeBackendStorageFactory.kt
new file mode 100644
index 00000000000..20785cd57e2
--- /dev/null
+++ b/feature/mail/message/list/src/test/kotlin/net/thunderbird/feature/mail/message/list/fakes/FakeBackendStorageFactory.kt
@@ -0,0 +1,30 @@
+package net.thunderbird.feature.mail.message.list.fakes
+
+import com.fsck.k9.backend.api.BackendFolder
+import com.fsck.k9.backend.api.BackendFolderUpdater
+import com.fsck.k9.backend.api.BackendStorage
+import dev.mokkery.spy
+import net.thunderbird.backend.api.BackendStorageFactory
+import net.thunderbird.feature.mail.account.api.BaseAccount
+
+internal open class FakeBackendStorageFactory(
+    backendFolderUpdater: FakeBackendFolderUpdater = FakeBackendFolderUpdater(),
+) : BackendStorageFactory<BaseAccount> {
+    val backendFolderUpdater = spy(backendFolderUpdater)
+
+    override fun createBackendStorage(account: BaseAccount): BackendStorage = object : BackendStorage {
+        override fun getFolder(folderServerId: String): BackendFolder = error("not implemented.")
+
+        override fun getFolderServerIds(): List<String> = error("not implemented.")
+
+        override fun createFolderUpdater(): BackendFolderUpdater = backendFolderUpdater
+
+        override fun getExtraString(name: String): String? = error("not implemented.")
+
+        override fun setExtraString(name: String, value: String) = error("not implemented.")
+
+        override fun getExtraNumber(name: String): Long? = error("not implemented.")
+
+        override fun setExtraNumber(name: String, value: Long) = error("not implemented.")
+    }
+}
diff --git a/feature/mail/message/list/src/test/kotlin/net/thunderbird/feature/mail/message/list/fakes/FakeSpecialFolderUpdaterFactory.kt b/feature/mail/message/list/src/test/kotlin/net/thunderbird/feature/mail/message/list/fakes/FakeSpecialFolderUpdaterFactory.kt
new file mode 100644
index 00000000000..c0d22ef0f3f
--- /dev/null
+++ b/feature/mail/message/list/src/test/kotlin/net/thunderbird/feature/mail/message/list/fakes/FakeSpecialFolderUpdaterFactory.kt
@@ -0,0 +1,23 @@
+package net.thunderbird.feature.mail.message.list.fakes
+
+import dev.mokkery.spy
+import net.thunderbird.feature.mail.account.api.BaseAccount
+import net.thunderbird.feature.mail.folder.api.FolderType
+import net.thunderbird.feature.mail.folder.api.SpecialFolderSelection
+import net.thunderbird.feature.mail.folder.api.SpecialFolderUpdater
+
+internal class FakeSpecialFolderUpdaterFactory : SpecialFolderUpdater.Factory<BaseAccount> {
+    val specialFolderUpdater = spy<SpecialFolderUpdater>(FakeSpecialFolderUpdater())
+
+    override fun create(account: BaseAccount): SpecialFolderUpdater = specialFolderUpdater
+}
+
+private open class FakeSpecialFolderUpdater : SpecialFolderUpdater {
+    override fun updateSpecialFolders() = Unit
+
+    override fun setSpecialFolder(
+        type: FolderType,
+        folderId: Long?,
+        selection: SpecialFolderSelection,
+    ) = Unit
+}
diff --git a/legacy/core/src/test/java/com/fsck/k9/helper/MessageHelperTest.kt b/legacy/core/src/test/java/com/fsck/k9/helper/MessageHelperTest.kt
index 7ccf7631e77..9361d006be3 100644
--- a/legacy/core/src/test/java/com/fsck/k9/helper/MessageHelperTest.kt
+++ b/legacy/core/src/test/java/com/fsck/k9/helper/MessageHelperTest.kt
@@ -49,6 +49,7 @@ class MessageHelperTest : RobolectricTest() {
                 isShowStarredCount = false,
                 isShowMessageListStars = false,
                 isShowAnimations = false,
+                shouldShowSetupArchiveFolderDialog = false,
             ),
         )
     }
diff --git a/legacy/core/src/test/java/com/fsck/k9/notification/NotificationContentCreatorTest.kt b/legacy/core/src/test/java/com/fsck/k9/notification/NotificationContentCreatorTest.kt
index 612646a71c0..74bb0d1ecf8 100644
--- a/legacy/core/src/test/java/com/fsck/k9/notification/NotificationContentCreatorTest.kt
+++ b/legacy/core/src/test/java/com/fsck/k9/notification/NotificationContentCreatorTest.kt
@@ -159,6 +159,7 @@ class NotificationContentCreatorTest : RobolectricTest() {
                     isShowUnifiedInbox = false,
                     isShowMessageListStars = false,
                     isShowAnimations = false,
+                    shouldShowSetupArchiveFolderDialog = false,
                 )
             },
         )
diff --git a/mail/protocols/imap/src/test/java/com/fsck/k9/mail/store/imap/RealImapFolderTest.kt b/mail/protocols/imap/src/test/java/com/fsck/k9/mail/store/imap/RealImapFolderTest.kt
index 1b5e8c24157..6588cc462dd 100644
--- a/mail/protocols/imap/src/test/java/com/fsck/k9/mail/store/imap/RealImapFolderTest.kt
+++ b/mail/protocols/imap/src/test/java/com/fsck/k9/mail/store/imap/RealImapFolderTest.kt
@@ -19,6 +19,7 @@ import com.fsck.k9.mail.Body
 import com.fsck.k9.mail.DefaultBodyFactory
 import com.fsck.k9.mail.FetchProfile
 import com.fsck.k9.mail.Flag
+import com.fsck.k9.mail.FolderType
 import com.fsck.k9.mail.MessageRetrievalListener
 import com.fsck.k9.mail.MessagingException
 import com.fsck.k9.mail.Part
@@ -31,6 +32,7 @@ import java.io.IOException
 import java.nio.file.Files
 import java.util.Date
 import java.util.TimeZone
+import net.thunderbird.protocols.imap.folder.attributeName
 import okio.Buffer
 import org.apache.james.mime4j.util.MimeUtil
 import org.junit.After
@@ -53,6 +55,7 @@ import org.mockito.kotlin.never
 import org.mockito.kotlin.stub
 import org.mockito.kotlin.whenever
 
+@Suppress("MaxLineLength")
 class RealImapFolderTest {
     private val imapStoreConfig = FakeImapStoreConfig()
     private val internalImapStore = object : InternalImapStore {
@@ -1519,6 +1522,31 @@ class RealImapFolderTest {
         assertThat(message.uid).isEqualTo("uid")
     }
 
+    @Test
+    fun `create() when folderType not in (REGULAR INBOX OUTBOX) and connection has CREATE_SPECIAL_USE capability, should call CREATE command with USE command and return true`() {
+        // Arrange
+        val types = FolderType.entries.filterNot { folderType ->
+            folderType == FolderType.REGULAR ||
+                folderType == FolderType.INBOX ||
+                folderType == FolderType.OUTBOX
+        }
+        imapConnection.stub {
+            on { hasCapability(Capabilities.CREATE_SPECIAL_USE) } doReturn true
+        }
+        val folderName = "New Folder"
+        val remoteFolder = createFolder(folderName)
+
+        for (folderType in types) {
+            // Act
+            remoteFolder.create(folderType = folderType)
+        }
+
+        // Assert
+        for (folderType in types) {
+            assertCommandIssued("CREATE \"$folderName\" (USE (${folderType.attributeName}))")
+        }
+    }
+
     @Suppress("SameParameterValue")
     private fun createPlainTextPart(serverExtra: String): Part {
         val part = createPart(serverExtra)
diff --git a/mail/protocols/imap/src/test/java/com/fsck/k9/mail/store/imap/TestImapFolder.kt b/mail/protocols/imap/src/test/java/com/fsck/k9/mail/store/imap/TestImapFolder.kt
index b62bed4366d..8e11367e680 100644
--- a/mail/protocols/imap/src/test/java/com/fsck/k9/mail/store/imap/TestImapFolder.kt
+++ b/mail/protocols/imap/src/test/java/com/fsck/k9/mail/store/imap/TestImapFolder.kt
@@ -3,6 +3,7 @@ package com.fsck.k9.mail.store.imap
 import com.fsck.k9.mail.BodyFactory
 import com.fsck.k9.mail.FetchProfile
 import com.fsck.k9.mail.Flag
+import com.fsck.k9.mail.FolderType
 import com.fsck.k9.mail.Message
 import com.fsck.k9.mail.MessageRetrievalListener
 import com.fsck.k9.mail.Part
@@ -128,7 +129,7 @@ internal open class TestImapFolder(
         throw UnsupportedOperationException("not implemented")
     }
 
-    override fun create(): Boolean {
+    override fun create(folderType: FolderType): Boolean {
         throw UnsupportedOperationException("not implemented")
     }