Data Layer

Room Database (:core:database)

EntityTableKeyPurpose
ThreadEntitythreadsthreadId (PK)Thread-level metadata
EmailEntityemailsid (PK)Individual email messages
ScheduledMessageEntityscheduled_messagesid (PK)Scheduled sends
PendingActionEntitypending_actionsid (PK)Optimistic write queue

Current schema version: 12 (10 migrations from v2 → v12).

Migration strategy

EmailRepository (:core:data)

This is the central nervous system:

class EmailRepository @Inject constructor(
    private val database: AppDatabase,
    private val providerFactory: (UserProfile) -> EmailProvider,
    private val accountManager: AccountManager,
    private val pendingActionDao: PendingActionDao
)

Folder queries

Use Room Flow queries with composite indices on (accountId, folder, date):

fun getInboxThreadsFlow(accountId: String): Flow<List<EmailThread>> =
    threadDao.getThreadsByFolder(accountId, inInbox = true)

Write operations

Follow this pattern:

suspend fun archiveThread(accountId: String, threadId: String) {
    // 1. Optimistic local update
    threadDao.markAsArchived(threadId, accountId)
    emailDao.markAsArchivedByThread(threadId, accountId)
    // 2. Schedule server sync
    insertPendingAction(ARCHIVE, accountId, threadId)
}

insertPendingAction creates a PendingActionEntity row. The ActionQueueManager processes it against the server.

ActionQueueManager (:app)

@Singleton
class ActionQueueManager @Inject constructor(
    private val pendingActionDao: PendingActionDao,
    private val accountManager: AccountManager,
    private val providerFactory: (UserProfile) -> EmailProvider
) {
    fun start()  // called from MonoMailApp.onCreate()
    fun stop()   // on app shutdown
}

SettingsDataStore

All settings in one AppSettings data class, persisted as DataStore Preferences:

data class AppSettings(
    val themeMode: ThemeMode = ThemeMode.SYSTEM,
    val fontScale: FontScale = FontScale.DEFAULT,
    val showDividers: Boolean = false,
    val compactList: Boolean = false,
    val showSnippet: Boolean = true,
    val swipeLeftAction: SwipeAction = SwipeAction.READ_UNREAD,
    val swipeRightAction: SwipeAction = SwipeAction.ARCHIVE,
    val confirmBeforeSending: Boolean = false,
    val defaultReply: DefaultReply = DefaultReply.REPLY,
    val emailNotifications: Boolean = true,
    val syncFrequency: SyncFrequency = SyncFrequency.MIN_15,
    val unifiedInbox: Boolean = false,
    val smartGrouping: Boolean = true,
    val groupRecentOnly: Boolean = true,
    val organizeByThread: Boolean = true,
    val loadRemoteImages: Boolean = true,
    val renderMarkdown: Boolean = false,
    val navScale: Float = 1.0f,
    val undoSendEnabled: Boolean = true,
    val undoSendWindow: UndoSendWindow = UndoSendWindow.SEC_10,
    val dockConfig: DockConfig = DockConfig(),
    val hasSeenWelcomePrompt: Boolean = false,
    val hasDismissedUpdatePrompt: Boolean = false,
    val lastKnownVersion: String = ""
)

Also stores: emailTemplates (list as JSON), accountNotificationSettings (per account).

← Multi-Module UI Layer →