fix: correctly handles the server ID when an IMAP prefix is present, ensuring the prefix is not duplicated

Fixes #9567.
Fixes #9771.

## Testing
### Pre-requisites
To test the following changes, you need to have:
- An email from an email provider which DOES enforce IMAP prefixes (returned via `NAMESPACE` command; Email providers that use the dovecot IMAP Servers are good ones to use)
- An email from an email provider which DOES NOT enforce IMAP prefixes (Google, Outlook, Yahoo, etc)

### Testing #9567
#### Steps to Reproduce
1. Delete the app if you have it installed
2. `git checkout 70b3dc3b41650a13a7ed9a1933377513afe8a3da`
3. Sync, build and install the app
4. Set up the account that uses the IMAP prefix
5. Close the app
6. Checks out this branch
7. Sync, build and install the app
8. Open the app, try to synchronize any folder that is not the INBOX folder.

#### Expected
The app should be able to download the messages.

### Testing #9771
#### Steps to Reproduce
1. Delete the app if you have it installed
2. `git checkout 70b3dc3b41650a13a7ed9a1933377513afe8a3da`
3. Sync, build and install the app
4. Set up the account that uses the IMAP prefix
5. Set up the special folders, setting any folder you want
6. Close the app
6. Checks out this branch
7. Sync, build and install the app
8. Open the Account Drawer
9. Tap on "Manage folders"
10. Tap on the Overflow button (Three dots, top right)
11. Tap on "Refresh folder list"

#### Expected
The folders must keep their assignment (Drafts, Archive, etc), unless there was a change on the server side.

Special folders gets wiped out when updating from 11.x to 12.x
### 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

Thunderbird for Android

### App version

12.1

### Where did you get the app from?

Google Play

### Android version

16

### Device model

Pixel 8

### Steps to reproduce

Upgrade Thunderbird Mobile to 12.1 with Play store. After that open app and check folders:
- The "Unified inbox" only contains messages from Imap's INBOX folder, and no other messages from folders which were added to be shown in unified inbox.
- If you select a folder and refresh it, no messages appear
- In the account's settings under "folders", all "standard" folders (Trash, Sent, Drafts, Archives,...)  disappeared and have not selected any IMAP folder on server side. They can be selected again by clicking on them, but this does also not fix the issue
- If you send a mail, the mail does not appear in Sent folder anymore (not even server side).

The important information about Server: Dovecot Mail server with "dotted" folder name. "INBOX" is Root folder, and all folders below are named "INBOX.Sent", "INBOX.Trash",....

They still appear correctly in the UI, also after refresh, but basically all folders are unreachable.

The mail server does not show any messages about non-existant folders, will enable debug logging there.

### Expected behavior

After upgrade the folders which appear in the UI should contain messages.

### Actual behavior

All folders are empty and syncing them does not refresh them.

### Logs

The above "retrieve logs" documentation is not available so I have no idea how to get logs!
Folders on IMAP servers with prefixes are completely broken since 12.0b4
### 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

Thunderbird for Android

### App version

12.0b4, 12.0b5

### Where did you get the app from?

Other

### Android version

13 (xiaomi.eu 14.0.3.0.TJUMIXM)

### Device model

Xiaomi POCO X3 Pro (global)

### Steps to reproduce

