diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/android/AppModule.kt b/mailbox-android/src/main/java/org/briarproject/mailbox/android/AppModule.kt
index d1126699664b698566427a16011841c02c0eadaf..d7cee665e1be3fa7f82e511bd2a29ca4f6ab52d8 100644
--- a/mailbox-android/src/main/java/org/briarproject/mailbox/android/AppModule.kt
+++ b/mailbox-android/src/main/java/org/briarproject/mailbox/android/AppModule.kt
@@ -1,13 +1,14 @@
 package org.briarproject.mailbox.android
 
 import android.app.Application
-import android.content.Context
+import android.content.Context.MODE_PRIVATE
 import dagger.Module
 import dagger.Provides
 import dagger.hilt.InstallIn
 import dagger.hilt.components.SingletonComponent
 import org.briarproject.mailbox.core.CoreModule
 import org.briarproject.mailbox.core.db.DatabaseConfig
+import org.briarproject.mailbox.core.files.FileProvider
 import java.io.File
 import javax.inject.Singleton
 
@@ -23,7 +24,18 @@ internal class AppModule {
     @Provides
     fun provideDatabaseConfig(app: Application) = object : DatabaseConfig {
         override fun getDatabaseDirectory(): File {
-            return app.applicationContext.getDir("db", Context.MODE_PRIVATE)
+            return app.applicationContext.getDir("db", MODE_PRIVATE)
         }
     }
+
+    @Singleton
+    @Provides
+    fun provideFileProvider(app: Application) = object : FileProvider {
+        private val tempFilesDir = File(app.applicationContext.cacheDir, "tmp").also { it.mkdirs() }
+        private val filesDir = app.applicationContext.getDir("folders", MODE_PRIVATE)
+
+        override fun getTemporaryFile(fileId: String) = File(tempFilesDir, fileId)
+        override fun getFolder(folderId: String) = File(filesDir, folderId).also { it.mkdirs() }
+        override fun getFile(folderId: String, fileId: String) = File(getFolder(folderId), fileId)
+    }
 }
diff --git a/mailbox-cli/src/main/java/org/briarproject/mailbox/cli/JavaCliModule.kt b/mailbox-cli/src/main/java/org/briarproject/mailbox/cli/JavaCliModule.kt
index dfa08b350f49ff75e9f0a51512151ec2baa27c0b..18263ad275ae7bc527e2acba979c0b4a7330f9fe 100644
--- a/mailbox-cli/src/main/java/org/briarproject/mailbox/cli/JavaCliModule.kt
+++ b/mailbox-cli/src/main/java/org/briarproject/mailbox/cli/JavaCliModule.kt
@@ -7,6 +7,7 @@ import dagger.hilt.components.SingletonComponent
 import org.briarproject.mailbox.core.CoreModule
 import org.briarproject.mailbox.core.db.DatabaseConfig
 import org.briarproject.mailbox.core.event.DefaultEventExecutorModule
+import org.briarproject.mailbox.core.files.FileProvider
 import org.briarproject.mailbox.core.system.DefaultTaskSchedulerModule
 import org.briarproject.mailbox.core.tor.JavaTorModule
 import org.briarproject.mailbox.core.util.LogUtils.info
@@ -41,22 +42,7 @@ internal class JavaCliModule {
         private const val DATAHOME_SUBDIR = "briar-mailbox"
     }
 
