warmbox/prisma/schema.prisma
Jonny Nguyen b300293a72 Enhance Inbox model and related services for improved error handling and resilience
Add a new field `consecutiveFailures` to the Inbox model to track consecutive errors. Update the inbox guard logic to mark mailboxes as error after exceeding a failure threshold. Implement retry logic in email and IMAP services to handle transient connection issues. Adjust job processing to reclaim stale send jobs and improve overall reliability of email dispatching. Update routes to initialize `consecutiveFailures` on inbox creation and reset it when the inbox status changes to active.
2026-07-03 15:02:41 -07:00

267 lines
7.2 KiB
Text

generator client {
provider = "prisma-client"
output = "../apps/api/src/generated/prisma"
}
datasource db {
provider = "sqlite"
}
enum InboxStatus {
active
paused
error
}
enum WarmupStatus {
sent
rescued_from_spam
engaged_no_reply
replied
complete
}
enum MailboxRole {
unassigned
primary
satellite
}
enum MailboxProvider {
google_workspace
gmail
yahoo
outlook
custom
}
enum MailboxAuthType {
password
microsoft_oauth
}
enum CampaignRecipe {
grow
flat
randomized
custom
}
enum CampaignStatus {
draft
active
paused
maintenance
completed
failed
}
model Workspace {
id String @id @default(cuid())
name String @unique
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
inboxes Inbox[]
campaigns WarmupCampaign[]
}
enum SendJobStatus {
pending
processing
completed
failed
}
model SendJob {
id String @id @default(cuid())
campaignId String
scheduledAt DateTime
status SendJobStatus @default(pending)
idempotencyKey String @unique
processedAt DateTime?
errorMessage String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
campaign WarmupCampaign @relation(fields: [campaignId], references: [id], onDelete: Cascade)
@@index([campaignId, status, scheduledAt])
}
model JobRunLog {
id String @id @default(cuid())
jobName String
ranAt DateTime @default(now())
success Boolean @default(true)
message String?
@@index([jobName, ranAt])
}
model Inbox {
id String @id @default(cuid())
workspaceId String
email String
smtpHost String
smtpPort Int
imapHost String
imapPort Int
username String
password String
authType MailboxAuthType @default(password)
oauthRefreshToken String?
role MailboxRole @default(unassigned)
provider MailboxProvider @default(custom)
status InboxStatus @default(active)
dailyLimit Int @default(40)
currentRamp Int @default(5)
industry String @default("SaaS")
senderName String?
companyName String?
lastError String?
errorAt DateTime?
consecutiveFailures Int @default(0)
spamFolderPath String?
lastSpamRescuedAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
sentLogs WarmupLog[] @relation("Sender")
receivedLogs WarmupLog[] @relation("Receiver")
primaryCampaigns WarmupCampaign[] @relation("PrimaryMailbox")
satelliteCampaigns CampaignSatellite[]
@@unique([workspaceId, email])
@@index([status])
@@index([workspaceId, role])
}
model WarmupCampaign {
id String @id @default(cuid())
workspaceId String
primaryMailboxId String
name String
recipe CampaignRecipe @default(grow)
status CampaignStatus @default(draft)
durationDays Int @default(30)
minEmailsPerDay Int @default(1)
maxEmailsPerDay Int @default(40)
replyRatePercent Int @default(30)
maintenanceVolumePercent Int @default(20)
timezone String @default("America/Vancouver")
sendWindowStart String @default("08:00")
sendWindowEnd String @default("18:00")
customSchedule String?
startedAt DateTime?
endsAt DateTime?
currentDay Int @default(0)
pausedFromStatus CampaignStatus?
lastSendAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
primaryMailbox Inbox @relation("PrimaryMailbox", fields: [primaryMailboxId], references: [id], onDelete: Cascade)
satellites CampaignSatellite[]
dailySchedules DailySchedule[]
dailyStats DailyStat[]
sendJobs SendJob[]
warmupLogs WarmupLog[]
@@index([workspaceId, status])
@@index([primaryMailboxId, status])
}
model CampaignSatellite {
id String @id @default(cuid())
campaignId String
satelliteMailboxId String
weight Int @default(1)
active Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
campaign WarmupCampaign @relation(fields: [campaignId], references: [id], onDelete: Cascade)
satelliteMailbox Inbox @relation(fields: [satelliteMailboxId], references: [id], onDelete: Cascade)
@@unique([campaignId, satelliteMailboxId])
@@index([satelliteMailboxId])
}
model DailySchedule {
id String @id @default(cuid())
campaignId String
dayIndex Int
date DateTime?
plannedSends Int
plannedReplies Int
actualSends Int @default(0)
actualReplies Int @default(0)
spamDetected Int @default(0)
spamRescued Int @default(0)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
campaign WarmupCampaign @relation(fields: [campaignId], references: [id], onDelete: Cascade)
@@unique([campaignId, dayIndex])
@@index([campaignId])
}
model DailyStat {
id String @id @default(cuid())
campaignId String
date DateTime
dayIndex Int
plannedSends Int
plannedReplies Int
actualSends Int @default(0)
actualReplies Int @default(0)
spamDetected Int @default(0)
spamRescued Int @default(0)
replyRateActual Float?
inboxPlacementRate Float?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
campaign WarmupCampaign @relation(fields: [campaignId], references: [id], onDelete: Cascade)
@@unique([campaignId, date])
@@index([campaignId])
}
model WarmupLog {
id String @id @default(cuid())
campaignId String?
senderId String
receiverId String
messageId String?
inReplyTo String?
subject String
body String
status WarmupStatus @default(sent)
placement String?
rescuedFromSpam Boolean @default(false)
validationResult String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
campaign WarmupCampaign? @relation(fields: [campaignId], references: [id], onDelete: SetNull)
sender Inbox @relation("Sender", fields: [senderId], references: [id], onDelete: Cascade)
receiver Inbox @relation("Receiver", fields: [receiverId], references: [id], onDelete: Cascade)
@@index([senderId, status])
@@index([receiverId, status])
@@index([messageId])
@@index([campaignId])
}
model Setting {
id String @id @default(cuid())
key String @unique
value String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}