Requires an IMAP mail server that uses prefix for non-INBOX mailboxes (e.g. GMail - #9616).

#### Variant 1 - fetching folder

1. Fetch list of folders
2. Open any folder other than _Inbox_ (e.g. _Spam_ - `INBOX.Spam`)
3. Load messages/refresh

#### Variant 2 - deleting messages

1. Make sure that there is a trash folder (e.g. `INBOX.Trash`)
2. Delete a message from inbox

### Expected behavior

#### Variant 1 - fetching folder

Messages from the selected folder are fetched from server (e.g. using command `EXAMINE "INBOX.Spam"`) and displayed.

#### Variant 2 - deleting messages

Deleted message is moved to the trash folder.

### Actual behavior

#### Variant 1 - fetching folder

Command `EXAMINE "INBOX.INBOX.Spam"` is sent and obviously fails, because there is no such folder.

#### Variant 2 - deleting messages

Command similar to `UID MOVE 27707:27708,27714:27715 "INBOX.INBOX.Trash"` (here I was deleting multiple messages) is sent and also fails. This leads to a desync, because messages end up in the local trash, disappearing from inbox, but still exist on server and are shown to other clients.

I was wondering why desktop Thunderbird and webmail showed 15 more unread messages than Thunderbird for Android and thought it is a weird sync problem.

### Regression analysis

This regression is caused by #9241 that was uplifted to beta in #9523 (I guess now some of the associated risk is known) and released in 12.0b4.

`serverId` is _absolute_ - it includes folder name including the prefix. Previously most folder `serverId`s were "old server IDs" and [had the prefix removed](https://github.com/thunderbird/thunderbird-android/commit/74cbed1bdb1375cb15014b16cd75463b48f60071#diff-b88fcb0b0a4fbe9cda33c13ffc19bf81f7bc110a545cf81e24c87532fdef03b5L193-L203). Now folders are initialised with `serverId` that contains prefix, but commands either use `encodedName` which is just [`folderNameCodec.encode(prefixedName)`](https://github.com/thunderbird/thunderbird-android/blob/5a296ceaccbb5c4976cd109119f8a6135c376b40/mail/protocols/imap/src/main/java/com/fsck/k9/mail/store/imap/RealImapFolder.kt#L98-L102) or directly call `folderNameCodec.encode(folder.prefixedName)` ([`copyMessages`](https://github.com/thunderbird/thunderbird-android/blob/5a296ceaccbb5c4976cd109119f8a6135c376b40/mail/protocols/imap/src/main/java/com/fsck/k9/mail/store/imap/RealImapFolder.kt#L309-L310) and [`moveMessagesUsingMoveExtension`](https://github.com/thunderbird/thunderbird-android/blob/5a296ceaccbb5c4976cd109119f8a6135c376b40/mail/protocols/imap/src/main/java/com/fsck/k9/mail/store/imap/RealImapFolder.kt#L345-L346)) - in either case [`prefixedName`](https://github.com/thunderbird/thunderbird-android/blob/5a296ceaccbb5c4976cd109119f8a6135c376b40/mail/protocols/imap/src/main/java/com/fsck/k9/mail/store/imap/RealImapFolder.kt#L73-L96) is used and [`prefixedName`](https://github.com/thunderbird/thunderbird-android/blob/5a296ceaccbb5c4976cd109119f8a6135c376b40/mail/protocols/imap/src/main/java/com/fsck/k9/mail/store/imap/RealImapFolder.kt#L73-L96) prefixes the `serverId` (that already has prefix).

### Logs

Logs showing refreshing folders list (LIST command) and an attempt at fetching spam folder content (EXAMINE)

```log
08-04 23:19:05.601 23247  7026 I MessagingController: Running command 'refreshFolderList', seq = 33 (foreground priority)
08-04 23:19:05.609 23247  7026 V RealImapConnection: conn9763719>>> 5 NOOP
08-04 23:19:05.677 23247  7026 V ImapResponseParser: conn9763719<<<#5# [OK, NOOP completed.]
08-04 23:19:05.690 23247  7026 V RealImapConnection: conn9763719>>> 6 LIST "" "INBOX.*" RETURN (SPECIAL-USE)
08-04 23:19:05.703 23247 23247 D DecorView[]: onWindowFocusChanged hasWindowFocus true
08-04 23:19:05.715 23247 23247 I HandWritingStubImpl: refreshLastKeyboardType: 1
08-04 23:19:05.715 23247 23247 I HandWritingStubImpl: getCurrentKeyboardType: 1
08-04 23:19:05.768 23247  7026 V ImapResponseParser: conn9763719<<<#null# [LIST, [\Drafts], ., INBOX.Drafts]
08-04 23:19:05.785 23247  7026 V ImapResponseParser: conn9763719<<<#null# [LIST, [\Junk], ., INBOX.Spam]
08-04 23:19:05.800 23247  7026 V ImapResponseParser: conn9763719<<<#null# [LIST, [\Sent], ., INBOX.Sent Items]
08-04 23:19:05.814 23247  7026 V ImapResponseParser: conn9763719<<<#null# [LIST, [\Trash], ., INBOX.Trash]
08-04 23:19:05.828 23247  7026 V ImapResponseParser: conn9763719<<<#null# [LIST, [], ., INBOX.Templates]
08-04 23:19:05.837 23247  7026 V ImapResponseParser: conn9763719<<<#6# [OK, List completed.]
08-04 23:19:05.930 23247  7026 I K9StorageEditor: Committing preference changes
08-04 23:19:05.987 23247  7027 D OpenGLRenderer: endAllActiveAnimators on 0xb4000079135b5000 (MenuPopupWindow$MenuDropDownListView) with handle 0xb40000798156a080
08-04 23:19:05.999 23247  7026 I K9StorageEditor: Preferences commit took 68 ms
08-04 23:19:06.058 23247  7030 V PushController: PushController.updatePushers()
08-04 23:19:06.124 23247  7026 I K9StorageEditor: Committing preference changes
08-04 23:19:06.178 23247  7026 I K9StorageEditor: Preferences commit took 57 ms
08-04 23:19:06.232 23247  7026 D MessagingController: Folder list successfully refreshed @ Mon Aug 04 23:19:06 CEST 2025
08-04 23:19:06.241 23247  7030 V PushController: ..Running PushControllers: [0308d0e4-7659-46d8-befe-ec78911c1b7d]
08-04 23:19:06.269 23247  7026 I K9StorageEditor: Committing preference changes
08-04 23:19:06.350 23247  7026 I K9StorageEditor: Preferences commit took 81 ms
08-04 23:19:06.390 23247  7026 I MessagingController:  Command 'refreshFolderList' completed
08-04 23:19:06.411 23247  7030 V PushServiceManager: PushServiceManager.start()
08-04 23:19:06.419 23247  7030 V PushServiceManager: ..PushService already running
08-04 23:19:06.426 23247  7030 V BootCompleteManager: Enable BootCompleteReceiver
08-04 23:19:06.436 23247  7030 V PushController: PushController.updatePushers()
08-04 23:19:06.447 23247  7030 V PushController: ..Running PushControllers: [0308d0e4-7659-46d8-befe-ec78911c1b7d]
08-04 23:19:06.465 23247  7030 V PushServiceManager: PushServiceManager.start()
08-04 23:19:06.468 23247  7030 V PushServiceManager: ..PushService already running
08-04 23:19:06.471 23247  7030 V BootCompleteManager: Enable BootCompleteReceiver
08-04 23:19:06.477 23247  7030 V PushController: PushController.updatePushers()
08-04 23:19:06.483 23247  7030 V PushController: ..Running PushControllers: [0308d0e4-7659-46d8-befe-ec78911c1b7d]
08-04 23:19:06.493 23247  7030 V PushServiceManager: PushServiceManager.start()
08-04 23:19:06.496 23247  7030 V PushServiceManager: ..PushService already running
08-04 23:19:06.500 23247  7030 V BootCompleteManager: Enable BootCompleteReceiver
08-04 23:19:07.987 23247 23247 D MIUIInput: [KeyEvent] ViewRootImpl windowName 'net.thunderbird.android.debug/com.fsck.k9.ui.managefolders.ManageFoldersActivity', KeyEvent { action=ACTION_DOWN, keyCode=KEYCODE_BACK, scanCode=0, metaState=0, flags=0x48, repeatCount=0, eventTime=90282514000000, downTime=90282514000000, deviceId=-1, source=0x101, displayId=0 }, phoneEventTime=23:19:07.891
08-04 23:19:08.008 23247 23247 D MIUIInput: [KeyEvent] ViewRootImpl windowName 'net.thunderbird.android.debug/com.fsck.k9.ui.managefolders.ManageFoldersActivity', KeyEvent { action=ACTION_UP, keyCode=KEYCODE_BACK, scanCode=0, metaState=0, flags=0x48, repeatCount=0, eventTime=90282571000000, downTime=90282514000000, deviceId=-1, source=0x101, displayId=0 }, phoneEventTime=23:19:07.948
08-04 23:19:08.180 23247  7026 I MessagingController: Running command 'clearNotifications', seq = 34 (foreground priority)
08-04 23:19:08.191 23247  7026 V NotificationController: Removing some notifications for account 0308d0e4-7659-46d8-befe-ec78911c1b7d
08-04 23:19:08.195 23247 23247 D DecorView[]: onWindowFocusChanged hasWindowFocus false
08-04 23:19:08.199 23247  7026 I MessagingController:  Command 'clearNotifications' completed
08-04 23:19:08.279 23247 23247 D VRI[MessageList]: vri.reportNextDraw android.view.ViewRootImpl.performTraversals:2957 android.view.ViewRootImpl.doTraversal:36 android.view.ViewRootImpl$TraversalRunnable.run:7 android.view.Choreographer$CallbackRecord.run:20 android.view.Choreographer$CallbackRecord.run:20
08-04 23:19:08.280 23247 23247 D VRI[MessageList]: vri.Setup new sync id=7 syncSeqId=0
08-04 23:19:08.469 23247 23247 D VRI[MessageList]: vri.reportDrawFinished syncSeqId=0 android.view.ViewRootImpl.lambda$createSyncIfNeeded$4$android-view-ViewRootImpl:5 android.view.ViewRootImpl$$ExternalSyntheticLambda1.run:6 android.os.Handler.handleCallback:2 android.os.Handler.dispatchMessage:4 android.os.Looper.loopOnce:182
08-04 23:19:08.544 23247 23247 D MessageListViewModel: Received new MessageListInfo: MessageListInfo(messageListItems=[], hasMoreMessages=true)
08-04 23:19:08.546 23247 23247 D DecorView[]: onWindowFocusChanged hasWindowFocus true
08-04 23:19:08.559 23247 23247 I HandWritingStubImpl: refreshLastKeyboardType: 1
08-04 23:19:08.559 23247 23247 I HandWritingStubImpl: getCurrentKeyboardType: 1
08-04 23:19:10.953 23247  7026 I MessagingController: Running command 'loadMoreMessages', seq = 35 (background priority)
08-04 23:19:10.974 23247  7026 D MessagingController: Last folder list refresh @ Mon Aug 04 23:19:06 CEST 2025. Not refreshing now.
08-04 23:19:10.992 23247  7026 I ImapSync: Synchronizing folder konto.pl:INBOX.Spam
08-04 23:19:10.998 23247  7026 V ImapSync: SYNC: About to get local folder INBOX.Spam
08-04 23:19:11.012 23247  7026 V ImapSync: SYNC: About to get remote folder INBOX.Spam
08-04 23:19:11.019 23247  7026 V ImapSync: SYNC: About to open remote folder INBOX.Spam
08-04 23:19:11.028 23247  7026 V RealImapConnection: conn9763719>>> 7 NOOP
08-04 23:19:11.119 23247  7026 V ImapResponseParser: conn9763719<<<#7# [OK, NOOP completed.]
08-04 23:19:11.131 23247  7026 V RealImapConnection: conn9763719>>> 8 EXAMINE "INBOX.INBOX.Spam"
08-04 23:19:11.201 23247  7026 V ImapResponseParser: conn9763719<<<#8# [NO, Mailbox doesn't exist: INBOX.INBOX.Spam]
08-04 23:19:11.221 23247  7026 E RealImapFolder: Unable to open connection for 0308d0e4-7659-46d8-befe-ec78911c1b7d:INBOX.Spam/MessagingController/conn9763719
08-04 23:19:11.221 23247  7026 E RealImapFolder: com.fsck.k9.mail.store.imap.NegativeImapResponseException: Command: EXAMINE "INBOX.INBOX.Spam"; response: #8# [NO, Mailbox doesn't exist: INBOX.INBOX.Spam]
08-04 23:19:11.221 23247  7026 E RealImapFolder:        at com.fsck.k9.mail.store.imap.ImapResponseParser.readStatusResponse(ImapResponseParser.java:127)
08-04 23:19:11.221 23247  7026 E RealImapFolder:        at com.fsck.k9.mail.store.imap.RealImapConnection.executeSimpleCommand(RealImapConnection.kt:747)
08-04 23:19:11.221 23247  7026 E RealImapFolder:        at com.fsck.k9.mail.store.imap.RealImapConnection.executeSimpleCommand(RealImapConnection.kt:733)
08-04 23:19:11.221 23247  7026 E RealImapFolder:        at com.fsck.k9.mail.store.imap.RealImapFolder.executeSimpleCommand(RealImapFolder.kt:106)
08-04 23:19:11.221 23247  7026 E RealImapFolder:        at com.fsck.k9.mail.store.imap.RealImapFolder.internalOpen(RealImapFolder.kt:146)
08-04 23:19:11.221 23247  7026 E RealImapFolder:        at com.fsck.k9.mail.store.imap.RealImapFolder.open(RealImapFolder.kt:117)
08-04 23:19:11.221 23247  7026 E RealImapFolder:        at com.fsck.k9.backend.imap.ImapSync.synchronizeMailboxSynchronous(ImapSync.kt:84)
08-04 23:19:11.221 23247  7026 E RealImapFolder:        at com.fsck.k9.backend.imap.ImapSync.sync(ImapSync.kt:34)
08-04 23:19:11.221 23247  7026 E RealImapFolder:        at com.fsck.k9.backend.imap.ImapBackend.sync(ImapBackend.kt:56)
08-04 23:19:11.221 23247  7026 E RealImapFolder:        at com.fsck.k9.controller.MessagingController.syncFolder(MessagingController.java:658)
08-04 23:19:11.221 23247  7026 E RealImapFolder:        at com.fsck.k9.controller.MessagingController.synchronizeMailboxSynchronous(MessagingController.java:596)
08-04 23:19:11.221 23247  7026 E RealImapFolder:        at com.fsck.k9.controller.MessagingController.loadMoreMessagesSynchronous(MessagingController.java:560)
08-04 23:19:11.221 23247  7026 E RealImapFolder:        at com.fsck.k9.controller.MessagingController.lambda$loadMoreMessages$4$com-fsck-k9-controller-MessagingController(MessagingController.java:544)
08-04 23:19:11.221 23247  7026 E RealImapFolder:        at com.fsck.k9.controller.MessagingController$$ExternalSyntheticLambda10.run(D8$$SyntheticClass:0)
08-04 23:19:11.221 23247  7026 E RealImapFolder:        at com.fsck.k9.controller.MessagingController.runInBackground(MessagingController.java:239)
08-04 23:19:11.221 23247  7026 E RealImapFolder:        at com.fsck.k9.controller.MessagingController.-$$Nest$mrunInBackground(Unknown Source:0)
08-04 23:19:11.221 23247  7026 E RealImapFolder:        at com.fsck.k9.controller.MessagingController$1.run(MessagingController.java:179)
08-04 23:19:11.221 23247  7026 E RealImapFolder:        at java.lang.Thread.run(Thread.java:1012)
08-04 23:19:11.236 23247  7026 E ImapSync: synchronizeMailbox
08-04 23:19:11.236 23247  7026 E ImapSync: com.fsck.k9.mail.store.imap.NegativeImapResponseException: Command: EXAMINE "INBOX.INBOX.Spam"; response: #8# [NO, Mailbox doesn't exist: INBOX.INBOX.Spam]
08-04 23:19:11.236 23247  7026 E ImapSync:      at com.fsck.k9.mail.store.imap.ImapResponseParser.readStatusResponse(ImapResponseParser.java:127)
08-04 23:19:11.236 23247  7026 E ImapSync:      at com.fsck.k9.mail.store.imap.RealImapConnection.executeSimpleCommand(RealImapConnection.kt:747)
08-04 23:19:11.236 23247  7026 E ImapSync:      at com.fsck.k9.mail.store.imap.RealImapConnection.executeSimpleCommand(RealImapConnection.kt:733)
08-04 23:19:11.236 23247  7026 E ImapSync:      at com.fsck.k9.mail.store.imap.RealImapFolder.executeSimpleCommand(RealImapFolder.kt:106)
08-04 23:19:11.236 23247  7026 E ImapSync:      at com.fsck.k9.mail.store.imap.RealImapFolder.internalOpen(RealImapFolder.kt:146)
08-04 23:19:11.236 23247  7026 E ImapSync:      at com.fsck.k9.mail.store.imap.RealImapFolder.open(RealImapFolder.kt:117)
08-04 23:19:11.236 23247  7026 E ImapSync:      at com.fsck.k9.backend.imap.ImapSync.synchronizeMailboxSynchronous(ImapSync.kt:84)
08-04 23:19:11.236 23247  7026 E ImapSync:      at com.fsck.k9.backend.imap.ImapSync.sync(ImapSync.kt:34)
08-04 23:19:11.236 23247  7026 E ImapSync:      at com.fsck.k9.backend.imap.ImapBackend.sync(ImapBackend.kt:56)
08-04 23:19:11.236 23247  7026 E ImapSync:      at com.fsck.k9.controller.MessagingController.syncFolder(MessagingController.java:658)
08-04 23:19:11.236 23247  7026 E ImapSync:      at com.fsck.k9.controller.MessagingController.synchronizeMailboxSynchronous(MessagingController.java:596)
08-04 23:19:11.236 23247  7026 E ImapSync:      at com.fsck.k9.controller.MessagingController.loadMoreMessagesSynchronous(MessagingController.java:560)
08-04 23:19:11.236 23247  7026 E ImapSync:      at com.fsck.k9.controller.MessagingController.lambda$loadMoreMessages$4$com-fsck-k9-controller-MessagingController(MessagingController.java:544)
08-04 23:19:11.236 23247  7026 E ImapSync:      at com.fsck.k9.controller.MessagingController$$ExternalSyntheticLambda10.run(D8$$SyntheticClass:0)
08-04 23:19:11.236 23247  7026 E ImapSync:      at com.fsck.k9.controller.MessagingController.runInBackground(MessagingController.java:239)
08-04 23:19:11.236 23247  7026 E ImapSync:      at com.fsck.k9.controller.MessagingController.-$$Nest$mrunInBackground(Unknown Source:0)
08-04 23:19:11.236 23247  7026 E ImapSync:      at com.fsck.k9.controller.MessagingController$1.run(MessagingController.java:179)
08-04 23:19:11.236 23247  7026 E ImapSync:      at java.lang.Thread.run(Thread.java:1012)
08-04 23:19:11.259 23247  7026 E ImapSync: Failed synchronizing folder konto.pl:INBOX.Spam @ Mon Aug 04 23:19:11 CEST 2025
08-04 23:19:11.267 23247  7026 I MessagingController:  Command 'loadMoreMessages' completed
```

Logs showing failed message removal (moving to trash)

```
08-04 21:02:52.606 22513 31526 V RealImapConnection: conn83236368>>> 191 UID MOVE 27707:27708,27714:27715 "INBOX.INBOX.Trash"
08-04 21:02:52.653 22513 31526 V ImapResponseParser: conn83236368<<<#191# [NO, [TRYCREATE], Mailbox doesn't exist: INBOX.INBOX.Trash]
08-04 21:02:52.655 22513 31526 E MessagingController: Failure of command 'move_and_mark_as_read' was permanent, removing command from queue
08-04 21:02:52.655 22513 31526 E MessagingController: com.fsck.k9.mail.store.imap.NegativeImapResponseException: Command: UID MOVE 27707:27708,27714:27715 "INBOX.INBOX.Trash"; response: #191# [NO, [TRYCREATE], Mailbox doesn't exist: INBOX.INBOX.Trash]
08-04 21:02:52.655 22513 31526 E MessagingController: 	at com.fsck.k9.mail.store.imap.ImapResponseParser.readStatusResponse(ImapResponseParser.java:127)
08-04 21:02:52.655 22513 31526 E MessagingController: 	at com.fsck.k9.mail.store.imap.RealImapConnection.executeSimpleCommand(RealImapConnection.kt:747)
08-04 21:02:52.655 22513 31526 E MessagingController: 	at com.fsck.k9.mail.store.imap.RealImapConnection.executeSimpleCommand(RealImapConnection.kt:733)
08-04 21:02:52.655 22513 31526 E MessagingController: 	at com.fsck.k9.mail.store.imap.RealImapConnection.executeCommandWithIdSet(RealImapConnection.kt:770)
08-04 21:02:52.655 22513 31526 E MessagingController: 	at com.fsck.k9.mail.store.imap.RealImapFolder.moveMessagesUsingMoveExtension(RealImapFolder.kt:350)
08-04 21:02:52.655 22513 31526 E MessagingController: 	at com.fsck.k9.mail.store.imap.RealImapFolder.moveMessages(RealImapFolder.kt:335)
08-04 21:02:52.655 22513 31526 E MessagingController: 	at com.fsck.k9.backend.imap.CommandMoveOrCopyMessages.moveOrCopyMessages(CommandMoveOrCopyMessages.kt:59)
08-04 21:02:52.655 22513 31526 E MessagingController: 	at com.fsck.k9.backend.imap.CommandMoveOrCopyMessages.moveMessages(CommandMoveOrCopyMessages.kt:15)
08-04 21:02:52.655 22513 31526 E MessagingController: 	at com.fsck.k9.backend.imap.ImapBackend.moveMessagesAndMarkAsRead(ImapBackend.kt:104)
08-04 21:02:52.655 22513 31526 E MessagingController: 	at com.fsck.k9.controller.MessagingController.processPendingMoveOrCopy(MessagingController.java:932)
08-04 21:02:52.655 22513 31526 E MessagingController: 	at com.fsck.k9.controller.MessagingController.processPendingMoveAndRead(MessagingController.java:902)
08-04 21:02:52.655 22513 31526 E MessagingController: 	at com.fsck.k9.controller.MessagingControllerCommands$PendingMoveAndMarkAsRead.execute(MessagingControllerCommands.java:94)
08-04 21:02:52.655 22513 31526 E MessagingController: 	at com.fsck.k9.controller.MessagingController.processPendingCommandsSynchronous(MessagingController.java:752)
08-04 21:02:52.655 22513 31526 E MessagingController: 	at com.fsck.k9.controller.MessagingController$3.run(MessagingController.java:722)
08-04 21:02:52.655 22513 31526 E MessagingController: 	at com.fsck.k9.controller.MessagingController.runInBackground(MessagingController.java:239)
08-04 21:02:52.655 22513 31526 E MessagingController: 	at com.fsck.k9.controller.MessagingController.-$$Nest$mrunInBackground(MessagingController.java:0)
08-04 21:02:52.655 22513 31526 E MessagingController: 	at com.fsck.k9.controller.MessagingController$1.run(MessagingController.java:179)
08-04 21:02:52.655 22513 31526 E MessagingController: 	at java.lang.Thread.run(Thread.java:1012)
```

The problem is the following:

The folder on Dovecot IMAP server is named "INBOX.PANGAEA" (shown in UI as "PANGAEA" on Thunderbird Mobile), but when Thunderbird synchronizes it, the mailserver logs:

2025-09-10T13:56:58.772552+00:00 serv2 dovecot: IMAP(thetaphi): Debug: Mailbox INBOX.INBOX.PANGAEA: Mailbox opened

So basically it seems to duplicate the root folder name and therefore opens an empty mailbox. The folder is not created on mailserver (I am happy about this). When sending a mail, the move to "INBOX.Sent" is not even logged on server.
This is a blocker issue, because it makes using IMAP servers using the above IMAP folder structure completely unuseable.

I need to downgrade now.
Hi @uschindler, thanks for the report! We are aware of the issue and we are working on a fix.

Please see the comments in #9567, as there are some workarounds you can use until the fix is ready.
Thanks @rafaeltonholo,
Sorry for duplicate issue. The other one was older already and I did not find it. I had seen it but wasn't able to find it later (the description was a bit strange).

Are you also aware about the issue that the connection of the standard folders trash, sent, archives, drafts in the settings of account are also completely lost?
> Thanks [@rafaeltonholo](https://github.com/rafaeltonholo), Sorry for duplicate issue. The other one was older already and I did not find it. I had seen it but wasn't able to find it later (the description was a bit strange).
>
> Are you also aware about the issue that the connection of the standard folders trash, sent, archives, drafts in the settings of account are also completely lost?

I'm not entirely clear on what you mean by "completely lost", but if you’re referring to the issue of not being able to synchronize the folders, then yes, that is the same problem.

As you noted in the logs, there is a duplication in the IMAP prefix when we attempt to sync the folders using the command `EXAMINE <folder>`. This is why you see `INBOX.INBOX.<folder>` in the Dovecot logs; you can also find this in the app's logs.

For now, a workaround would be to disable the "Auto-detect IMAP namespace" and leave the "IMAP path prefix" empty in your incoming configuration until we can implement a proper fix for the issue that does not cause additional side effects.

We apologize for the inconvenience and are committed to resolving this issue promptly.
Hi,
The workaround works, no problem.

The second issue is about "Settings -> Account Name -> Folders".

After the upgrade to 12.x (without the workaround applied) the settings there are also lost. The previously selected default folders are all empty. Of course, you can select the folders from drawer again.

After applying the workaround you have to fix this, because folder names changed. But that's not the issue! The folder connections got lost from beginning. This was also reported as side effect in other issues, e.g. https://github.com/thunderbird/thunderbird-android/issues/9755#issuecomment-3266770220

Does this help? Of course this could be a side effect of this issue, I just want to make sure that the default folders on settings stay alive, and the 11.x folder setting gets restored for users who do not yet have applied the workaround.

It also looks after applying the workaround the IMAP default folders are not detected, too. So you have to select them manually. Normally the IMAP server returns some special flags for the default folders so they are auto detected as sent, trash, drafts, archives.

My request is just to make sure the default folder assignments get restored after Bugfix and auto detection works, too.
I am talking about this screen:

<img width="50%" alt="Image" src="https://github.com/user-attachments/assets/a1fec768-ab35-46a3-8c31-0d41a5a51513" />

This one had no folder assignments after upgrade to 12.x.
Oh, I see now what you are talking about. Yes, it is related to the same issue, but unfortunately, there is no automatic solution once it occurs.

As the `folders.server_id` is updated to include the IMAP prefix, the app interprets that the folders got deleted and new folders were created. That makes the app no longer able to locate the previously configured special folders, which results in the loss of that configuration.

The only way to resolve this is by manually reconfiguring those settings, unfortunately.

Thank you for bringing this to my attention. I will re-open this ticket to maintain visibility, but will change the title to specifically address the special folders issue.

Once the IMAP prefix issue is resolved, I will look for a way to prevent this from happening to others who haven't updated yet, so their settings won't be erased.
Same issue here, 12.0b4 installed on Samsung S25 from Google Play, logs showing same issue.
I tried replacing[^1] `prefixedName` with `serverId` and everything that I've checked seems to work (fetching messages, deleting, moving, copying, adding/removing flags (seen and starred)).

[^1]: What I really did for test was just adding `return serverId` as the first line of `prefixedName` getter.
For info: there's a workaround for this issue, at least in my case. For those accounts where this issue occurred - they were all on the same server - after disabling "Auto-detect IMAP namespace" and leaving IMAP path prefix empty, the problem is gone.
Thank for the workaround @GerryArqIT , it's working well (just the name for folder are replaced with imap path, but it's ok until a futur fix)
Same Here.. with dovecot IMAP Servers.
I see this bug with Courier IMAP which is used by one of the most famous rental server companies in Japan.
Sorry guys.. but this is a high priority bug. this is happens at the moment to many users. nobody of them can see their e-mails in folgers in the client. looks always empty. two of my friends got the same problem.
and no: they dont want to change the imap prefix.. deinstall 🫩😢🫣
Thanks for the workaround. Works flawlessly with freenet.de, be-webspace.DE and t-online.de. I hope the fallback lands in the next minor release: If no Prefix is detected accept none "" as valid server answer.
> For info: there's a workaround for this issue, at least in my case. For those accounts where this issue occurred - they were all on the same server - after disabling "Auto-detect IMAP namespace" and leaving IMAP path prefix empty, the problem is gone.

~~Where would you set this option?
I can't find it in the K9/thunderbird settings nor do I host the mailserver so there is very limited stuff I can do server side.~~

Found it: _Settings > account > fetching mail > incoming server_
> > For info: there's a workaround for this issue, at least in my case. For those accounts where this issue occurred - they were all on the same server - after disabling "Auto-detect IMAP namespace" and leaving IMAP path prefix empty, the problem is gone.
>
> Where would you set this option? I can't find it in the K9/thunderbird settings nor do I host the mailserver so there is very limited stuff I can do server side.

In the account settings, under Fetching mail -> Incoming server.

It would be really great to get an actual fix for this issue, though.  It was really not obvious why things stopped working.
> Where would you set this option?

Settings -> select account -> Fetching mail -> Incoming server\
The relevant options are at the bottom, uncheck "Auto-detect IMAP namespace".
> It would be really great to get an actual fix for this issue, though. It was really not obvious why things stopped working.

Yeah I was confused why I could not load any mails in my spam folder as well as why deleting mails didn't sync to the server
Similar and **very unpleasant issue** here. Thunderbird 12.1 on Pixel 9 Pro XL with GrapheneOS-Android 16.
However, this issue does not apply with all my email-services. **While some email-services work well, others do not sync properly (in my case ...@t-online.de)**. What I see is the following when using Thunderbird latest versions on both Mac and Android:

Read mail on Android - marked as read on Mac (thus all fine so far)
Delete mail on Android - gets **NOT** deleted on Mac (**sync issue!**)
Moving mail on Android from inbox to another folder - gets **Not** moved on Mac (**sync issue!**)
Moving mail on Mac to another folder - gets removed from Android (**sync issue!**). In other words - all mails moved on Mac from inbox to a folder **are gone on the Android**. This is a really unpleasant.

Note: All **these issues do NOT appear with Thunderbird v11.1**. Thus, for the moment I have switched back to that version on my Android.
> For info: there's a workaround for this issue, at least in my case. For those accounts where this issue occurred - they were all on the same server - after disabling "Auto-detect IMAP namespace" and leaving IMAP path prefix empty, the problem is gone.

Solved for me as well. Thank you!
The affected version is on Play Store now: https://forum.k9mail.app/t/-/13509
> For info: there's a workaround for this issue, at least in my case. For those accounts where this issue occurred - they were all on the same server - after disabling "Auto-detect IMAP namespace" and leaving IMAP path prefix empty, the problem is gone.

Thank you! The workaround works for me as well.
However, I you have moved a mail on the Android BEFORE disabling "Auto-detect IMAP namespace", this particular mail will NOT be synced AFTER disabling "Auto-detect IMAP namespace". In stead, you will get a copy of that mail.
Thx again for your hint.
@GerryArqIT's workaround did the trick for me as well. Many, many thanks! :-)
> > For info: there's a workaround for this issue, at least in my case. For those accounts where this issue occurred - they were all on the same server - after disabling "Auto-detect IMAP namespace" and leaving IMAP path prefix empty, the problem is gone.
>
> Thank you! The workaround works for me as well. However, I you have moved a mail on the Android BEFORE disabling "Auto-detect IMAP namespace", this particular mail will NOT be synced AFTER disabling "Auto-detect IMAP namespace". In stead, you will get a copy of that mail. Thx again for your hint.

Be careful with that copy! I am pretty sure that the “copy” you see in K9/Thunderbird Android is just a ghost. Check this by opening the message and then attempting to move it somewhere—a ghost will give you the error about the message not being synchronized to the server. Don’t delete the other one—you might permanently lose it whenever the ghost copy gets cleaned up!
> > > For info: there's a workaround for this issue, at least in my case. For those accounts where this issue occurred - they were all on the same server - after disabling "Auto-detect IMAP namespace" and leaving IMAP path prefix empty, the problem is gone.
> >
> >
> > Thank you! The workaround works for me as well. However, I you have moved a mail on the Android BEFORE disabling "Auto-detect IMAP namespace", this particular mail will NOT be synced AFTER disabling "Auto-detect IMAP namespace". In stead, you will get a copy of that mail. Thx again for your hint.
>
> Be careful with that copy! I am pretty sure that the “copy” you see in K9/Thunderbird Android is just a ghost. Check this by opening the message and then attempting to move it somewhere—a ghost will give you the error about the message not being synchronized to the server. Don’t delete the other one—you might permanently lose it whenever the ghost copy gets cleaned up!

Yes, it's a ghost, in fact - I have used the same approach as you to detect the ghost copy - which then can be deleted on the Android.
> Be careful with that copy! I am pretty sure that the “copy” you see in K9/Thunderbird Android is just a ghost.

*Possibly* it can be useful to do a refresh with:
- "Manage folders" (at the bottom of the folder list)
- -> choose a problematic folder
- -> in "Folder Settings" for that folder, choose "Clear local messages" in the three-dot menu on the upper right.

I say **possibly**; that's what I did. But I don't know if this could make things worse.
I wonder if this is why 12.1 won't retrieve any of my mail from my primary IMAP account on DreamHost.
Confirmed based on multiple reports.
> For info: there's a workaround for this issue, at least in my case. For those accounts where this issue occurred - they were all on the same server - after disabling "Auto-detect IMAP namespace" and leaving IMAP path prefix empty, the problem is gone.

Stumbled upon the issue in 12.1. Thank you @GerryArqIT for the workaround.

./gradlew :backend:imap:test --tests "net.thunderbird.backend.imap.ImapRemoteFolderCreatorTest" :mail:protocols:imap:test --tests "com.fsck.k9.mail.store.imap.RealImapStoreTest" --tests "com.fsck.k9.mail.store.imap.RealImapFolderTest"

diff --git a/backend/imap/src/main/java/com/fsck/k9/backend/imap/CommandRefreshFolderList.kt b/backend/imap/src/main/java/com/fsck/k9/backend/imap/CommandRefreshFolderList.kt
index 9ae0e96d537..d6b55215e60 100644
--- a/backend/imap/src/main/java/com/fsck/k9/backend/imap/CommandRefreshFolderList.kt
+++ b/backend/imap/src/main/java/com/fsck/k9/backend/imap/CommandRefreshFolderList.kt
@@ -6,15 +6,25 @@ import com.fsck.k9.backend.api.updateFolders
 import com.fsck.k9.mail.FolderType
 import com.fsck.k9.mail.store.imap.FolderListItem
 import com.fsck.k9.mail.store.imap.ImapStore
+import net.thunderbird.core.logging.Logger
+import net.thunderbird.core.logging.legacy.Log
 import net.thunderbird.feature.mail.folder.api.FolderPathDelimiter

+private const val TAG = "CommandRefreshFolderList"
+
 internal class CommandRefreshFolderList(
     private val backendStorage: BackendStorage,
     private val imapStore: ImapStore,
+    private val logger: Logger = Log,
 ) {
+
+    private val LegacyFolderListItem.normalizedServerId: String
+        get() = imapStore.combinedPrefix?.let {
+            serverId.removePrefix(prefix = it)
+        } ?: serverId
+
     fun refreshFolderList(): FolderPathDelimiter? {
-        // TODO: Start using the proper server ID.
-        //  For now we still use the old server ID format (decoded, with prefix removed).
+        logger.verbose(TAG) { "refreshFolderList() called" }
         val folders = imapStore.getFolders()
         val folderPathDelimiter = folders.firstOrNull { it.folderPathDelimiter != null }?.folderPathDelimiter
         val foldersOnServer = folders.toLegacyFolderList()
@@ -23,16 +33,19 @@ internal class CommandRefreshFolderList(
         backendStorage.updateFolders {
             val foldersToCreate = mutableListOf<FolderInfo>()
             for (folder in foldersOnServer) {
-                if (folder.serverId !in oldFolderServerIds) {
-                    foldersToCreate.add(FolderInfo(folder.serverId, folder.name, folder.type))
+                if (folder.normalizedServerId !in oldFolderServerIds) {
+                    foldersToCreate.add(FolderInfo(folder.normalizedServerId, folder.name, folder.type))
                 } else {
-                    changeFolder(folder.serverId, folder.name, folder.type)
+                    changeFolder(folder.normalizedServerId, folder.name, folder.type)
                 }
             }
+
+            logger.verbose(TAG) { "refreshFolderList: foldersToCreate = $foldersToCreate" }
             createFolders(foldersToCreate)

-            val newFolderServerIds = foldersOnServer.map { it.serverId }
+            val newFolderServerIds = foldersOnServer.map { it.normalizedServerId }
             val removedFolderServerIds = oldFolderServerIds - newFolderServerIds
+            logger.verbose(TAG) { "refreshFolderList: folders to remove = $removedFolderServerIds" }
             deleteFolders(removedFolderServerIds)
         }
         return folderPathDelimiter
diff --git a/legacy/storage/build.gradle.kts b/legacy/storage/build.gradle.kts
index 5878bc52642..52d9b9538c0 100644
--- a/legacy/storage/build.gradle.kts
+++ b/legacy/storage/build.gradle.kts
@@ -8,6 +8,10 @@ dependencies {
     implementation(projects.core.logging.api)

     implementation(projects.legacy.core)
+    // Required for MigrationTo107
+    implementation(projects.mail.common)
+    implementation(projects.mail.protocols.imap)
+
     implementation(libs.androidx.core.ktx)
     implementation(libs.mime4j.core)
     implementation(libs.commons.io)
diff --git a/legacy/storage/src/main/java/com/fsck/k9/storage/StoreSchemaDefinition.java b/legacy/storage/src/main/java/com/fsck/k9/storage/StoreSchemaDefinition.java
index a6f103b5acc..e8bb1822d21 100644
--- a/legacy/storage/src/main/java/com/fsck/k9/storage/StoreSchemaDefinition.java
+++ b/legacy/storage/src/main/java/com/fsck/k9/storage/StoreSchemaDefinition.java
@@ -12,7 +12,7 @@

 class StoreSchemaDefinition implements SchemaDefinition {
-    static final int DB_VERSION = 89;
+    static final int DB_VERSION = 90;

     private final MigrationsHelper migrationsHelper;

diff --git a/legacy/storage/src/main/java/com/fsck/k9/storage/migrations/MigrationTo90.kt b/legacy/storage/src/main/java/com/fsck/k9/storage/migrations/MigrationTo90.kt
new file mode 100644
index 00000000000..a04c38128a0
--- /dev/null
+++ b/legacy/storage/src/main/java/com/fsck/k9/storage/migrations/MigrationTo90.kt
@@ -0,0 +1,122 @@
+package com.fsck.k9.storage.migrations
+
+import android.database.sqlite.SQLiteDatabase
+import com.fsck.k9.mail.AuthType
+import com.fsck.k9.mail.AuthenticationFailedException
+import com.fsck.k9.mail.ServerSettings
+import com.fsck.k9.mail.oauth.AuthStateStorage
+import com.fsck.k9.mail.oauth.OAuth2TokenProviderFactory
+import com.fsck.k9.mail.ssl.TrustedSocketFactory
+import com.fsck.k9.mail.store.imap.ImapClientInfo
+import com.fsck.k9.mail.store.imap.ImapStore
+import com.fsck.k9.mail.store.imap.ImapStoreConfig
+import com.fsck.k9.mail.store.imap.ImapStoreSettings
+import com.fsck.k9.mail.store.imap.ImapStoreSettings.autoDetectNamespace
+import com.fsck.k9.mail.store.imap.ImapStoreSettings.pathPrefix
+import com.fsck.k9.mailstore.MigrationsHelper
+import net.thunderbird.core.android.account.Expunge
+import net.thunderbird.core.android.account.LegacyAccountDto
+import net.thunderbird.core.common.mail.Protocols
+import net.thunderbird.core.logging.Logger
+import net.thunderbird.core.logging.legacy.Log
+import org.koin.core.component.KoinComponent
+import org.koin.core.component.inject
+import org.koin.core.qualifier.named
+
+private const val TAG = "MigrationTo90"
+
+internal class MigrationTo90(
+    private val db: SQLiteDatabase,
+    private val migrationsHelper: MigrationsHelper,
+    private val logger: Logger = Log,
+) : KoinComponent {
+    private val trustedSocketFactory: TrustedSocketFactory by inject()
+    private val clientInfoAppName: String by inject(named("ClientInfoAppName"))
+    private val clientInfoAppVersion: String by inject(named("ClientInfoAppVersion"))
+    private val oAuth2TokenProviderFactory: OAuth2TokenProviderFactory by inject()
+
+    fun removeImapPrefixFromFolderServerId() {
+        val account = migrationsHelper.account
+        if (account.incomingServerSettings.type != Protocols.IMAP) {
+            logger.verbose(TAG) {
+                "account ${account.uuid} is not an IMAP account, skipping db migration for this account."
+            }
+            return
+        }
+
+        logger.verbose(TAG) { "started db migration to version 107 to account ${account.uuid}" }
+
+        val imapStore = createImapStore(account)
+
+        try {
+            logger.verbose(TAG) { "fetching IMAP prefix" }
+            imapStore.fetchImapPrefix()
+        } catch (e: AuthenticationFailedException) {
+            logger.warn(TAG, e) { "failed to fetch IMAP prefix." }
+            return
+        }
+
+        val imapPrefix = imapStore.combinedPrefix
+
+        if (imapPrefix?.isNotBlank() == true) {
+            logger.verbose(TAG) { "Imap Prefix ($imapPrefix) detected, updating folder's server_id" }
+            val query = """
+                |UPDATE folders
+                |    SET server_id = REPLACE(server_id, '$imapPrefix', '')
+                |WHERE
+                |    server_id LIKE '$imapPrefix%'
+            """.trimMargin()
+
+            db.execSQL(query)
+        } else {
+            logger.verbose(TAG) { "No Imap Prefix detected, skipping db migration" }
+        }
+
+        logger.verbose(TAG) { "completed db migration to version 107 for account ${account.uuid}" }
+    }
+
+    private fun createImapStore(account: LegacyAccountDto): ImapStore {
+        val serverSettings = account.toImapServerSettings()
+        val oAuth2TokenProvider = if (serverSettings.authenticationType == AuthType.XOAUTH2) {
+            val authStateStorage = object : AuthStateStorage {
+                override fun getAuthorizationState(): String? = account.oAuthState
+                override fun updateAuthorizationState(authorizationState: String?) = Unit
+            }
+            oAuth2TokenProviderFactory.create(authStateStorage)
+        } else {
+            null
+        }
+
+        return ImapStore.create(
+            serverSettings = serverSettings,
+            config = createImapStoreConfig(account),
+            trustedSocketFactory = trustedSocketFactory,
+            oauthTokenProvider = oAuth2TokenProvider,
+        )
+    }
+
+    private fun LegacyAccountDto.toImapServerSettings(): ServerSettings {
+        val serverSettings = incomingServerSettings
+        return serverSettings.copy(
+            extra = ImapStoreSettings.createExtra(
+                autoDetectNamespace = serverSettings.autoDetectNamespace,
+                pathPrefix = serverSettings.pathPrefix,
+                useCompression = useCompression,
+                sendClientInfo = isSendClientInfoEnabled,
+            ),
+        )
+    }
+
+    private fun createImapStoreConfig(account: LegacyAccountDto): ImapStoreConfig {
+        return object : ImapStoreConfig {
+            override val logLabel
+                get() = account.uuid
+
+            override fun isSubscribedFoldersOnly() = account.isSubscribedFoldersOnly
+
+            override fun isExpungeImmediately() = account.expungePolicy == Expunge.EXPUNGE_IMMEDIATELY
+
+            override fun clientInfo() = ImapClientInfo(appName = clientInfoAppName, appVersion = clientInfoAppVersion)
+        }
+    }
+}
diff --git a/legacy/storage/src/main/java/com/fsck/k9/storage/migrations/Migrations.kt b/legacy/storage/src/main/java/com/fsck/k9/storage/migrations/Migrations.kt
index 5b284ea76e1..41415a120ec 100644
--- a/legacy/storage/src/main/java/com/fsck/k9/storage/migrations/Migrations.kt
+++ b/legacy/storage/src/main/java/com/fsck/k9/storage/migrations/Migrations.kt
@@ -36,5 +36,6 @@ object Migrations {
         if (oldVersion < 87) MigrationTo87(db, migrationsHelper).addFoldersSyncEnabledColumn()
         if (oldVersion < 88) MigrationTo88(db, migrationsHelper).addFoldersVisibleColumn()
         if (oldVersion < 89) MigrationTo89(db, migrationsHelper).addAccountIdColumn()
+        if (oldVersion < 90) MigrationTo90(db, migrationsHelper).removeImapPrefixFromFolderServerId()
     }
 }
diff --git a/mail/protocols/imap/src/main/java/com/fsck/k9/mail/store/imap/ImapStore.kt b/mail/protocols/imap/src/main/java/com/fsck/k9/mail/store/imap/ImapStore.kt
index df02a98b1fd..4ab19c98b4c 100644
--- a/mail/protocols/imap/src/main/java/com/fsck/k9/mail/store/imap/ImapStore.kt
+++ b/mail/protocols/imap/src/main/java/com/fsck/k9/mail/store/imap/ImapStore.kt
@@ -6,6 +6,11 @@ import com.fsck.k9.mail.ssl.TrustedSocketFactory
 import net.thunderbird.core.common.exception.MessagingException

 interface ImapStore {
+    /**
+     * The IMAP prefix combined with the Path delimiter given by the server.
+     */
+    val combinedPrefix: String?
+
     @Throws(MessagingException::class)
     fun checkSettings()

@@ -16,6 +21,8 @@ interface ImapStore {

     fun closeAllConnections()

+    fun fetchImapPrefix()
+
     companion object : ImapStoreFactory {
         override fun create(
             serverSettings: ServerSettings,
diff --git a/mail/protocols/imap/src/main/java/com/fsck/k9/mail/store/imap/InternalImapStore.kt b/mail/protocols/imap/src/main/java/com/fsck/k9/mail/store/imap/InternalImapStore.kt
index 647327f8f90..ae1800ba0db 100644
--- a/mail/protocols/imap/src/main/java/com/fsck/k9/mail/store/imap/InternalImapStore.kt
+++ b/mail/protocols/imap/src/main/java/com/fsck/k9/mail/store/imap/InternalImapStore.kt
@@ -6,7 +6,10 @@ internal interface InternalImapStore {
     val logLabel: String
     val config: ImapStoreConfig

-    fun getCombinedPrefix(): String
+    /**
+     * The IMAP prefix combined with the Path delimiter given by the server.
+     */
+    val combinedPrefix: String?

     fun getPermanentFlagsIndex(): MutableSet<Flag>
 }
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 99b7975321e..b6aa84017a8 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
@@ -73,8 +73,7 @@ internal class RealImapFolder(
     @get:Throws(MessagingException::class)
     private val prefixedName: String
         get() {
-            var prefixedName = ""
-            if (!INBOX.equals(serverId, ignoreCase = true)) {
+            val imapPrefix = if (!INBOX.equals(serverId, ignoreCase = true)) {
                 val connection = synchronized(this) {
                     this.connection ?: connectionManager.getConnection()
                 }
@@ -88,11 +87,13 @@ internal class RealImapFolder(
                         connectionManager.releaseConnection(connection)
                     }
                 }
-                prefixedName = internalImapStore.getCombinedPrefix()
+                internalImapStore.combinedPrefix.orEmpty()
+            } else {
+                ""
             }
-            prefixedName += serverId
+            val normalizedServerId = serverId.removePrefix(imapPrefix)

-            return prefixedName
+            return "$imapPrefix$normalizedServerId"
         }

     @get:Throws(MessagingException::class)
diff --git a/mail/protocols/imap/src/main/java/com/fsck/k9/mail/store/imap/RealImapStore.kt b/mail/protocols/imap/src/main/java/com/fsck/k9/mail/store/imap/RealImapStore.kt
index cb440b41001..761a853a775 100644
--- a/mail/protocols/imap/src/main/java/com/fsck/k9/mail/store/imap/RealImapStore.kt
+++ b/mail/protocols/imap/src/main/java/com/fsck/k9/mail/store/imap/RealImapStore.kt
@@ -31,7 +31,11 @@ internal open class RealImapStore(
     private val host: String = checkNotNull(serverSettings.host)

     private var pathPrefix: String?
-    private var combinedPrefix: String? = null
+
+    private var _combinedPrefix: String? = null
+    override val combinedPrefix: String?
+        get() = _combinedPrefix ?: buildCombinedPrefix().also { _combinedPrefix = it }
+
     private var pathDelimiter: String? = null

     private val permanentFlagsIndex: MutableSet<Flag> = mutableSetOf()
@@ -59,12 +63,22 @@ internal open class RealImapStore(
         )
     }

-    override fun getCombinedPrefix(): String {
-        return combinedPrefix ?: buildCombinedPrefix().also { combinedPrefix = it }
+    override fun fetchImapPrefix() {
+        if (combinedPrefix != null) return
+
+        val connection = createImapConnection()
+        connection.open()
+        if (connection.hasCapability(Capabilities.NAMESPACE)) {
+            val responses = connection.executeSimpleCommand(Commands.NAMESPACE)
+            NamespaceResponse.parse(responses)?.let { response ->
+                pathPrefix = response.prefix
+                pathDelimiter = response.hierarchyDelimiter
+            }
+        }
     }

-    private fun buildCombinedPrefix(): String {
-        val pathPrefix = pathPrefix ?: return ""
+    private fun buildCombinedPrefix(): String? {
+        val pathPrefix = pathPrefix ?: return null

         val trimmedPathPrefix = pathPrefix.trim { it <= ' ' }
         val trimmedPathDelimiter = pathDelimiter?.trim { it <= ' ' }.orEmpty()
@@ -74,7 +88,7 @@ internal open class RealImapStore(
         } else if (trimmedPathPrefix.isNotEmpty()) {
             trimmedPathPrefix + trimmedPathDelimiter
         } else {
-            ""
+            null
         }
     }

@@ -118,15 +132,17 @@ internal open class RealImapStore(
             subscribedOnly -> {
                 "LSUB \"\" %s"
             }
+
             connection.supportsListExtended -> {
                 "LIST \"\" %s RETURN (SPECIAL-USE)"
             }
+
             else -> {
                 "LIST \"\" %s"
             }
         }

-        val encodedListPrefix = ImapUtility.encodeString(getCombinedPrefix() + "*")
+        val encodedListPrefix = ImapUtility.encodeString("${combinedPrefix.orEmpty()}*")
         val responses = connection.executeSimpleCommand(String.format(commandFormat, encodedListPrefix))

         val listResponses = if (subscribedOnly) {
@@ -141,7 +157,7 @@ internal open class RealImapStore(

             if (pathDelimiter == null) {
                 pathDelimiter = listResponse.hierarchyDelimiter
-                combinedPrefix = null
+                _combinedPrefix = null
             }

             if (RealImapFolder.INBOX.equals(serverId, ignoreCase = true)) {
@@ -196,7 +212,7 @@ internal open class RealImapStore(
     }

     private fun removePrefixFromFolderName(folderName: String): String? {
-        val prefix = getCombinedPrefix()
+        val prefix = combinedPrefix.orEmpty()
         val prefixLength = prefix.length
         if (prefixLength == 0) {
             return folderName
@@ -319,7 +335,7 @@ internal open class RealImapStore(
             }

         override fun setCombinedPrefix(prefix: String?) {
-            combinedPrefix = prefix
+            _combinedPrefix = prefix
         }
     }
 }

diff --git a/backend/imap/src/test/java/com/fsck/k9/backend/imap/TestImapStore.kt b/backend/imap/src/test/java/com/fsck/k9/backend/imap/TestImapStore.kt
index 098cb8e909c..d868503eed5 100644
--- a/backend/imap/src/test/java/com/fsck/k9/backend/imap/TestImapStore.kt
+++ b/backend/imap/src/test/java/com/fsck/k9/backend/imap/TestImapStore.kt
@@ -8,6 +8,9 @@ import com.fsck.k9.mail.store.imap.ImapStore
 class TestImapStore : ImapStore {
     private val folders = mutableMapOf<String, ImapFolder>()

+    override val combinedPrefix: String?
+        get() = throw UnsupportedOperationException("not implemented")
+
     fun addFolder(serverId: String): TestImapFolder {
         require(!folders.containsKey(serverId)) { "Folder '$serverId' already exists" }

@@ -44,4 +47,8 @@ class TestImapStore : ImapStore {
     override fun closeAllConnections() {
         throw UnsupportedOperationException("not implemented")
     }
+
+    override fun fetchImapPrefix() {
+        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 92c2f628cec..3ea01d40f09 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
@@ -122,6 +122,9 @@ class ImapRemoteFolderCreatorTest {
 private class FakeImapStore(
     private val folder: TestImapFolder,
 ) : ImapStore {
+    override val combinedPrefix: String?
+        get() = throw NotImplementedError("combinedPrefix not implemented")
+
     override fun checkSettings() {
         throw NotImplementedError("checkSettings not implemented")
     }
@@ -135,4 +138,8 @@ private class FakeImapStore(
     override fun closeAllConnections() {
         throw NotImplementedError("closeAllConnections not implemented")
     }
+
+    override fun fetchImapPrefix() {
+        throw NotImplementedError("fetchImapPrefix not implemented")
+    }
 }
diff --git a/mail/protocols/imap/src/test/java/com/fsck/k9/mail/store/imap/FakeImapStore.kt b/mail/protocols/imap/src/test/java/com/fsck/k9/mail/store/imap/FakeImapStore.kt
index d6a7f541c84..1f8516603d2 100644
--- a/mail/protocols/imap/src/test/java/com/fsck/k9/mail/store/imap/FakeImapStore.kt
+++ b/mail/protocols/imap/src/test/java/com/fsck/k9/mail/store/imap/FakeImapStore.kt
@@ -9,6 +9,9 @@ class FakeImapStore : ImapStore {
     val hasOpenConnections: Boolean
         get() = openConnectionCount != 0

+    override val combinedPrefix: String?
+        get() = null
+
     override fun checkSettings() {
         throw UnsupportedOperationException("not implemented")
     }
@@ -25,4 +28,8 @@ class FakeImapStore : ImapStore {
     override fun closeAllConnections() {
         openConnectionCount = 0
     }
+
+    override fun fetchImapPrefix() {
+        throw UnsupportedOperationException("not implemented")
+    }
 }
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 ef787b3281b..71e0b080100 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
@@ -32,6 +32,10 @@ import java.nio.file.Files
 import java.util.Date
 import java.util.TimeZone
 import net.thunderbird.core.common.exception.MessagingException
+import net.thunderbird.core.logging.legacy.Log
+import net.thunderbird.core.logging.testing.TestLogger
+import net.thunderbird.feature.mail.folder.api.FOLDER_DEFAULT_PATH_DELIMITER
+import net.thunderbird.feature.mail.folder.api.FolderPathDelimiter
 import net.thunderbird.protocols.imap.folder.attributeName
 import okio.Buffer
 import org.apache.james.mime4j.util.MimeUtil
@@ -55,15 +59,10 @@ import org.mockito.kotlin.never
 import org.mockito.kotlin.stub
 import org.mockito.kotlin.whenever

-@Suppress("MaxLineLength")
+@Suppress("MaxLineLength", "LargeClass")
 class RealImapFolderTest {
     private val imapStoreConfig = FakeImapStoreConfig()
-    private val internalImapStore = object : InternalImapStore {
-        override val logLabel = "Account"
-        override val config = imapStoreConfig
-        override fun getCombinedPrefix() = ""
-        override fun getPermanentFlagsIndex() = mutableSetOf<Flag>()
-    }
+    private val internalImapStore = FakeInternalImapStore(config = imapStoreConfig)
     private val imapConnection = mock<ImapConnection>()
     private val testConnectionManager = TestConnectionManager(imapConnection)

@@ -71,6 +70,7 @@ class RealImapFolderTest {

     @Before
     fun setUp() {
+        Log.logger = TestLogger()
         tempDirectory = Files.createTempDirectory("RealImapFolderTest").toFile()
         BinaryTempFileBody.setTempDirectory(tempDirectory)
     }
@@ -398,6 +398,48 @@ class RealImapFolderTest {
         assertCommandWithIdsIssued("UID MOVE 1 \"la vache dit møø\"")
     }

+    @Test
+    fun `moveMessages() should message to folder using serverId when IMAP Prefix is set`() {
+        // Arrange
+        val prefix = "INBOX"
+        val pathDelimiter = "."
+        val imapStore = FakeInternalImapStore(
+            config = imapStoreConfig,
+            imapPrefix = prefix,
+            pathDelimiter = pathDelimiter,
+        )
+
+        val sourceFolderName = "My New Folder"
+        val sourceFolderId = "$prefix$pathDelimiter$sourceFolderName"
+        val sourceFolder = createFolder(folderName = sourceFolderId, internalImapStore = imapStore)
+        prepareImapFolderForOpen(openMode = OpenMode.READ_WRITE, folderServerId = sourceFolderId)
+
+        val destinationFolderName = "Destination Folder"
+        val destinationFolderId = "$prefix$pathDelimiter$destinationFolderName"
+        val destinationFolder = createFolder(folderName = destinationFolderId, internalImapStore = imapStore)
+        imapConnection.stub {
+            on { hasCapability(Capabilities.MOVE) } doReturn true
+        }
+        val messageUid = "1"
+        val message = createImapMessage(messageUid)
+
+        setupMoveResponses(
+            "* OK [COPYUID 1757454189 2 1]",
+            "* 1 EXPUNGE",
+            "x OK MOVE completed",
+        )
+
+        // Act
+        sourceFolder.open(OpenMode.READ_WRITE)
+        sourceFolder.moveMessages(
+            messages = listOf(message),
+            folder = destinationFolder,
+        )
+
+        // Assert
+        assertCommandWithIdsIssued("UID MOVE $messageUid \"$destinationFolderId\"")
+    }
+
     @Test
     fun moveMessages_shouldCopyMessages() {
         val sourceFolder = createFolder("Folder")
@@ -1566,6 +1608,62 @@ class RealImapFolderTest {
         }
     }

+    @Test
+    fun `open() should open folder in read-only mode successfully when connection has IMAP prefix`() {
+        // Arrange
+        val prefix = "INBOX"
+        val pathDelimiter = "/"
+        val folderName = "My New Folder"
+        val serverId = "$prefix$pathDelimiter$folderName"
+        val imapStore = FakeInternalImapStore(
+            config = imapStoreConfig,
+            imapPrefix = prefix,
+            pathDelimiter = pathDelimiter,
+        )
+        val openMode = OpenMode.READ_ONLY
+        imapConnection.stub {
+            on { executeSimpleCommand(anyString()) } doReturn createOpenImapResponse(
+                openMode = OpenMode.READ_ONLY,
+            )
+        }
+
+        val folder = createFolder(folderName = serverId, internalImapStore = imapStore)
+
+        // Act
+        folder.open(openMode)
+
+        // Assert
+        assertCommandIssued("EXAMINE \"$serverId\"")
+    }
+
+    @Test
+    fun `open() should open folder in read-write mode successfully when connection has IMAP prefix`() {
+        // Arrange
+        val prefix = "INBOX"
+        val pathDelimiter = "/"
+        val folderName = "My New Folder"
+        val imapStore = FakeInternalImapStore(
+            config = imapStoreConfig,
+            imapPrefix = prefix,
+            pathDelimiter = pathDelimiter,
+        )
+        val serverId = "$prefix$pathDelimiter$folderName"
+        val openMode = OpenMode.READ_WRITE
+        imapConnection.stub {
+            on { executeSimpleCommand(anyString()) } doReturn createOpenImapResponse(
+                openMode = OpenMode.READ_WRITE,
+            )
+        }
+
+        val folder = createFolder(folderName = serverId, internalImapStore = imapStore)
+
+        // Act
+        folder.open(openMode)
+
+        // Assert
+        assertCommandIssued("SELECT \"$serverId\"")
+    }
+
     @Suppress("SameParameterValue")
     private fun createPlainTextPart(serverExtra: String): Part {
         val part = createPart(serverExtra)
@@ -1600,9 +1698,17 @@ class RealImapFolderTest {
         return response
     }

-    private fun createFolder(folderName: String): RealImapFolder {
-        return RealImapFolder(internalImapStore, testConnectionManager, folderName, FolderNameCodec())
-    }
+    private fun createFolder(
+        folderName: String,
+        internalImapStore: InternalImapStore = this.internalImapStore,
+        connectionManager: ImapConnectionManager = testConnectionManager,
+        folderNameCodec: FolderNameCodec = FolderNameCodec(),
+    ): RealImapFolder = RealImapFolder(
+        internalImapStore = internalImapStore,
+        connectionManager = connectionManager,
+        serverId = folderName,
+        folderNameCodec = folderNameCodec,
+    )

     private fun createImapMessage(uid: String): ImapMessage {
         return mock {
@@ -1622,29 +1728,32 @@ class RealImapFolderTest {

     private fun createMessageRetrievalListener() = mock<MessageRetrievalListener<ImapMessage>>()

-    private fun prepareImapFolderForOpen(openMode: OpenMode) {
-        val imapResponses = listOf(
-            createImapResponse("* FLAGS (\\Answered \\Flagged \\Deleted \\Seen \\Draft NonJunk \$MDNSent)"),
+    private fun createOpenImapResponse(openMode: OpenMode) = buildList {
+        add(
             createImapResponse(
                 "* OK [PERMANENTFLAGS (\\Answered \\Flagged \\Deleted \\Seen \\Draft NonJunk \$MDNSent \\*)] " +
                     "Flags permitted.",
             ),
-            createImapResponse("* 23 EXISTS"),
-            createImapResponse("* 0 RECENT"),
-            createImapResponse("* OK [UIDVALIDITY 1125022061] UIDs valid"),
-            createImapResponse("* OK [UIDNEXT 57576] Predicted next UID"),
+        )
+        add(createImapResponse("* 23 EXISTS"))
+        add(createImapResponse("* 0 RECENT"))
+        add(createImapResponse("* OK [UIDVALIDITY 1125022061] UIDs valid"))
+        add(createImapResponse("* OK [UIDNEXT 57576] Predicted next UID"))
+        add(
             if (openMode == OpenMode.READ_WRITE) {
                 createImapResponse("2 OK [READ-WRITE] Select completed.")
             } else {
                 createImapResponse("2 OK [READ-ONLY] Examine completed.")
             },
         )
+    }

-        if (openMode == OpenMode.READ_WRITE) {
-            whenever(imapConnection.executeSimpleCommand("SELECT \"Folder\"")).thenReturn(imapResponses)
-        } else {
-            whenever(imapConnection.executeSimpleCommand("EXAMINE \"Folder\"")).thenReturn(imapResponses)
-        }
+    private fun prepareImapFolderForOpen(openMode: OpenMode, folderServerId: String = "Folder") {
+        val imapResponses = createOpenImapResponse(openMode)
+
+        val command = if (openMode == OpenMode.READ_WRITE) "SELECT" else "EXAMINE"
+        whenever(imapConnection.executeSimpleCommand("$command \"$folderServerId\""))
+            .thenReturn(imapResponses)
     }

     private fun assertCommandWithIdsIssued(expectedCommand: String) {
@@ -1735,3 +1844,14 @@ internal class TestConnectionManager(private val connection: ImapConnection) : I

     override fun releaseConnection(connection: ImapConnection?) = Unit
 }
+
+private class FakeInternalImapStore(
+    override val config: ImapStoreConfig,
+    override val logLabel: String = "Account",
+    private val imapPrefix: String? = null,
+    private val pathDelimiter: FolderPathDelimiter = FOLDER_DEFAULT_PATH_DELIMITER,
+    private val permanentFlagsIndex: MutableSet<Flag> = mutableSetOf(),
+) : InternalImapStore {
+    override val combinedPrefix: String = imapPrefix?.let { "$it$pathDelimiter" }.orEmpty()
+    override fun getPermanentFlagsIndex() = permanentFlagsIndex
+}
diff --git a/mail/protocols/imap/src/test/java/com/fsck/k9/mail/store/imap/RealImapStoreTest.kt b/mail/protocols/imap/src/test/java/com/fsck/k9/mail/store/imap/RealImapStoreTest.kt
index 1cd1bf07304..bb8df4ed70d 100644
--- a/mail/protocols/imap/src/test/java/com/fsck/k9/mail/store/imap/RealImapStoreTest.kt
+++ b/mail/protocols/imap/src/test/java/com/fsck/k9/mail/store/imap/RealImapStoreTest.kt
@@ -493,9 +493,8 @@ class RealImapStoreTest {
             imapConnections.add(imapConnection)
         }

-        override fun getCombinedPrefix(): String {
-            return testCombinedPrefix ?: super.getCombinedPrefix()
-        }
+        override val combinedPrefix: String?
+            get() = testCombinedPrefix ?: super.combinedPrefix

         fun setTestCombinedPrefix(prefix: String?) {
             testCombinedPrefix = prefix
diff --git a/mail/protocols/imap/src/test/java/com/fsck/k9/mail/store/imap/TestImapStore.kt b/mail/protocols/imap/src/test/java/com/fsck/k9/mail/store/imap/TestImapStore.kt
index a347ead7c0e..61ff68c9efb 100644
--- a/mail/protocols/imap/src/test/java/com/fsck/k9/mail/store/imap/TestImapStore.kt
+++ b/mail/protocols/imap/src/test/java/com/fsck/k9/mail/store/imap/TestImapStore.kt
@@ -1,6 +1,9 @@
 package com.fsck.k9.mail.store.imap

 internal class TestImapStore(private val folder: ImapFolder) : ImapStore, ImapConnectionProvider {
+    override val combinedPrefix: String?
+        get() = throw UnsupportedOperationException("Not yet implemented")
+
     override fun checkSettings() {
         throw UnsupportedOperationException("not implemented")
     }
@@ -21,4 +24,8 @@ internal class TestImapStore(private val folder: ImapFolder) : ImapStore, ImapCo
     override fun closeAllConnections() {
         throw UnsupportedOperationException("not implemented")
     }
+
+    override fun fetchImapPrefix() {
+        throw UnsupportedOperationException("not implemented")
+    }
 }


 - Fixes #9758
 - **Summary** : This PR refactors the existing AutocryptHeader and AutocryptGossipHeader classes from Java into idiomatic Kotlin. The conversion simplifies code, enhances null safety, and leverages Kotlin’s modern language features.
 - **Implementation Highlights**:
   - `AutocryptHeader.kt`:
       - Rewritten as a Kotlin class with primary constructor parameters:
          - parameters: Map<String, String>
          - addr: String
          - keyData: ByteArray
          - isPreferEncryptMutual: Boolean
     -  `toRawHeaderString()` method builds the header string with support for folding base64 key data.
     - Overridden `equals()` and `hashCode()` methods based on Kotlin's conventions.
     - Companion object defines constants such as **AUTOCRYPT_HEADER**, **AUTOCRYPT_PARAM_ADDR**, and a helper `createFoldedBase64KeyData()` method for formatting
   - `AutocryptGossipHeader.kt` :
     - Defined as an internal data class:
       - Fields: addr: String, keyData: ByteArray
       - `toRawHeaderString()` method similar to AutocryptHeader
       - equals() and hashCode() override using contentEquals() and contentHashCode() for byte arrays
       - Companion object includes constants: **AUTOCRYPT_GOSSIP_HEADER**, **AUTOCRYPT_PARAM_ADDR**, **AUTOCRYPT_PARAM_KEY_DATA**
    - **Updated references in codebase**:
      - Minor adjustments in `AutocryptOperations.java` to accommodate Kotlin properties instead of Java getters: moved from `autocryptHeader.getKeyData()` to Kotlin-style property access` autocryptHeader.keyData`, and similarly for other fields
    - **Test updates**: Refactored `AutocryptHeaderParserTest.java` to reflect Kotlin property access instead of Java getter methods, ensuring test compatibility:
      - Replaced `autocryptHeader.getAddr()`, .`getKeyData(`), .`getParameters()` with Kotlin property access autocryptHeader.addr, .keyData, .parameters
      - Adjusted input stream handling for test resource loading (readFromResourceFile) to account for simpler method signature

Rewrite AutocryptHeader and AutocryptGossipHeader in Kotlin instead of Java
We should migrate the `AutocryptHeader`  and `AutocryptGossipHeader` to Kotlin to align with the ongoing modernization efforts and Kotlin-first development approach across the codebase.

./gradlew :legacy:core:testDebugUnitTest --tests "com.fsck.k9.autocrypt.AutocryptHeaderParserTest"

diff --git a/legacy/core/src/main/java/com/fsck/k9/autocrypt/AutocryptGossipHeader.java b/legacy/core/src/main/java/com/fsck/k9/autocrypt/AutocryptGossipHeader.java
deleted file mode 100644
index 91d9b788365..00000000000
--- a/legacy/core/src/main/java/com/fsck/k9/autocrypt/AutocryptGossipHeader.java
+++ /dev/null
@@ -1,60 +0,0 @@
-package com.fsck.k9.autocrypt;
-
-
-import java.util.Arrays;
-
-import androidx.annotation.NonNull;
-
-
-class AutocryptGossipHeader {
-    static final String AUTOCRYPT_GOSSIP_HEADER = "Autocrypt-Gossip";
-
-    private static final String AUTOCRYPT_PARAM_ADDR = "addr";
-    private static final String AUTOCRYPT_PARAM_KEY_DATA = "keydata";
-
-
-    @NonNull
-    final byte[] keyData;
-    @NonNull
-    final String addr;
-
-    AutocryptGossipHeader(@NonNull String addr, @NonNull byte[] keyData) {
-        this.addr = addr;
-        this.keyData = keyData;
-    }
-
-    String toRawHeaderString() {
-        StringBuilder builder = new StringBuilder();
-
-        builder.append(AutocryptGossipHeader.AUTOCRYPT_GOSSIP_HEADER).append(": ");
-        builder.append(AutocryptGossipHeader.AUTOCRYPT_PARAM_ADDR).append('=').append(addr).append("; ");
-        builder.append(AutocryptGossipHeader.AUTOCRYPT_PARAM_KEY_DATA).append('=');
-        builder.append(AutocryptHeader.createFoldedBase64KeyData(keyData));
-
-        return builder.toString();
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) {
-            return true;
-        }
-        if (o == null || getClass() != o.getClass()) {
-            return false;
-        }
-
-        AutocryptGossipHeader that = (AutocryptGossipHeader) o;
-
-        if (!Arrays.equals(keyData, that.keyData)) {
-            return false;
-        }
-        return addr.equals(that.addr);
-    }
-
-    @Override
-    public int hashCode() {
-        int result = Arrays.hashCode(keyData);
-        result = 31 * result + addr.hashCode();
-        return result;
-    }
-}
diff --git a/legacy/core/src/main/java/com/fsck/k9/autocrypt/AutocryptGossipHeader.kt b/legacy/core/src/main/java/com/fsck/k9/autocrypt/AutocryptGossipHeader.kt
new file mode 100644
index 00000000000..9aa26b41d4d
--- /dev/null
+++ b/legacy/core/src/main/java/com/fsck/k9/autocrypt/AutocryptGossipHeader.kt
@@ -0,0 +1,25 @@
+package com.fsck.k9.autocrypt
+
+internal data class AutocryptGossipHeader(@JvmField val addr: String, @JvmField val keyData: ByteArray) {
+    fun toRawHeaderString() = buildString {
+        append(AUTOCRYPT_GOSSIP_HEADER).append(": ")
+        append(AUTOCRYPT_PARAM_ADDR).append('=').append(addr).append("; ")
+        append(AUTOCRYPT_PARAM_KEY_DATA).append('=')
+        append(AutocryptHeader.createFoldedBase64KeyData(keyData))
+    }
+
+    override fun equals(other: Any?): Boolean =
+        other is AutocryptGossipHeader &&
+            addr == other.addr &&
+            keyData.contentEquals(other.keyData)
+
+    override fun hashCode(): Int =
+        31 * keyData.contentHashCode() + addr.hashCode()
+
+    companion object {
+        const val AUTOCRYPT_GOSSIP_HEADER: String = "Autocrypt-Gossip"
+
+        private const val AUTOCRYPT_PARAM_ADDR = "addr"
+        private const val AUTOCRYPT_PARAM_KEY_DATA = "keydata"
+    }
+}
diff --git a/legacy/core/src/main/java/com/fsck/k9/autocrypt/AutocryptHeader.java b/legacy/core/src/main/java/com/fsck/k9/autocrypt/AutocryptHeader.java
deleted file mode 100644
index d5d66710314..00000000000
--- a/legacy/core/src/main/java/com/fsck/k9/autocrypt/AutocryptHeader.java
+++ /dev/null
@@ -1,102 +0,0 @@
-package com.fsck.k9.autocrypt;
-
-
-import java.util.Arrays;
-import java.util.Map;
-
-import androidx.annotation.NonNull;
-
-import okio.ByteString;
-
-
-class AutocryptHeader {
-    static final String AUTOCRYPT_HEADER = "Autocrypt";
-
-    static final String AUTOCRYPT_PARAM_ADDR = "addr";
-    static final String AUTOCRYPT_PARAM_KEY_DATA = "keydata";
-
-    static final String AUTOCRYPT_PARAM_TYPE = "type";
-    static final String AUTOCRYPT_TYPE_1 = "1";
-
-    static final String AUTOCRYPT_PARAM_PREFER_ENCRYPT = "prefer-encrypt";
-    static final String AUTOCRYPT_PREFER_ENCRYPT_MUTUAL = "mutual";
-
-    private static final int HEADER_LINE_LENGTH = 76;
-
-
-    @NonNull
-    final byte[] keyData;
-    @NonNull
-    final String addr;
-    @NonNull
-    final Map<String,String> parameters;
-    final boolean isPreferEncryptMutual;
-
-    AutocryptHeader(@NonNull Map<String, String> parameters, @NonNull String addr,
-            @NonNull byte[] keyData, boolean isPreferEncryptMutual) {
-        this.parameters = parameters;
-        this.addr = addr;
-        this.keyData = keyData;
-        this.isPreferEncryptMutual = isPreferEncryptMutual;
-    }
-
-    String toRawHeaderString() {
-        // TODO we don't properly fold lines here. if we want to support parameters, we need to do that somehow
-        if (!parameters.isEmpty()) {
-            throw new UnsupportedOperationException("arbitrary parameters not supported");
-        }
-
-        StringBuilder builder = new StringBuilder();
-        builder.append(AutocryptHeader.AUTOCRYPT_HEADER).append(": ");
-        builder.append(AutocryptHeader.AUTOCRYPT_PARAM_ADDR).append('=').append(addr).append("; ");
-        if (isPreferEncryptMutual) {
-            builder.append(AutocryptHeader.AUTOCRYPT_PARAM_PREFER_ENCRYPT)
-                    .append('=').append(AutocryptHeader.AUTOCRYPT_PREFER_ENCRYPT_MUTUAL).append("; ");
-        }
-        builder.append(AutocryptHeader.AUTOCRYPT_PARAM_KEY_DATA).append("=");
-        builder.append(createFoldedBase64KeyData(keyData));
-
-        return builder.toString();
-    }
-
-    static String createFoldedBase64KeyData(byte[] keyData) {
-        String base64KeyData = ByteString.of(keyData).base64();
-        StringBuilder result = new StringBuilder();
-
-        for (int i = 0, base64Length = base64KeyData.length(); i < base64Length; i += HEADER_LINE_LENGTH) {
-            if (i + HEADER_LINE_LENGTH <= base64Length) {
-                result.append("\r\n ");
-                result.append(base64KeyData, i, i + HEADER_LINE_LENGTH);
-            } else {
-                result.append("\r\n ");
-                result.append(base64KeyData, i, base64Length);
-            }
-        }
-
-        return result.toString();
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) {
-            return true;
-        }
-        if (o == null || getClass() != o.getClass()) {
-            return false;
-        }
-
-        AutocryptHeader that = (AutocryptHeader) o;
-
-        return isPreferEncryptMutual == that.isPreferEncryptMutual && Arrays.equals(keyData, that.keyData)
-                && addr.equals(that.addr) && parameters.equals(that.parameters);
-    }
-
-    @Override
-    public int hashCode() {
-        int result = Arrays.hashCode(keyData);
-        result = 31 * result + addr.hashCode();
-        result = 31 * result + parameters.hashCode();
-        result = 31 * result + (isPreferEncryptMutual ? 1 : 0);
-        return result;
-    }
-}
diff --git a/legacy/core/src/main/java/com/fsck/k9/autocrypt/AutocryptHeader.kt b/legacy/core/src/main/java/com/fsck/k9/autocrypt/AutocryptHeader.kt
new file mode 100644
index 00000000000..2d61be5e4a4
--- /dev/null
+++ b/legacy/core/src/main/java/com/fsck/k9/autocrypt/AutocryptHeader.kt
@@ -0,0 +1,78 @@
+package com.fsck.k9.autocrypt
+
+import okio.ByteString
+
+data class AutocryptHeader(
+    val parameters: Map<String, String>,
+    val addr: String,
+    val keyData: ByteArray,
+    val isPreferEncryptMutual: Boolean,
+) {
+
+    fun toRawHeaderString(): String {
+        // TODO we don't properly fold lines here. if we want to support parameters, we need to do that somehow
+        if (parameters.isNotEmpty()) {
+            error("arbitrary parameters not supported")
+        }
+
+        return buildString {
+            append(AUTOCRYPT_HEADER).append(": ")
+            append(AUTOCRYPT_PARAM_ADDR).append('=').append(addr).append("; ")
+            if (isPreferEncryptMutual) {
+                append(AUTOCRYPT_PARAM_PREFER_ENCRYPT)
+                    .append('=').append(AUTOCRYPT_PREFER_ENCRYPT_MUTUAL).append("; ")
+            }
+            append(AUTOCRYPT_PARAM_KEY_DATA).append("=")
+            append(createFoldedBase64KeyData(keyData))
+        }
+    }
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other !is AutocryptHeader) return false
+
+        return isPreferEncryptMutual == other.isPreferEncryptMutual &&
+            keyData.contentEquals(other.keyData) &&
+            addr == other.addr &&
+            parameters == other.parameters
+    }
+
+    override fun hashCode(): Int {
+        var result = keyData.contentHashCode()
+        result = 31 * result + addr.hashCode()
+        result = 31 * result + parameters.hashCode()
+        result = 31 * result + if (isPreferEncryptMutual) 1 else 0
+        return result
+    }
+
+    companion object {
+        const val AUTOCRYPT_HEADER = "Autocrypt"
+
+        const val AUTOCRYPT_PARAM_ADDR = "addr"
+        const val AUTOCRYPT_PARAM_KEY_DATA = "keydata"
+
+        const val AUTOCRYPT_PARAM_TYPE = "type"
+        const val AUTOCRYPT_TYPE_1 = "1"
+
+        const val AUTOCRYPT_PARAM_PREFER_ENCRYPT = "prefer-encrypt"
+        const val AUTOCRYPT_PREFER_ENCRYPT_MUTUAL = "mutual"
+
+        private const val HEADER_LINE_LENGTH = 76
+
+        fun createFoldedBase64KeyData(keyData: ByteArray): String {
+            val base64KeyData = ByteString.of(data = keyData).base64()
+            val result = StringBuilder()
+
+            for (i in base64KeyData.indices step HEADER_LINE_LENGTH) {
+                result.append("\r\n ")
+                if (i + HEADER_LINE_LENGTH <= base64KeyData.length) {
+                    result.append(base64KeyData, i, i + HEADER_LINE_LENGTH)
+                } else {
+                    result.append(base64KeyData, i, base64KeyData.length)
+                }
+            }
+
+            return result.toString()
+        }
+    }
+}
diff --git a/legacy/core/src/main/java/com/fsck/k9/autocrypt/AutocryptOperations.java b/legacy/core/src/main/java/com/fsck/k9/autocrypt/AutocryptOperations.java
index 206733fa580..c8654d8e397 100644
--- a/legacy/core/src/main/java/com/fsck/k9/autocrypt/AutocryptOperations.java
+++ b/legacy/core/src/main/java/com/fsck/k9/autocrypt/AutocryptOperations.java
@@ -44,7 +44,7 @@ public boolean addAutocryptPeerUpdateToIntentIfPresent(Message currentMessage, I
         }

         String messageFromAddress = currentMessage.getFrom()[0].getAddress();
-        if (!autocryptHeader.addr.equalsIgnoreCase(messageFromAddress)) {
+        if (!autocryptHeader.getAddr().equalsIgnoreCase(messageFromAddress)) {
             return false;
         }

@@ -53,7 +53,7 @@ public boolean addAutocryptPeerUpdateToIntentIfPresent(Message currentMessage, I
         Date effectiveDate = messageDate.before(internalDate) ? messageDate : internalDate;

         AutocryptPeerUpdate data = AutocryptPeerUpdate.create(
-                autocryptHeader.keyData, effectiveDate, autocryptHeader.isPreferEncryptMutual);
+            autocryptHeader.getKeyData(), effectiveDate, autocryptHeader.isPreferEncryptMutual());
         intent.putExtra(OpenPgpApi.EXTRA_AUTOCRYPT_PEER_ID, messageFromAddress);
         intent.putExtra(OpenPgpApi.EXTRA_AUTOCRYPT_PEER_UPDATE, data);
         return true;

diff --git a/legacy/core/src/test/java/com/fsck/k9/autocrypt/AutocryptHeaderParserTest.java b/legacy/core/src/test/java/com/fsck/k9/autocrypt/AutocryptHeaderParserTest.java
index 4aefaecfcb1..1b2d996d5ff 100644
--- a/legacy/core/src/test/java/com/fsck/k9/autocrypt/AutocryptHeaderParserTest.java
+++ b/legacy/core/src/test/java/com/fsck/k9/autocrypt/AutocryptHeaderParserTest.java
@@ -1,14 +1,15 @@
 package com.fsck.k9.autocrypt;

-import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;

-import net.thunderbird.core.android.testing.RobolectricTest;
-import net.thunderbird.core.common.exception.MessagingException;
 import com.fsck.k9.mail.internet.BinaryTempFileBody;
 import com.fsck.k9.mail.internet.MimeMessage;
+import net.thunderbird.core.android.testing.RobolectricTest;
+import net.thunderbird.core.common.exception.MessagingException;
+import net.thunderbird.core.logging.legacy.Log;
+import net.thunderbird.core.logging.testing.TestLogger;
 import org.junit.Before;
 import org.junit.Test;
 import org.robolectric.RuntimeEnvironment;
@@ -23,6 +24,7 @@ public class AutocryptHeaderParserTest extends RobolectricTest {

     @Before
     public void setUp() throws Exception {
+        Log.logger = new TestLogger();
         BinaryTempFileBody.setTempDirectory(RuntimeEnvironment.getApplication().getCacheDir());
     }

@@ -53,9 +55,9 @@ public void getValidAutocryptHeader__withSimpleAutocrypt() throws Exception {
         AutocryptHeader autocryptHeader = autocryptHeaderParser.getValidAutocryptHeader(message);

         assertNotNull(autocryptHeader);
-        assertEquals("alice@testsuite.autocrypt.org", autocryptHeader.addr);
-        assertEquals(0, autocryptHeader.parameters.size());
-        assertEquals(1225, autocryptHeader.keyData.length);
+        assertEquals("alice@testsuite.autocrypt.org", autocryptHeader.getAddr());
+        assertEquals(0, autocryptHeader.getParameters().size());
+        assertEquals(1225, autocryptHeader.getKeyData().length);
     }

     @Test
@@ -65,8 +67,8 @@ public void getValidAutocryptHeader__withExplicitType() throws Exception {
         AutocryptHeader autocryptHeader = autocryptHeaderParser.getValidAutocryptHeader(message);

         assertNotNull(autocryptHeader);
-        assertEquals("alice@testsuite.autocrypt.org", autocryptHeader.addr);
-        assertEquals(0, autocryptHeader.parameters.size());
+        assertEquals("alice@testsuite.autocrypt.org", autocryptHeader.getAddr());
+        assertEquals(0, autocryptHeader.getParameters().size());
     }

     @Test
@@ -94,9 +96,9 @@ public void getValidAutocryptHeader__withUnknownNonCriticalHeader() throws Excep
         AutocryptHeader autocryptHeader = autocryptHeaderParser.getValidAutocryptHeader(message);

         assertNotNull(autocryptHeader);
-        assertEquals("alice@testsuite.autocrypt.org", autocryptHeader.addr);
-        assertEquals(1, autocryptHeader.parameters.size());
-        assertEquals("ignore", autocryptHeader.parameters.get("_monkey"));
+        assertEquals("alice@testsuite.autocrypt.org", autocryptHeader.getAddr());
+        assertEquals(1, autocryptHeader.getParameters().size());
+        assertEquals("ignore", autocryptHeader.getParameters().get("_monkey"));
     }

     @Test
@@ -104,6 +106,7 @@ public void parseAutocryptHeader_toRawHeaderString() throws Exception {
         MimeMessage message = parseFromResource("autocrypt/rsa2048-simple.eml");
         AutocryptHeader autocryptHeader = autocryptHeaderParser.getValidAutocryptHeader(message);

+        assertNotNull(autocryptHeader);
         String headerValue = autocryptHeader.toRawHeaderString();
         headerValue = headerValue.substring("Autocrypt: ".length());
         AutocryptHeader parsedAutocryptHeader = autocryptHeaderParser.parseAutocryptHeader(headerValue);
@@ -116,7 +119,7 @@ private MimeMessage parseFromResource(String resourceName) throws IOException, M
         return MimeMessage.parseMimeMessage(inputStream, false);
     }

-    private InputStream readFromResourceFile(String name) throws FileNotFoundException {
+    private InputStream readFromResourceFile(String name) {
         return getClass().getResourceAsStream("/" + name);
     }
 }
diff --git a/legacy/ui/legacy/src/test/java/com/fsck/k9/autocrypt/AutocryptOperationsHelper.java b/legacy/ui/legacy/src/test/java/com/fsck/k9/autocrypt/AutocryptOperationsHelper.java
index a939ee81fb3..34a52e81799 100644
--- a/legacy/ui/legacy/src/test/java/com/fsck/k9/autocrypt/AutocryptOperationsHelper.java
+++ b/legacy/ui/legacy/src/test/java/com/fsck/k9/autocrypt/AutocryptOperationsHelper.java
@@ -16,8 +16,8 @@ public static void assertMessageHasAutocryptHeader(
         AutocryptHeader autocryptHeader = INSTANCE.getValidAutocryptHeader(message);

         assertNotNull(autocryptHeader);
-        assertEquals(addr, autocryptHeader.addr);
-        assertEquals(isPreferEncryptMutual, autocryptHeader.isPreferEncryptMutual);
-        assertArrayEquals(keyData, autocryptHeader.keyData);
+        assertEquals(addr, autocryptHeader.getAddr());
+        assertEquals(isPreferEncryptMutual, autocryptHeader.isPreferEncryptMutual());
+        assertArrayEquals(keyData, autocryptHeader.getKeyData());
     }
 }


fix(drawer): crash when opening placeholder folder

Fixes #9644.

- Change the placeholder `MailDisplayFolder` to not have an account ID.
- Ensure that we only trigger `onClick(displayFolder)` in case we have an account ID.
- If tapping on a Placeholder folder, toggle the `isExpanded` state.

> [!NOTE]
> A follow-up ticket will be placed to investigate why the `[Gmail]` prefix is added back when refreshing the folder list. For now, this PR is only meant to fix the crash.

Screen recording:

https://github.com/user-attachments/assets/94a9f89f-91b1-4e12-8ee8-7d0902e7a0e6

12.0b7 crash. Upon [gmail] folder selection
### 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

Thunderbird for Android

### App version

12.0b7

### Where did you get the app from?

Other

### Android version

Android 15

### Device model

Oneplus 10 Pro

### Steps to reproduce

App downloaded from github.

1. Close app and remove from androids running app list.
2. Open app again.
3. Switch from Unified to gmail account.
4. Observe standard gmail folders are all correctly displayed at the same level beneath each other.
5. Drop back to android launcher (TBA running in background)
6. I wait about 30 minutes.
7. Open the app and view the gmail folder list.
7. Observe abnormal presence of [gmail] expandable folder.
8. Select this [gmail] folder.
9. App crashes

### Expected behavior

The normal behaviour is where the standard gmail folders (Inbox, outbox, all mail, draft, etc) are all displayed beneath each other at the top level.

![Image](https://github.com/user-attachments/assets/db64c041-97a8-4cfa-96ae-ad79c5226aba)

### Actual behavior

I open the app after approx 30 minutes running in the background. I observe the app has changed the normal folder view and now displays an expandable [gmail] folder. Selecting this folder crashes the app.

I'm not sure sure what is triggering the view to change as it does so without user interaction. Perhaps background sync.

![Image](https://github.com/user-attachments/assets/88d8d5d2-b314-4255-ad38-c64e9dc10c2c)

### Logs

[k9mail-logs.txt](https://github.com/user-attachments/files/21970985/k9mail-logs.txt)

Seems related to #9134 , #9353 and possibly a regression following #9213
The [gmail] folder exists in v11.1 however selecting it doesn't crash the app.
This is the top crash (affects the most users) for 12.0b7
I think I have a simple reproducer that does not require waiting:

1. Add 2 accounts, 1 of them Gmail
2. Refresh folders on the Gmail account (Manage folders -> 3 dots -> Refresh folder list) (this updates folders to include the `[Gmail]/` prefix)
3. Switch to a different account (can be Unified; causes the app to regenerate folder tree)
4. Switch to the Gmail account again - there should be a `[Gmail]` [placeholder folder](https://github.com/thunderbird/thunderbird-android/blob/94c9063858f999cc4e57538ec0eb2fcd0c35a124/feature/navigation/drawer/dropdown/src/main/kotlin/net/thunderbird/feature/navigation/drawer/dropdown/domain/usecase/GetDisplayTreeFolder.kt#L73)
Thanks @krzysdz , I can also reproduce using your steps.
Noting the issue is also present in today's v12 release (non beta).

diff --git a/feature/navigation/drawer/dropdown/src/main/kotlin/net/thunderbird/feature/navigation/drawer/dropdown/domain/entity/MailDisplayFolder.kt b/feature/navigation/drawer/dropdown/src/main/kotlin/net/thunderbird/feature/navigation/drawer/dropdown/domain/entity/MailDisplayFolder.kt
index ee14c89efd4..07b8a6643d4 100644
--- a/feature/navigation/drawer/dropdown/src/main/kotlin/net/thunderbird/feature/navigation/drawer/dropdown/domain/entity/MailDisplayFolder.kt
+++ b/feature/navigation/drawer/dropdown/src/main/kotlin/net/thunderbird/feature/navigation/drawer/dropdown/domain/entity/MailDisplayFolder.kt
@@ -3,13 +3,13 @@ package net.thunderbird.feature.navigation.drawer.dropdown.domain.entity
 import net.thunderbird.feature.mail.folder.api.Folder

 internal data class MailDisplayFolder(
-    val accountId: String,
+    val accountId: String?,
     val folder: Folder,
     val isInTopGroup: Boolean,
     override val unreadMessageCount: Int,
     override val starredMessageCount: Int,
 ) : DisplayFolder {
-    override val id: String = createMailDisplayAccountFolderId(accountId, folder.id)
+    override val id: String = createMailDisplayAccountFolderId(accountId.orEmpty(), folder.id)
 }

 fun createMailDisplayAccountFolderId(accountId: String, folderId: Long): String {
diff --git a/feature/navigation/drawer/dropdown/src/main/kotlin/net/thunderbird/feature/navigation/drawer/dropdown/domain/usecase/GetDisplayTreeFolder.kt b/feature/navigation/drawer/dropdown/src/main/kotlin/net/thunderbird/feature/navigation/drawer/dropdown/domain/usecase/GetDisplayTreeFolder.kt
index 05ade0c3e12..ae75fcae30a 100644
--- a/feature/navigation/drawer/dropdown/src/main/kotlin/net/thunderbird/feature/navigation/drawer/dropdown/domain/usecase/GetDisplayTreeFolder.kt
+++ b/feature/navigation/drawer/dropdown/src/main/kotlin/net/thunderbird/feature/navigation/drawer/dropdown/domain/usecase/GetDisplayTreeFolder.kt
@@ -90,7 +90,7 @@ internal class GetDisplayTreeFolder : UseCase.GetDisplayTreeFolder {
     private fun createPlaceholderFolder(name: String): MailDisplayFolder {
         placeholderCounter += 1
         return MailDisplayFolder(
-            accountId = "placeholder",
+            accountId = null,
             folder = Folder(
                 id = placeholderCounter,
                 name = name,
diff --git a/feature/navigation/drawer/dropdown/src/main/kotlin/net/thunderbird/feature/navigation/drawer/dropdown/ui/DrawerViewModel.kt b/feature/navigation/drawer/dropdown/src/main/kotlin/net/thunderbird/feature/navigation/drawer/dropdown/ui/DrawerViewModel.kt
index 793e71437e5..c81ab049921 100644
--- a/feature/navigation/drawer/dropdown/src/main/kotlin/net/thunderbird/feature/navigation/drawer/dropdown/ui/DrawerViewModel.kt
+++ b/feature/navigation/drawer/dropdown/src/main/kotlin/net/thunderbird/feature/navigation/drawer/dropdown/ui/DrawerViewModel.kt
@@ -240,12 +240,14 @@ internal class DrawerViewModel(
         selectFolder(folder.id)

         if (folder is MailDisplayFolder) {
-            emitEffect(
-                Effect.OpenFolder(
-                    accountId = folder.accountId,
-                    folderId = folder.folder.id,
-                ),
-            )
+            if (folder.accountId != null) {
+                emitEffect(
+                    Effect.OpenFolder(
+                        accountId = folder.accountId,
+                        folderId = folder.folder.id,
+                    ),
+                )
+            }
         } else if (folder is UnifiedDisplayFolder) {
             emitEffect(Effect.OpenUnifiedFolder)
         }
diff --git a/feature/navigation/drawer/dropdown/src/main/kotlin/net/thunderbird/feature/navigation/drawer/dropdown/ui/folder/FolderListItem.kt b/feature/navigation/drawer/dropdown/src/main/kotlin/net/thunderbird/feature/navigation/drawer/dropdown/ui/folder/FolderListItem.kt
index 9261aad3726..3dd9ef6fb49 100644
--- a/feature/navigation/drawer/dropdown/src/main/kotlin/net/thunderbird/feature/navigation/drawer/dropdown/ui/folder/FolderListItem.kt
+++ b/feature/navigation/drawer/dropdown/src/main/kotlin/net/thunderbird/feature/navigation/drawer/dropdown/ui/folder/FolderListItem.kt
@@ -76,13 +76,14 @@ internal fun FolderListItem(
                 )
             },
             selected = selectedFolderId == displayFolder.id,
-            onClick = { onClick(displayFolder) },
-            modifier = Modifier.fillMaxWidth(),
-            icon = {
-                Icon(
-                    imageVector = mapFolderIcon(displayFolder),
-                )
+            onClick = {
+                when (displayFolder) {
+                    is MailDisplayFolder if displayFolder.accountId == null -> isExpanded.value = !isExpanded.value
+                    else -> onClick(displayFolder)
+                }
             },
+            modifier = Modifier.fillMaxWidth(),
+            icon = { Icon(imageVector = mapFolderIcon(displayFolder)) },
         )

         // Managing children

diff --git a/feature/navigation/drawer/dropdown/src/test/kotlin/net/thunderbird/feature/navigation/drawer/dropdown/domain/usecase/GetDisplayTreeFolderTest.kt b/feature/navigation/drawer/dropdown/src/test/kotlin/net/thunderbird/feature/navigation/drawer/dropdown/domain/usecase/GetDisplayTreeFolderTest.kt
index 0598feaccbd..1229ea96891 100644
--- a/feature/navigation/drawer/dropdown/src/test/kotlin/net/thunderbird/feature/navigation/drawer/dropdown/domain/usecase/GetDisplayTreeFolderTest.kt
+++ b/feature/navigation/drawer/dropdown/src/test/kotlin/net/thunderbird/feature/navigation/drawer/dropdown/domain/usecase/GetDisplayTreeFolderTest.kt
@@ -199,7 +199,7 @@ class GetDisplayTreeFolderTest {
                     children = persistentListOf(
                         DisplayTreeFolder(
                             displayFolder = MailDisplayFolder(
-                                accountId = "placeholder",
+                                accountId = null,
                                 folder = Folder(
                                     id = 1L,
                                     name = "(Unnamed)/(Unnamed)",
@@ -238,7 +238,7 @@ class GetDisplayTreeFolderTest {
                 ),
                 DisplayTreeFolder(
                     displayFolder = MailDisplayFolder(
-                        accountId = "placeholder",
+                        accountId = null,
                         folder = Folder(id = 2, name = "valid1", type = FolderType.REGULAR, isLocalOnly = false),
                         isInTopGroup = true,
                         unreadMessageCount = 0,
@@ -250,7 +250,7 @@ class GetDisplayTreeFolderTest {
                     children = persistentListOf(
                         DisplayTreeFolder(
                             displayFolder = MailDisplayFolder(
-                                accountId = "placeholder",
+                                accountId = null,
                                 folder = Folder(
                                     id = 3L,
                                     name = "valid1/(Unnamed)",
@@ -321,7 +321,7 @@ class GetDisplayTreeFolderTest {
             children = persistentListOf(
                 createDisplayTreeFolder(
                     displayFolder = createDisplayAccountFolder(
-                        accountId = "placeholder",
+                        accountId = null,
                         folderId = 1L,
                         folderName = "folderLevel1",
                         unreadMessageCount = 0,
@@ -380,7 +380,7 @@ class GetDisplayTreeFolderTest {
             children = persistentListOf(
                 createDisplayTreeFolder(
                     displayFolder = createDisplayAccountFolder(
-                        accountId = "placeholder",
+                        accountId = null,
                         folderId = 1,
                         folderName = "level1",
                         unreadMessageCount = 0,
@@ -392,7 +392,7 @@ class GetDisplayTreeFolderTest {
                     children = persistentListOf(
                         createDisplayTreeFolder(
                             displayFolder = createDisplayAccountFolder(
-                                accountId = "placeholder",
+                                accountId = null,
                                 folderId = 2L,
                                 folderName = "level1/level2",
                                 unreadMessageCount = 0,
@@ -430,7 +430,7 @@ class GetDisplayTreeFolderTest {
             folderName: String,
             unreadMessageCount: Int,
             starredMessageCount: Int,
-            accountId: String = "accountId",
+            accountId: String? = "accountId",
         ): MailDisplayFolder {
             return MailDisplayFolder(
                 accountId = accountId,
diff --git a/feature/navigation/drawer/dropdown/src/test/kotlin/net/thunderbird/feature/navigation/drawer/dropdown/ui/DrawerViewModelTest.kt b/feature/navigation/drawer/dropdown/src/test/kotlin/net/thunderbird/feature/navigation/drawer/dropdown/ui/DrawerViewModelTest.kt
index 9b845976de8..a0a1af139e7 100644
--- a/feature/navigation/drawer/dropdown/src/test/kotlin/net/thunderbird/feature/navigation/drawer/dropdown/ui/DrawerViewModelTest.kt
+++ b/feature/navigation/drawer/dropdown/src/test/kotlin/net/thunderbird/feature/navigation/drawer/dropdown/ui/DrawerViewModelTest.kt
@@ -334,7 +334,7 @@ internal class DrawerViewModelTest {

         assertThat(turbines.awaitEffectItem()).isEqualTo(
             Effect.OpenFolder(
-                accountId = displayFolders[1].accountId,
+                accountId = requireNotNull(displayFolders[1].accountId),
                 folderId = displayFolders[1].folder.id,
             ),
         )