-    @Singleton
-    @Provides
-    fun provideDatabaseConfig() = object : DatabaseConfig {
-        override fun getDatabaseDirectory(): File {
-            val dataDir = getDataDir()
-            val dbDir = File(dataDir, "db")
-            if (!dbDir.exists() && !dbDir.mkdirs()) {
-                throw IOException("dbDir could not be created: ${dbDir.absolutePath}")
-            } else if (!dbDir.isDirectory) {
-                throw IOException("dbDir is not a directory: ${dbDir.absolutePath}")
-            }
-            return dbDir
-        }
-    }
-
-    private fun getDataDir(): File {
+    private val dataDir: File by lazy {
         val dataHome = when (val custom = System.getenv("XDG_DATA_HOME").orEmpty()) {
             "" -> File(DEFAULT_DATAHOME)
             else -> File(custom)
@@ -79,7 +65,32 @@ internal class JavaCliModule {
         setPosixFilePermissions(dataDir.toPath(), perms)
 
         LOG.info { "Datadir set to: ${dataDir.absolutePath}" }
-        return dataDir
+        dataDir
+    }
+
+    @Singleton
+    @Provides
+    fun provideDatabaseConfig() = object : DatabaseConfig {
+        override fun getDatabaseDirectory(): File {
+            val dbDir = File(dataDir, "db")
+            if (!dbDir.exists() && !dbDir.mkdirs()) {
+                throw IOException("dbDir could not be created: ${dbDir.absolutePath}")
+            } else if (!dbDir.isDirectory) {
+                throw IOException("dbDir is not a directory: ${dbDir.absolutePath}")
+            }
+            return dbDir
+        }
+    }
+
+    @Singleton
+    @Provides
+    fun provideFileProvider() = object : FileProvider {
+        private val tempFilesDir = File(dataDir, "tmp").also { it.mkdirs() }
+        private val filesDir = File(dataDir, "folders").also { it.mkdirs() }
+
+        override fun getTemporaryFile(fileId: String) = File(tempFilesDir, fileId)
+        override fun getFolder(folderId: String) = File(filesDir, folderId).also { it.mkdirs() }
+        override fun getFile(folderId: String, fileId: String) = File(getFolder(folderId), fileId)
     }
 
 }
diff --git a/mailbox-core/src/main/java/org/briarproject/mailbox/core/files/FileProvider.kt b/mailbox-core/src/main/java/org/briarproject/mailbox/core/files/FileProvider.kt
new file mode 100644
index 0000000000000000000000000000000000000000..7c01845b84c65431e2073235ca54ab339680e810
--- /dev/null
+++ b/mailbox-core/src/main/java/org/briarproject/mailbox/core/files/FileProvider.kt
@@ -0,0 +1,9 @@
+package org.briarproject.mailbox.core.files
+
+import java.io.File
+
+interface FileProvider {
+    fun getTemporaryFile(fileId: String): File
+    fun getFolder(folderId: String): File
+    fun getFile(folderId: String, fileId: String): File
+}
diff --git a/mailbox-core/src/test/java/org/briarproject/mailbox/core/TestModule.kt b/mailbox-core/src/test/java/org/briarproject/mailbox/core/TestModule.kt
index b269d9dcb7d5e8e5649615da87c6a61dca813134..571adf9d7b159c8dce26956d381fa13aa62d2030 100644
--- a/mailbox-core/src/test/java/org/briarproject/mailbox/core/TestModule.kt
+++ b/mailbox-core/src/test/java/org/briarproject/mailbox/core/TestModule.kt
@@ -6,6 +6,7 @@ import dagger.hilt.InstallIn
 import dagger.hilt.components.SingletonComponent
 import org.briarproject.mailbox.core.db.DatabaseConfig
 import org.briarproject.mailbox.core.db.DatabaseModule
+import org.briarproject.mailbox.core.files.FileProvider
 import org.briarproject.mailbox.core.lifecycle.LifecycleModule
 import org.briarproject.mailbox.core.server.WebServerModule
 import org.briarproject.mailbox.core.settings.SettingsModule
@@ -35,4 +36,15 @@ internal class TestModule(private val tempDir: File) {
             return File(tempDir, "db")
         }
     }
+
+    @Singleton
+    @Provides
+    fun provideFileProvider() = object : FileProvider {
+        private val tempFilesDir = File(tempDir, "tmp").also { it.mkdirs() }
+        private val filesDir = File(tempDir, "folders")
+
+        override fun getTemporaryFile(fileId: String) = File(tempFilesDir, fileId)
+        override fun getFolder(folderId: String) = File(filesDir, folderId).also { it.mkdirs() }
+        override fun getFile(folderId: String, fileId: String) = File(getFolder(folderId), fileId)
+    }
 }