diff --git a/mailbox-core/src/main/java/org/briarproject/mailbox/core/files/FileManager.kt b/mailbox-core/src/main/java/org/briarproject/mailbox/core/files/FileManager.kt
index 2ac6a11c2ee31e8a2b4d97ef5dccae22572e86e3..6e7aac6556a17eb9c9ff2fa0590a990be08d230a 100644
--- a/mailbox-core/src/main/java/org/briarproject/mailbox/core/files/FileManager.kt
+++ b/mailbox-core/src/main/java/org/briarproject/mailbox/core/files/FileManager.kt
@@ -8,6 +8,7 @@ import io.ktor.response.respond
 import io.ktor.response.respondFile
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.withContext
+import org.briarproject.mailbox.core.db.Database
 import org.briarproject.mailbox.core.server.AuthException
 import org.briarproject.mailbox.core.server.AuthManager
 import org.briarproject.mailbox.core.server.MailboxPrincipal
@@ -16,6 +17,7 @@ import org.briarproject.mailbox.core.system.RandomIdManager
 import javax.inject.Inject
 
 class FileManager @Inject constructor(
+    private val db: Database,
     private val authManager: AuthManager,
     private val fileProvider: FileProvider,
     private val randomIdManager: RandomIdManager,
@@ -118,12 +120,23 @@ class FileManager @Inject constructor(
         val principal: MailboxPrincipal? = call.principal()
         authManager.assertIsOwner(principal)
 
-        // TODO implement
-
-        call.respond(HttpStatusCode.OK, "get: Not yet implemented")
+        val folderListResponse = withContext(Dispatchers.IO) {
+            val list = ArrayList<FolderResponse>()
+            val contacts = db.transactionWithResult(true) { txn -> db.getContacts(txn) }
+            contacts.forEach { c ->
+                val id = c.outboxId
+                val folder = fileProvider.getFolder(id)
+                if (folder.listFiles()?.isNotEmpty() == true) {
+                    list.add(FolderResponse(id))
+                }
+            }
+            FolderListResponse(list)
+        }
+        call.respond(folderListResponse)
     }
-
 }
 
-data class FileListResponse(val files: ArrayList<FileResponse>)
+data class FileListResponse(val files: List<FileResponse>)
 data class FileResponse(val name: String, val time: Long)
+data class FolderListResponse(val folders: List<FolderResponse>)
+data class FolderResponse(val id: String)
diff --git a/mailbox-core/src/test/java/org/briarproject/mailbox/core/files/FileManagerIntegrationTest.kt b/mailbox-core/src/test/java/org/briarproject/mailbox/core/files/FileManagerIntegrationTest.kt
index 13894021687cd9658a081d4d35e0a14180302dbe..ad2363bf49fb1bf85b111dc47069885a5583ea6f 100644
--- a/mailbox-core/src/test/java/org/briarproject/mailbox/core/files/FileManagerIntegrationTest.kt
+++ b/mailbox-core/src/test/java/org/briarproject/mailbox/core/files/FileManagerIntegrationTest.kt
@@ -221,4 +221,68 @@ class FileManagerIntegrationTest : IntegrationTest() {
         }
         assertEquals(HttpStatusCode.NotFound.value, response.status.value)
     }
