generator client { provider = "prisma-client-js" engineType = "binary" } datasource db { provider = "postgresql" } enum MailProvider { GMAIL GMX WEBDE } enum UserRole { USER ADMIN } enum JobStatus { QUEUED RUNNING SUCCEEDED FAILED CANCELED } enum RuleActionType { MOVE DELETE ARCHIVE LABEL MARK_READ MARK_UNREAD } enum RuleConditionType { HEADER HEADER_MISSING SUBJECT FROM LIST_UNSUBSCRIBE LIST_ID UNSUBSCRIBE_STATUS SCORE } enum ExportStatus { QUEUED RUNNING DONE FAILED } model Tenant { id String @id @default(cuid()) name String isActive Boolean @default(true) exportJobs ExportJob[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt users User[] mailboxAccounts MailboxAccount[] rules Rule[] jobs CleanupJob[] metric TenantMetric? providerMetrics TenantProviderMetric[] } model TenantMetric { id String @id @default(cuid()) tenantId String @unique avgProcessingRate Float? sampleCount Int @default(0) updatedAt DateTime @updatedAt tenant Tenant @relation(fields: [tenantId], references: [id]) } model TenantProviderMetric { id String @id @default(cuid()) tenantId String provider MailProvider avgListingRate Float? avgProcessingRate Float? avgUnsubscribeRate Float? avgRoutingRate Float? avgListingSecondsPerMessage Float? avgProcessingSecondsPerMessage Float? avgUnsubscribeSecondsPerMessage Float? avgRoutingSecondsPerMessage Float? listingSampleCount Int @default(0) processingSampleCount Int @default(0) unsubscribeSampleCount Int @default(0) routingSampleCount Int @default(0) updatedAt DateTime @updatedAt tenant Tenant @relation(fields: [tenantId], references: [id]) @@unique([tenantId, provider]) @@index([tenantId]) } model ExportJob { id String @id @default(cuid()) tenantId String status ExportStatus @default(QUEUED) format String scope String progress Int @default(0) filePath String? error String? expiresAt DateTime? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt tenant Tenant @relation(fields: [tenantId], references: [id]) @@index([tenantId]) } model User { id String @id @default(cuid()) tenantId String email String @unique password String role UserRole @default(USER) isActive Boolean @default(true) passwordResetRequired Boolean @default(false) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt tenant Tenant @relation(fields: [tenantId], references: [id]) } model MailboxAccount { id String @id @default(cuid()) tenantId String email String provider MailProvider isActive Boolean @default(true) imapHost String imapPort Int imapTLS Boolean smtpHost String? smtpPort Int? smtpTLS Boolean? oauthToken String? oauthRefreshToken String? oauthAccessToken String? oauthExpiresAt DateTime? providerUserId String? oauthLastCheckedAt DateTime? oauthLastErrorCode String? appPassword String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt tenant Tenant @relation(fields: [tenantId], references: [id]) folders MailboxFolder[] jobs CleanupJob[] candidates CleanupJobCandidate[] @@index([tenantId]) } model MailboxFolder { id String @id @default(cuid()) mailboxAccountId String name String remoteId String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt mailboxAccount MailboxAccount @relation(fields: [mailboxAccountId], references: [id]) mailItems MailItem[] @@index([mailboxAccountId]) } model MailItem { id String @id @default(cuid()) folderId String messageId String subject String? from String? receivedAt DateTime? listId String? listUnsubscribe String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt folder MailboxFolder @relation(fields: [folderId], references: [id]) @@index([folderId]) @@index([messageId]) } model Rule { id String @id @default(cuid()) tenantId String name String enabled Boolean @default(true) matchMode RuleMatchMode @default(ALL) position Int @default(0) stopOnMatch Boolean @default(false) phase RulePhase @default(POST_UNSUBSCRIBE) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt tenant Tenant @relation(fields: [tenantId], references: [id]) conditions RuleCondition[] actions RuleAction[] @@index([tenantId]) @@index([tenantId, position]) } enum RuleMatchMode { ALL ANY } enum RulePhase { PRE_UNSUBSCRIBE POST_UNSUBSCRIBE } model RuleCondition { id String @id @default(cuid()) ruleId String type RuleConditionType value String rule Rule @relation(fields: [ruleId], references: [id]) @@index([ruleId]) } model RuleAction { id String @id @default(cuid()) ruleId String type RuleActionType target String? rule Rule @relation(fields: [ruleId], references: [id]) @@index([ruleId]) } model CleanupJob { id String @id @default(cuid()) tenantId String mailboxAccountId String status JobStatus @default(QUEUED) dryRun Boolean @default(true) unsubscribeEnabled Boolean @default(true) routingEnabled Boolean @default(true) checkpoint Json? checkpointUpdatedAt DateTime? processedMessages Int? totalMessages Int? listingSeconds Int? processingSeconds Int? unsubscribeSeconds Int? routingSeconds Int? unsubscribeAttempts Int? actionAttempts Int? startedAt DateTime? finishedAt DateTime? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt tenant Tenant @relation(fields: [tenantId], references: [id]) mailboxAccount MailboxAccount @relation(fields: [mailboxAccountId], references: [id]) unsubscribeAttemptItems UnsubscribeAttempt[] events CleanupJobEvent[] candidates CleanupJobCandidate[] @@index([tenantId]) @@index([mailboxAccountId]) } model CleanupJobCandidate { id String @id @default(cuid()) jobId String mailboxAccountId String provider MailProvider externalId String subject String? from String? fromDomain String? receivedAt DateTime? listId String? listUnsubscribe String? listUnsubscribePost String? score Int signals Json actions Json? unsubscribeStatus String? unsubscribeMessage String? unsubscribeTarget String? unsubscribeDetails Json? reviewed Boolean @default(false) createdAt DateTime @default(now()) job CleanupJob @relation(fields: [jobId], references: [id]) mailboxAccount MailboxAccount @relation(fields: [mailboxAccountId], references: [id]) @@unique([jobId, externalId]) @@index([jobId]) @@index([jobId, fromDomain]) } model UnsubscribeAttempt { id String @id @default(cuid()) jobId String mailItemId String? dedupeKey String? method String target String status String createdAt DateTime @default(now()) job CleanupJob @relation(fields: [jobId], references: [id]) @@index([jobId]) @@unique([jobId, dedupeKey]) } model UnsubscribeHistory { id String @id @default(cuid()) tenantId String dedupeKey String target String status String createdAt DateTime @default(now()) @@unique([tenantId, dedupeKey]) @@index([tenantId]) } model CleanupJobEvent { id String @id @default(cuid()) jobId String level String message String progress Int? createdAt DateTime @default(now()) job CleanupJob @relation(fields: [jobId], references: [id]) @@index([jobId]) } model AppSetting { id String @id @default(cuid()) key String @unique value String updatedAt DateTime @updatedAt }