diff --git a/mailbox-cli/src/main/java/org/briarproject/mailbox/cli/Main.kt b/mailbox-cli/src/main/java/org/briarproject/mailbox/cli/Main.kt
index b0d58f0519a53c511c11c0527702d995d7a52fc6..da436f36811783ebe5a1f13d05d44fd73a554bfb 100644
--- a/mailbox-cli/src/main/java/org/briarproject/mailbox/cli/Main.kt
+++ b/mailbox-cli/src/main/java/org/briarproject/mailbox/cli/Main.kt
@@ -26,7 +26,7 @@ import com.github.ajalt.clikt.parameters.options.counted
 import com.github.ajalt.clikt.parameters.options.flag
 import com.github.ajalt.clikt.parameters.options.option
 import org.briarproject.mailbox.core.system.InvalidIdException
-import org.briarproject.mailbox.lib.ProductionMailbox
+import org.briarproject.mailbox.lib.Mailbox
 import org.slf4j.LoggerFactory.getLogger
 
 class Main : CliktCommand(
@@ -71,8 +71,7 @@ class Main : CliktCommand(
         getLogger(this.javaClass).debug("Hello Mailbox")
         println("Hello Mailbox")
 
-        val mailbox = ProductionMailbox()
-        mailbox.init()
+        val mailbox = Mailbox()
 
         if (wipe) {
             mailbox.wipeFilesOnly()
@@ -82,7 +81,7 @@ class Main : CliktCommand(
         startLifecycle(mailbox)
     }
 
-    private fun startLifecycle(mailbox: ProductionMailbox) {
+    private fun startLifecycle(mailbox: Mailbox) {
         Runtime.getRuntime().addShutdownHook(
             Thread {
                 mailbox.stopLifecycle(false)
diff --git a/mailbox-lib/src/main/java/org/briarproject/mailbox/core/JavaLibEagerSingletons.kt b/mailbox-lib/src/main/java/org/briarproject/mailbox/core/MailboxLibEagerSingletons.kt
similarity index 94%
rename from mailbox-lib/src/main/java/org/briarproject/mailbox/core/JavaLibEagerSingletons.kt
rename to mailbox-lib/src/main/java/org/briarproject/mailbox/core/MailboxLibEagerSingletons.kt
index a105dc2bc6ac7826801c36c8f1bfcd1eb01954c1..079af827749d824257f9f92d98a6b58843f57e8c 100644
--- a/mailbox-lib/src/main/java/org/briarproject/mailbox/core/JavaLibEagerSingletons.kt
+++ b/mailbox-lib/src/main/java/org/briarproject/mailbox/core/MailboxLibEagerSingletons.kt
@@ -24,7 +24,7 @@ import org.briarproject.mailbox.core.tor.TorPlugin
 import javax.inject.Inject
 
 @Suppress("unused")
-internal class JavaLibEagerSingletons @Inject constructor(
+internal class MailboxLibEagerSingletons @Inject constructor(
     val taskScheduler: TaskScheduler,
     val javaTorPlugin: TorPlugin,
 )
diff --git a/mailbox-lib/src/main/java/org/briarproject/mailbox/lib/AbstractMailbox.kt b/mailbox-lib/src/main/java/org/briarproject/mailbox/lib/AbstractMailbox.kt
new file mode 100644
index 0000000000000000000000000000000000000000..c07974ee1378df1ca6c30b1e0d4196edb94c5bc7
--- /dev/null
+++ b/mailbox-lib/src/main/java/org/briarproject/mailbox/lib/AbstractMailbox.kt
@@ -0,0 +1,135 @@
+/*
+ *     Briar Mailbox
+ *     Copyright (C) 2021-2022  The Briar Project
+ *
+ *     This program is free software: you can redistribute it and/or modify
+ *     it under the terms of the GNU Affero General Public License as
+ *     published by the Free Software Foundation, either version 3 of the
+ *     License, or (at your option) any later version.
+ *
+ *     This program is distributed in the hope that it will be useful,
+ *     but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *     GNU Affero General Public License for more details.
+ *
+ *     You should have received a copy of the GNU Affero General Public License
+ *     along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+package org.briarproject.mailbox.lib
+
+import com.google.zxing.common.BitMatrix
+import kotlinx.coroutines.flow.takeWhile
+import kotlinx.coroutines.runBlocking
+import org.briarproject.mailbox.core.CoreEagerSingletons
+import org.briarproject.mailbox.core.MailboxLibEagerSingletons
+import org.briarproject.mailbox.core.db.TransactionManager
+import org.briarproject.mailbox.core.lifecycle.LifecycleManager
+import org.briarproject.mailbox.core.setup.QrCodeEncoder
+import org.briarproject.mailbox.core.setup.SetupManager
+import org.briarproject.mailbox.core.setup.WipeManager
+import org.briarproject.mailbox.core.system.System
+import org.briarproject.mailbox.core.tor.TorPlugin
+import org.briarproject.mailbox.core.tor.TorState
+import org.briarproject.mailbox.core.util.LogUtils.info
+import org.slf4j.Logger
+import org.slf4j.LoggerFactory.getLogger
+import java.io.File
+import javax.inject.Inject
+
+abstract class AbstractMailbox(protected val customDataDir: File? = null) {
+
+    companion object {
+        val LOG: Logger = getLogger(AbstractMailbox::class.java)
+    }
+
+    @Inject
+    internal lateinit var coreEagerSingletons: CoreEagerSingletons
+
+    @Inject
+    internal lateinit var mailboxLibEagerSingletons: MailboxLibEagerSingletons
+
+    @Inject
+    internal lateinit var lifecycleManager: LifecycleManager
+
+    @Inject
+    internal lateinit var db: TransactionManager
+
+    @Inject
+    internal lateinit var setupManager: SetupManager
+
+    @Inject
+    internal lateinit var wipeManager: WipeManager
+
+    @Inject
+    internal lateinit var torPlugin: TorPlugin
+
+    @Inject
+    internal lateinit var qrCodeEncoder: QrCodeEncoder
+
+    @Inject
+    internal lateinit var system: System
+
+    fun wipeFilesOnly() {
+        wipeManager.wipeFilesOnly()
+        LOG.info { "Mailbox wiped successfully \\o/" }
+    }
+
+    fun startLifecycle() {
+        LOG.info { "Starting lifecycle" }
+        lifecycleManager.startServices()
+        LOG.info { "Waiting for startup" }
+        lifecycleManager.waitForStartup()
+        LOG.info { "Startup finished" }
+    }
+
+    fun stopLifecycle(exitAfterStopping: Boolean) {
+        LOG.info { "Stopping lifecycle" }
+        lifecycleManager.stopServices(exitAfterStopping)
+        LOG.info { "Waiting for shutdown" }
+        lifecycleManager.waitForShutdown()
+        LOG.info { "Shutdown finished" }
+    }
+
+    fun setSetupToken(token: String) {
+        setupManager.setToken(token, null)
+    }
+
+    fun setOwnerToken(token: String) {
+        setupManager.setToken(null, token)
+    }
+
+    fun waitForTorPublished() {
+        LOG.info { "Waiting for Tor to publish hidden service" }
+        runBlocking {
+            // wait until Tor becomes active and published the onion service
+            torPlugin.state.takeWhile { state ->
+                state != TorState.Published
+            }.collect { }
+        }
+        LOG.info { "Hidden service published" }
+    }
+
+    fun waitForShutdown() {
+        lifecycleManager.waitForShutdown()
+    }
+
+    fun getSetupToken(): String? {
+        return db.read { txn ->
+            setupManager.getSetupToken(txn)
+        }
+    }
+
+    fun getOwnerToken(): String? {
+        return db.read { txn ->
+            setupManager.getOwnerToken(txn)
+        }
+    }
+
+    fun getQrCode(): BitMatrix? {
+        return qrCodeEncoder.getQrCodeBitMatrix()
+    }
+
+    fun getSystem(): System = system
+}
diff --git a/mailbox-lib/src/main/java/org/briarproject/mailbox/lib/Mailbox.kt b/mailbox-lib/src/main/java/org/briarproject/mailbox/lib/Mailbox.kt
index 32a909e3080231336b1da9618e32e8b07016048c..115bb0717779cf5e3447358d70f9587b68b100ed 100644
--- a/mailbox-lib/src/main/java/org/briarproject/mailbox/lib/Mailbox.kt
+++ b/mailbox-lib/src/main/java/org/briarproject/mailbox/lib/Mailbox.kt
@@ -1,137 +1,14 @@
-/*
- *     Briar Mailbox
- *     Copyright (C) 2021-2022  The Briar Project
- *
- *     This program is free software: you can redistribute it and/or modify
- *     it under the terms of the GNU Affero General Public License as
- *     published by the Free Software Foundation, either version 3 of the
- *     License, or (at your option) any later version.
- *
- *     This program is distributed in the hope that it will be useful,
- *     but WITHOUT ANY WARRANTY; without even the implied warranty of
- *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *     GNU Affero General Public License for more details.
- *
- *     You should have received a copy of the GNU Affero General Public License
- *     along with this program.  If not, see <https://www.gnu.org/licenses/>.
- *
- */
-
 package org.briarproject.mailbox.lib
 
-import com.google.zxing.common.BitMatrix
-import kotlinx.coroutines.flow.takeWhile
-import kotlinx.coroutines.runBlocking
-import org.briarproject.mailbox.core.CoreEagerSingletons
-import org.briarproject.mailbox.core.JavaLibEagerSingletons
-import org.briarproject.mailbox.core.db.TransactionManager
-import org.briarproject.mailbox.core.lifecycle.LifecycleManager
-import org.briarproject.mailbox.core.setup.QrCodeEncoder
-import org.briarproject.mailbox.core.setup.SetupManager
-import org.briarproject.mailbox.core.setup.WipeManager
-import org.briarproject.mailbox.core.system.System
-import org.briarproject.mailbox.core.tor.TorPlugin
-import org.briarproject.mailbox.core.tor.TorState
 import org.briarproject.mailbox.core.util.LogUtils.info
-import org.slf4j.Logger
-import org.slf4j.LoggerFactory.getLogger
 import java.io.File
-import javax.inject.Inject
-
-abstract class Mailbox(protected val customDataDir: File? = null) {
-
-    companion object {
-        val LOG: Logger = getLogger(Mailbox::class.java)
-    }
-
-    @Inject
-    internal lateinit var coreEagerSingletons: CoreEagerSingletons
-
-    @Inject
-    internal lateinit var javaLibEagerSingletons: JavaLibEagerSingletons
-
-    @Inject
-    internal lateinit var lifecycleManager: LifecycleManager
-
-    @Inject
-    internal lateinit var db: TransactionManager
-
-    @Inject
-    internal lateinit var setupManager: SetupManager
-
-    @Inject
-    internal lateinit var wipeManager: WipeManager
-
-    @Inject
-    internal lateinit var torPlugin: TorPlugin
-
-    @Inject
-    internal lateinit var qrCodeEncoder: QrCodeEncoder
-
-    @Inject
-    internal lateinit var system: System
-
-    abstract fun init()
-
-    fun wipeFilesOnly() {
-        wipeManager.wipeFilesOnly()
-        LOG.info { "Mailbox wiped successfully \\o/" }
-    }
 
-    fun startLifecycle() {
-        LOG.info { "Starting lifecycle" }
-        lifecycleManager.startServices()
-        LOG.info { "Waiting for startup" }
-        lifecycleManager.waitForStartup()
-        LOG.info { "Startup finished" }
-    }
-
-    fun stopLifecycle(exitAfterStopping: Boolean) {
-        LOG.info { "Stopping lifecycle" }
-        lifecycleManager.stopServices(exitAfterStopping)
-        LOG.info { "Waiting for shutdown" }
-        lifecycleManager.waitForShutdown()
-        LOG.info { "Shutdown finished" }
-    }
-
-    fun setSetupToken(token: String) {
-        setupManager.setToken(token, null)
-    }
-
-    fun setOwnerToken(token: String) {
-        setupManager.setToken(null, token)
-    }
-
-    fun waitForTorPublished() {
-        LOG.info { "Waiting for Tor to publish hidden service" }
-        runBlocking {
-            // wait until Tor becomes active and published the onion service
-            torPlugin.state.takeWhile { state ->
-                state != TorState.Published
-            }.collect { }
-        }
-        LOG.info { "Hidden service published" }
-    }
-
-    fun waitForShutdown() {
-        lifecycleManager.waitForShutdown()
-    }
-
-    fun getSetupToken(): String? {
-        return db.read { txn ->
-            setupManager.getSetupToken(txn)
-        }
-    }
-
-    fun getOwnerToken(): String? {
-        return db.read { txn ->
-            setupManager.getOwnerToken(txn)
-        }
-    }
+class Mailbox(mailboxDir: File? = null) : AbstractMailbox(mailboxDir) {
 
-    fun getQrCode(): BitMatrix? {
-        return qrCodeEncoder.getQrCodeBitMatrix()
+    init {
+        LOG.info { "Hello Mailbox" }
+        val mailboxLibComponent = DaggerMailboxLibComponent.builder()
+            .mailboxLibModule(MailboxLibModule(customDataDir)).build()
+        mailboxLibComponent.inject(this)
     }
-
-    fun getSystem(): System = system
 }
diff --git a/mailbox-lib/src/main/java/org/briarproject/mailbox/lib/MailboxLibProductionComponent.kt b/mailbox-lib/src/main/java/org/briarproject/mailbox/lib/MailboxLibComponent.kt
similarity index 92%
rename from mailbox-lib/src/main/java/org/briarproject/mailbox/lib/MailboxLibProductionComponent.kt
rename to mailbox-lib/src/main/java/org/briarproject/mailbox/lib/MailboxLibComponent.kt
index 7479fe68926df2e7bca83c88f822eeedc7ef259d..03ef11d02906328a201284370f86bea3c6988321 100644
--- a/mailbox-lib/src/main/java/org/briarproject/mailbox/lib/MailboxLibProductionComponent.kt
+++ b/mailbox-lib/src/main/java/org/briarproject/mailbox/lib/MailboxLibComponent.kt
@@ -30,6 +30,6 @@ import javax.inject.Singleton
         ProductionSystemModule::class,
     ]
 )
-interface MailboxLibProductionComponent {
-    fun inject(mailbox: ProductionMailbox)
+interface MailboxLibComponent {
+    fun inject(mailbox: Mailbox)
 }
diff --git a/mailbox-lib/src/main/java/org/briarproject/mailbox/lib/ProductionMailbox.kt b/mailbox-lib/src/main/java/org/briarproject/mailbox/lib/ProductionMailbox.kt
deleted file mode 100644
index 7e15dc54023b4a5ea2080f4c7878c1ae66df1934..0000000000000000000000000000000000000000
--- a/mailbox-lib/src/main/java/org/briarproject/mailbox/lib/ProductionMailbox.kt
+++ /dev/null
@@ -1,15 +0,0 @@
-package org.briarproject.mailbox.lib
-
-import org.briarproject.mailbox.core.util.LogUtils.info
-import java.io.File
-
-class ProductionMailbox(mailboxDir: File? = null) : Mailbox(mailboxDir) {
-
-    override fun init() {
-        LOG.info { "Hello Mailbox" }
-        val javaLibComponent = DaggerMailboxLibProductionComponent.builder()
-            .mailboxLibModule(MailboxLibModule(customDataDir)).build()
-        javaLibComponent.inject(this)
-    }
-
-}
diff --git a/mailbox-lib/src/main/java/org/briarproject/mailbox/lib/TestMailbox.kt b/mailbox-lib/src/main/java/org/briarproject/mailbox/lib/TestMailbox.kt
index fc2a113473c436f494cbd88081fe18a5f68dc6e3..3d1b0784c30237bb57efb24d3d6b06b80fa5f563 100644
--- a/mailbox-lib/src/main/java/org/briarproject/mailbox/lib/TestMailbox.kt
+++ b/mailbox-lib/src/main/java/org/briarproject/mailbox/lib/TestMailbox.kt
@@ -5,13 +5,13 @@ import org.briarproject.mailbox.system.TestSystem
 import java.io.File
 import javax.inject.Inject
 
-class TestMailbox(mailboxDir: File? = null) : Mailbox(mailboxDir) {
+class TestMailbox(mailboxDir: File? = null) : AbstractMailbox(mailboxDir) {
 
-    override fun init() {
+    init {
         LOG.info { "Hello Mailbox" }
-        val javaLibComponent = DaggerMailboxLibTestComponent.builder()
+        val mailboxLibComponent = DaggerMailboxLibTestComponent.builder()
             .mailboxLibModule(MailboxLibModule(customDataDir)).build()
-        javaLibComponent.inject(this)
+        mailboxLibComponent.inject(this)
     }
 
     @Inject
diff --git a/mailbox-lib/src/test/java/org/briarproject/mailbox/lib/MailboxLibTest.kt b/mailbox-lib/src/test/java/org/briarproject/mailbox/lib/MailboxLibTest.kt
index dfe8d73dc8f890af456482405700740f436d93ad..f602617ee2f1fc2d0d2a6b8dc2d9b4b9e37d5cc7 100644
--- a/mailbox-lib/src/test/java/org/briarproject/mailbox/lib/MailboxLibTest.kt
+++ b/mailbox-lib/src/test/java/org/briarproject/mailbox/lib/MailboxLibTest.kt
@@ -33,7 +33,6 @@ class MailboxLibTest {
     @Test
     fun testStartStopMailbox() {
         val mailbox = TestMailbox(mailboxDataDirectory)
-        mailbox.init()
         mailbox.startLifecycle()
         mailbox.waitForTorPublished()
         mailbox.stopLifecycle(true)