463 lines
16 KiB
Plaintext
463 lines
16 KiB
Plaintext
// VA-P28c: tenant-less email workspace for HiveGrid's own sales team.
|
|
//
|
|
// Every row is owned by an active AdminUser. Worker-readable rows repeat the
|
|
// owner id deliberately so Postgres RLS can enforce the boundary without a
|
|
// tenant context or an unreviewable join chain.
|
|
|
|
enum AdminMailboxStatus {
|
|
connecting
|
|
active
|
|
degraded
|
|
revoked
|
|
}
|
|
|
|
enum AdminMailboxSyncStatus {
|
|
pending
|
|
running
|
|
complete
|
|
failed
|
|
}
|
|
|
|
enum AdminEmailDirection {
|
|
inbound
|
|
outbound
|
|
}
|
|
|
|
enum AdminEmailMatchStatus {
|
|
matched
|
|
ambiguous
|
|
unmatched
|
|
manual
|
|
}
|
|
|
|
enum AdminEmailEventType {
|
|
queued
|
|
sent
|
|
delivered
|
|
bounced
|
|
opened
|
|
clicked
|
|
replied
|
|
}
|
|
|
|
enum AdminScheduledEmailStatus {
|
|
draft
|
|
blocked
|
|
queued
|
|
sending
|
|
sent
|
|
cancelled
|
|
failed
|
|
}
|
|
|
|
enum AdminNoReplyReminderStatus {
|
|
pending
|
|
fired
|
|
dismissed
|
|
cancelled
|
|
}
|
|
|
|
enum AdminBulkEmailRunStatus {
|
|
draft
|
|
blocked
|
|
queued
|
|
running
|
|
complete
|
|
cancelled
|
|
failed
|
|
}
|
|
|
|
enum AdminSendingDomainStatus {
|
|
pending
|
|
verified
|
|
degraded
|
|
revoked
|
|
}
|
|
|
|
model AdminMailbox {
|
|
id String @id @default(cuid())
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
archivedAt DateTime?
|
|
adminUserId String
|
|
connectionId String @unique
|
|
emailAddress String
|
|
normalizedEmail String
|
|
displayName String?
|
|
providerAccountId String
|
|
status AdminMailboxStatus @default(connecting)
|
|
lastSyncAt DateTime?
|
|
lastRecoveryPollAt DateTime?
|
|
webhookExpiresAt DateTime?
|
|
lastErrorMessageKey String?
|
|
|
|
adminUser AdminUser @relation(fields: [adminUserId], references: [id], onDelete: Restrict)
|
|
connection AdminDataConnection @relation(fields: [connectionId], references: [id], onDelete: Restrict)
|
|
syncCursors AdminMailboxSyncCursor[]
|
|
webhookEvents AdminMailboxWebhookEvent[]
|
|
threads AdminEmailThread[]
|
|
messages AdminEmailMessage[]
|
|
scheduled AdminScheduledEmail[]
|
|
bulkRuns AdminBulkEmailRun[]
|
|
bccAddresses AdminBccAddress[]
|
|
|
|
@@unique([adminUserId, normalizedEmail], name: "admin_mailbox_owner_email_key")
|
|
@@index([adminUserId, status, archivedAt], name: "admin_mailbox_owner_status_idx")
|
|
@@index([status, lastSyncAt], name: "admin_mailbox_recovery_poll_idx")
|
|
}
|
|
|
|
model AdminMailboxSyncCursor {
|
|
id String @id @default(cuid())
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
adminUserId String
|
|
mailboxId String
|
|
externalFolderId String
|
|
cursorToken String?
|
|
syncStatus AdminMailboxSyncStatus @default(pending)
|
|
recoveryGeneration Int @default(0)
|
|
lastAttemptAt DateTime?
|
|
lastSuccessAt DateTime?
|
|
lastErrorMessageKey String?
|
|
|
|
mailbox AdminMailbox @relation(fields: [mailboxId], references: [id], onDelete: Cascade)
|
|
|
|
@@unique([mailboxId, externalFolderId], name: "admin_mailbox_cursor_folder_key")
|
|
@@index([adminUserId, syncStatus, lastAttemptAt], name: "admin_mailbox_cursor_owner_status_idx")
|
|
}
|
|
|
|
model AdminMailboxWebhookEvent {
|
|
id String @id @default(cuid())
|
|
createdAt DateTime @default(now())
|
|
processedAt DateTime?
|
|
failedAt DateTime?
|
|
adminUserId String
|
|
mailboxId String
|
|
providerEventId String
|
|
idempotencyKey String @unique
|
|
payload Json
|
|
lastErrorMessageKey String?
|
|
|
|
mailbox AdminMailbox @relation(fields: [mailboxId], references: [id], onDelete: Cascade)
|
|
|
|
@@unique([mailboxId, providerEventId], name: "admin_mailbox_webhook_provider_event_key")
|
|
@@index([adminUserId, processedAt, createdAt], name: "admin_mailbox_webhook_pending_idx")
|
|
}
|
|
|
|
model AdminEmailThread {
|
|
id String @id @default(cuid())
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
archivedAt DateTime?
|
|
adminUserId String
|
|
mailboxId String
|
|
providerThreadId String
|
|
normalizedSubject String?
|
|
latestMessageAt DateTime
|
|
messageCount Int @default(0)
|
|
unreadCount Int @default(0)
|
|
|
|
mailbox AdminMailbox @relation(fields: [mailboxId], references: [id], onDelete: Cascade)
|
|
messages AdminEmailMessage[]
|
|
scheduled AdminScheduledEmail[]
|
|
|
|
@@unique([mailboxId, providerThreadId], name: "admin_email_thread_provider_key")
|
|
@@index([adminUserId, latestMessageAt(sort: Desc)], name: "admin_email_thread_owner_latest_idx")
|
|
}
|
|
|
|
model AdminEmailMessage {
|
|
id String @id @default(cuid())
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
adminUserId String
|
|
mailboxId String
|
|
threadId String
|
|
providerMessageId String
|
|
internetMessageId String?
|
|
direction AdminEmailDirection
|
|
fromAddress String
|
|
subject String?
|
|
snippet String?
|
|
bodyHtml String?
|
|
bodyText String?
|
|
sentAt DateTime
|
|
receivedAt DateTime?
|
|
isRead Boolean @default(false)
|
|
isNewsletter Boolean @default(false)
|
|
providerLabels String[] @default([])
|
|
matchStatus AdminEmailMatchStatus @default(unmatched)
|
|
matchedContactId String?
|
|
matchedLeadId String?
|
|
matchedDealId String?
|
|
matchedAt DateTime?
|
|
matchedByAdminUserId String?
|
|
matchingReason String?
|
|
matchingCandidates Json?
|
|
|
|
mailbox AdminMailbox @relation(fields: [mailboxId], references: [id], onDelete: Cascade)
|
|
thread AdminEmailThread @relation(fields: [threadId], references: [id], onDelete: Cascade)
|
|
matchedContact AdminContact? @relation(fields: [matchedContactId], references: [id], onDelete: SetNull)
|
|
matchedLead AdminLead? @relation(fields: [matchedLeadId], references: [id], onDelete: SetNull)
|
|
matchedDeal AdminDeal? @relation(fields: [matchedDealId], references: [id], onDelete: SetNull)
|
|
participants AdminEmailParticipant[]
|
|
attachments AdminEmailAttachment[]
|
|
events AdminEmailEvent[]
|
|
links AdminEmailLink[]
|
|
activities AdminActivity[]
|
|
reminders AdminNoReplyReminder[]
|
|
|
|
@@unique([mailboxId, providerMessageId], name: "admin_email_message_provider_key")
|
|
@@index([mailboxId, internetMessageId], name: "admin_email_message_rfc_id_idx")
|
|
@@index([adminUserId, matchStatus, receivedAt(sort: Desc)], name: "admin_email_message_match_queue_idx")
|
|
@@index([matchedContactId, sentAt(sort: Desc)], name: "admin_email_message_contact_time_idx")
|
|
@@index([matchedLeadId, sentAt(sort: Desc)], name: "admin_email_message_lead_time_idx")
|
|
@@index([matchedDealId, sentAt(sort: Desc)], name: "admin_email_message_deal_time_idx")
|
|
}
|
|
|
|
model AdminEmailParticipant {
|
|
id String @id @default(cuid())
|
|
createdAt DateTime @default(now())
|
|
adminUserId String
|
|
messageId String
|
|
kind String
|
|
emailAddress String
|
|
displayName String?
|
|
|
|
message AdminEmailMessage @relation(fields: [messageId], references: [id], onDelete: Cascade)
|
|
|
|
@@unique([messageId, kind, emailAddress], name: "admin_email_participant_message_kind_address_key")
|
|
@@index([adminUserId, emailAddress], name: "admin_email_participant_owner_address_idx")
|
|
}
|
|
|
|
model AdminEmailAttachment {
|
|
id String @id @default(cuid())
|
|
createdAt DateTime @default(now())
|
|
adminUserId String
|
|
messageId String
|
|
providerAttachmentId String?
|
|
filename String
|
|
mimeType String?
|
|
sizeBytes Int
|
|
storageBucket String
|
|
storageKey String
|
|
checksumSha256 String
|
|
scannedAt DateTime
|
|
linkedDealId String?
|
|
|
|
message AdminEmailMessage @relation(fields: [messageId], references: [id], onDelete: Cascade)
|
|
|
|
@@unique([messageId, checksumSha256], name: "admin_email_attachment_message_checksum_key")
|
|
@@index([adminUserId, linkedDealId, createdAt], name: "admin_email_attachment_owner_deal_idx")
|
|
}
|
|
|
|
model AdminEmailEvent {
|
|
id String @id @default(cuid())
|
|
createdAt DateTime @default(now())
|
|
adminUserId String
|
|
messageId String
|
|
type AdminEmailEventType
|
|
providerEventId String?
|
|
recipient String?
|
|
linkId String?
|
|
occurredAt DateTime
|
|
rawExpiresAt DateTime?
|
|
metadata Json?
|
|
|
|
message AdminEmailMessage @relation(fields: [messageId], references: [id], onDelete: Cascade)
|
|
link AdminEmailLink? @relation(fields: [linkId], references: [id], onDelete: SetNull)
|
|
|
|
@@unique([messageId, type, providerEventId], name: "admin_email_event_provider_key")
|
|
@@index([adminUserId, type, occurredAt(sort: Desc)], name: "admin_email_event_owner_type_idx")
|
|
@@index([rawExpiresAt], name: "admin_email_event_retention_idx")
|
|
}
|
|
|
|
model AdminEmailLink {
|
|
id String @id @default(cuid())
|
|
createdAt DateTime @default(now())
|
|
adminUserId String
|
|
messageId String
|
|
destinationUrl String
|
|
trackingTokenHash String @unique
|
|
|
|
message AdminEmailMessage @relation(fields: [messageId], references: [id], onDelete: Cascade)
|
|
events AdminEmailEvent[]
|
|
|
|
@@index([adminUserId, messageId], name: "admin_email_link_owner_message_idx")
|
|
}
|
|
|
|
model AdminEmailTemplate {
|
|
id String @id @default(cuid())
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
archivedAt DateTime?
|
|
adminUserId String
|
|
name String
|
|
subjectTemplate String
|
|
htmlTemplate String
|
|
textTemplate String?
|
|
allowedPlaceholders String[] @default([])
|
|
version Int @default(1)
|
|
|
|
@@unique([adminUserId, name, version], name: "admin_email_template_owner_name_version_key")
|
|
@@index([adminUserId, archivedAt, name], name: "admin_email_template_owner_archive_idx")
|
|
}
|
|
|
|
model AdminEmailSnippet {
|
|
id String @id @default(cuid())
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
archivedAt DateTime?
|
|
adminUserId String
|
|
shortcut String
|
|
title String
|
|
content String
|
|
|
|
@@unique([adminUserId, shortcut], name: "admin_email_snippet_owner_shortcut_key")
|
|
@@index([adminUserId, archivedAt, title], name: "admin_email_snippet_owner_archive_idx")
|
|
}
|
|
|
|
model AdminEmailSignature {
|
|
id String @id @default(cuid())
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
adminUserId String @unique
|
|
html String
|
|
text String?
|
|
|
|
@@index([adminUserId, updatedAt], name: "admin_email_signature_owner_updated_idx")
|
|
}
|
|
|
|
model AdminEmailOutboundPolicy {
|
|
id String @id @default(cuid())
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
adminUserId String @unique
|
|
timeZone String
|
|
workdayStartMinutes Int
|
|
workdayEndMinutes Int
|
|
allowedWeekDays Int[] @default([])
|
|
dailySendLimit Int
|
|
bulkDailySendLimit Int
|
|
consentRulesConfigured Boolean @default(false)
|
|
trackingEnabled Boolean @default(false)
|
|
liveOpenEnabled Boolean @default(false)
|
|
trackingRetentionDays Int @default(180)
|
|
configuredAt DateTime?
|
|
|
|
@@index([adminUserId, configuredAt], name: "admin_email_outbound_policy_owner_config_idx")
|
|
}
|
|
|
|
model AdminScheduledEmail {
|
|
id String @id @default(cuid())
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
adminUserId String
|
|
mailboxId String
|
|
contactId String?
|
|
leadId String?
|
|
dealId String?
|
|
threadId String?
|
|
bulkRunId String?
|
|
toAddresses String[]
|
|
ccAddresses String[] @default([])
|
|
bccAddresses String[] @default([])
|
|
subject String
|
|
bodyHtml String
|
|
bodyText String?
|
|
scheduledFor DateTime
|
|
timeZone String
|
|
noReplyAfterDays Int?
|
|
status AdminScheduledEmailStatus @default(draft)
|
|
idempotencyKey String @unique
|
|
claimedAt DateTime?
|
|
sentAt DateTime?
|
|
sendAttemptCount Int @default(0)
|
|
providerMessageId String?
|
|
sentMessageId String?
|
|
lastErrorMessageKey String?
|
|
|
|
mailbox AdminMailbox @relation(fields: [mailboxId], references: [id], onDelete: Restrict)
|
|
thread AdminEmailThread? @relation(fields: [threadId], references: [id], onDelete: SetNull)
|
|
bulkRun AdminBulkEmailRun? @relation(fields: [bulkRunId], references: [id], onDelete: SetNull)
|
|
|
|
@@index([adminUserId, status, scheduledFor], name: "admin_scheduled_email_due_idx")
|
|
@@index([dealId, scheduledFor], name: "admin_scheduled_email_deal_idx")
|
|
@@index([bulkRunId, status], name: "admin_scheduled_email_bulk_status_idx")
|
|
}
|
|
|
|
model AdminNoReplyReminder {
|
|
id String @id @default(cuid())
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
adminUserId String
|
|
outboundMessageId String
|
|
dueAt DateTime
|
|
status AdminNoReplyReminderStatus @default(pending)
|
|
taskId String?
|
|
idempotencyKey String @unique
|
|
|
|
outboundMessage AdminEmailMessage @relation(fields: [outboundMessageId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([adminUserId, status, dueAt], name: "admin_no_reply_reminder_due_idx")
|
|
}
|
|
|
|
model AdminBulkEmailRun {
|
|
id String @id @default(cuid())
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
adminUserId String
|
|
mailboxId String
|
|
name String
|
|
selectedContactIds String[] @default([])
|
|
templateId String?
|
|
status AdminBulkEmailRunStatus @default(draft)
|
|
scheduledFor DateTime?
|
|
timeZone String
|
|
recipientCount Int @default(0)
|
|
sentCount Int @default(0)
|
|
failedCount Int @default(0)
|
|
idempotencyKey String @unique
|
|
lastErrorMessageKey String?
|
|
|
|
mailbox AdminMailbox @relation(fields: [mailboxId], references: [id], onDelete: Restrict)
|
|
scheduled AdminScheduledEmail[]
|
|
|
|
@@index([adminUserId, status, scheduledFor], name: "admin_bulk_email_run_due_idx")
|
|
}
|
|
|
|
model AdminSendingDomain {
|
|
id String @id @default(cuid())
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
archivedAt DateTime?
|
|
adminUserId String
|
|
domain String @unique
|
|
spfRecordName String
|
|
spfExpectedValue String
|
|
dkimRecordName String
|
|
dkimExpectedValue String
|
|
status AdminSendingDomainStatus @default(pending)
|
|
spfStatus String
|
|
dkimStatus String
|
|
lastCheckedAt DateTime?
|
|
lastErrorMessageKey String?
|
|
|
|
@@index([adminUserId, status, archivedAt], name: "admin_sending_domain_owner_status_idx")
|
|
}
|
|
|
|
model AdminBccAddress {
|
|
id String @id @default(cuid())
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
archivedAt DateTime?
|
|
adminUserId String
|
|
mailboxId String
|
|
localPartHash String @unique
|
|
displayAddress String
|
|
rotatedAt DateTime?
|
|
|
|
mailbox AdminMailbox @relation(fields: [mailboxId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([adminUserId, archivedAt], name: "admin_bcc_address_owner_archive_idx")
|
|
}
|