+
+    @Test
+    fun `list folders rejects contacts`(): Unit = runBlocking {
+        val response: HttpResponse = httpClient.get("$baseUrl/folders") {
+            authenticateWithToken(contact1.token)
+        }
+        assertEquals(HttpStatusCode.Unauthorized.value, response.status.value)
+    }
+
+    @Test
+    fun `list folders allows owner, returns empty result`(): Unit = runBlocking {
+        val response: HttpResponse = httpClient.get("$baseUrl/folders") {
+            authenticateWithToken(ownerToken)
+        }
+        assertEquals(HttpStatusCode.OK.value, response.status.value)
+        assertEquals("""{"folders":[]}""", response.readText())
+    }
+
+    @Test
+    fun `list folders returns more than a single folder`(): Unit = runBlocking {
+        // contact1 uploads a file
+        val response1: HttpResponse = httpClient.post("$baseUrl/files/${contact1.outboxId}") {
+            authenticateWithToken(contact1.token)
+            body = bytes
+        }
+        assertEquals(HttpStatusCode.OK.value, response1.status.value)
+
+        // contact2 uploads a file
+        val response2: HttpResponse = httpClient.post("$baseUrl/files/${contact2.outboxId}") {
+            authenticateWithToken(contact2.token)
+            body = bytes
+        }
+        assertEquals(HttpStatusCode.OK.value, response2.status.value)
+
+        // owner now sees both contacts' outboxes in folders list
+        val folderListResponse: FolderListResponse = httpClient.get("$baseUrl/folders") {
+            authenticateWithToken(ownerToken)
+        }
+        val folderList =
+            listOf(FolderResponse(contact1.outboxId), FolderResponse(contact2.outboxId))
+        assertEquals(FolderListResponse(folderList), folderListResponse)
+
+        // get the file IDs to be able to delete them to not interfere with other tests
+        val fileListResponse1: FileListResponse =
+            httpClient.get("$baseUrl/files/${contact1.outboxId}") {
+                authenticateWithToken(ownerToken)
+            }
+        val id1 = fileListResponse1.files[0].name
+        val deleteResponse1: HttpResponse =
+            httpClient.delete("$baseUrl/files/${contact1.outboxId}/$id1") {
+                authenticateWithToken(ownerToken)
+            }
+        assertEquals(HttpStatusCode.OK.value, deleteResponse1.status.value)
+        val fileListResponse2: FileListResponse =
+            httpClient.get("$baseUrl/files/${contact2.outboxId}") {
+                authenticateWithToken(ownerToken)
+            }
+        val id2 = fileListResponse2.files[0].name
+        val deleteResponse2: HttpResponse =
+            httpClient.delete("$baseUrl/files/${contact2.outboxId}/$id2") {
+                authenticateWithToken(ownerToken)
+            }
+        assertEquals(HttpStatusCode.OK.value, deleteResponse2.status.value)
+    }
 }
diff --git a/mailbox-core/src/test/java/org/briarproject/mailbox/core/files/FileManagerTest.kt b/mailbox-core/src/test/java/org/briarproject/mailbox/core/files/FileManagerTest.kt
index 0d21736ef398bdf42db5790546a4a2b17c04bfa6..638ed6afe226aae99cf46b2b62ed18accc2ccfd0 100644
--- a/mailbox-core/src/test/java/org/briarproject/mailbox/core/files/FileManagerTest.kt
+++ b/mailbox-core/src/test/java/org/briarproject/mailbox/core/files/FileManagerTest.kt
@@ -13,6 +13,7 @@ import io.mockk.mockk
 import io.mockk.mockkStatic
 import kotlinx.coroutines.runBlocking
 import org.briarproject.mailbox.core.TestUtils.getNewRandomId
+import org.briarproject.mailbox.core.db.Database
 import org.briarproject.mailbox.core.server.AuthManager
 import org.briarproject.mailbox.core.server.MailboxPrincipal
 import org.briarproject.mailbox.core.system.RandomIdManager
@@ -27,11 +28,12 @@ import kotlin.test.assertTrue
 
 class FileManagerTest {
 
+    private val db: Database = mockk()
     private val authManager: AuthManager = mockk()
     private val fileProvider: FileProvider = mockk()
     private val randomIdManager = RandomIdManager()
 
-    private val fileManager = FileManager(authManager, fileProvider, randomIdManager)
+    private val fileManager = FileManager(db, authManager, fileProvider, randomIdManager)
 
     private val call: ApplicationCall = mockk()
     private val id = getNewRandomId()