762 lines
20 KiB
Plaintext
762 lines
20 KiB
Plaintext
generator client {
|
|
provider = "prisma-client-js"
|
|
binaryTargets = ["native", "linux-musl-openssl-3.0.x", "linux-musl-arm64-openssl-3.0.x"]
|
|
}
|
|
|
|
datasource db {
|
|
provider = "postgresql"
|
|
url = env("DATABASE_URL")
|
|
directUrl = env("DIRECT_DATABASE_URL")
|
|
}
|
|
|
|
// ============================================
|
|
// CORE MODELS
|
|
// ============================================
|
|
|
|
model User {
|
|
id String @id @default(uuid())
|
|
|
|
// Credentials
|
|
email String @unique
|
|
password String?
|
|
type AuthMethod
|
|
|
|
// Relations
|
|
memberships Membership[]
|
|
|
|
// Timestamps
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@map("users")
|
|
}
|
|
|
|
model Project {
|
|
id String @id @default(uuid())
|
|
|
|
// Details
|
|
name String
|
|
|
|
// API
|
|
public String @unique
|
|
secret String @unique
|
|
|
|
// Admin
|
|
disabled Boolean @default(false)
|
|
|
|
// Billing
|
|
customer String? @unique
|
|
subscription String? @unique
|
|
|
|
// Billing Limits (per calendar month, null = unlimited)
|
|
billingLimitWorkflows Int? // Max workflow emails per month
|
|
billingLimitCampaigns Int? // Max campaign emails per month
|
|
billingLimitTransactional Int? // Max transactional emails per month
|
|
|
|
// Email Tracking
|
|
tracking TrackingMode @default(ENABLED) // Open and click tracking mode
|
|
|
|
// Relations
|
|
members Membership[]
|
|
contacts Contact[]
|
|
templates Template[]
|
|
segments Segment[]
|
|
workflows Workflow[]
|
|
campaigns Campaign[]
|
|
emails Email[]
|
|
events Event[]
|
|
domains Domain[]
|
|
apiRequests ApiRequest[]
|
|
|
|
// Timestamps
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@map("projects")
|
|
}
|
|
|
|
model Membership {
|
|
user User @relation(fields: [userId], references: [id])
|
|
userId String
|
|
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
|
projectId String
|
|
|
|
// Details
|
|
role Role @default(MEMBER)
|
|
|
|
// Timestamps
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@id([userId, projectId])
|
|
@@map("memberships")
|
|
}
|
|
|
|
model Domain {
|
|
id String @id @default(uuid())
|
|
|
|
// Domain details
|
|
domain String
|
|
verified Boolean @default(false)
|
|
|
|
// DKIM tokens for DNS verification
|
|
dkimTokens Json? // Array of DKIM token strings
|
|
|
|
// Relations
|
|
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
|
projectId String
|
|
|
|
// Timestamps
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@unique([projectId, domain])
|
|
@@index([projectId])
|
|
@@index([projectId, verified])
|
|
@@map("domains")
|
|
}
|
|
|
|
// ============================================
|
|
// CONTACTS & TEMPLATES
|
|
// ============================================
|
|
|
|
model Contact {
|
|
id String @id @default(uuid())
|
|
|
|
// Details
|
|
email String
|
|
data Json? // Custom fields: { firstName: "John", plan: "pro", ... }
|
|
|
|
// Subscription
|
|
subscribed Boolean @default(true)
|
|
|
|
// Relations
|
|
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
|
projectId String
|
|
|
|
emails Email[]
|
|
workflowExecutions WorkflowExecution[]
|
|
events Event[]
|
|
segmentMemberships SegmentMembership[]
|
|
|
|
// Timestamps
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@unique([projectId, email])
|
|
@@index([projectId])
|
|
@@index([projectId, subscribed])
|
|
@@index([data(ops: JsonbOps)], type: Gin) // GIN index for fast JSON queries on Contact.data
|
|
@@map("contacts")
|
|
}
|
|
|
|
model Template {
|
|
id String @id @default(uuid())
|
|
|
|
// Details
|
|
name String
|
|
description String?
|
|
|
|
// Content
|
|
subject String
|
|
body String // HTML content with {{variable}} placeholders
|
|
from String
|
|
fromName String?
|
|
replyTo String?
|
|
|
|
// Type
|
|
type TemplateType @default(MARKETING)
|
|
|
|
// Relations
|
|
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
|
projectId String
|
|
|
|
emails Email[]
|
|
workflowSteps WorkflowStep[]
|
|
|
|
// Timestamps
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@index([projectId])
|
|
@@index([projectId, type])
|
|
@@map("templates")
|
|
}
|
|
|
|
// ============================================
|
|
// SEGMENTS (Dynamic audience groups)
|
|
// ============================================
|
|
|
|
model Segment {
|
|
id String @id @default(uuid())
|
|
|
|
// Details
|
|
name String
|
|
description String?
|
|
|
|
// Filter condition (evaluated dynamically)
|
|
condition Json
|
|
// Nested filter structure with AND/OR logic:
|
|
// {
|
|
// logic: "OR",
|
|
// groups: [
|
|
// {
|
|
// filters: [
|
|
// { field: "data.plan", operator: "equals", value: "VIP" },
|
|
// { field: "subscribed", operator: "equals", value: true }
|
|
// ]
|
|
// },
|
|
// {
|
|
// filters: [
|
|
// { field: "createdAt", operator: "within", value: 30, unit: "days" }
|
|
// ],
|
|
// conditions: {
|
|
// logic: "AND",
|
|
// groups: [...]
|
|
// }
|
|
// }
|
|
// ]
|
|
// }
|
|
// Operators: equals, notEquals, contains, greaterThan, lessThan, within, exists, etc.
|
|
|
|
// Track membership changes (enables segment entry/exit events)
|
|
trackMembership Boolean @default(false)
|
|
|
|
// Stats (computed)
|
|
memberCount Int @default(0)
|
|
|
|
// Relations
|
|
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
|
projectId String
|
|
|
|
memberships SegmentMembership[]
|
|
campaigns Campaign[]
|
|
|
|
// Timestamps
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@index([projectId])
|
|
@@index([condition(ops: JsonbOps)], type: Gin) // GIN index for segment condition queries
|
|
@@map("segments")
|
|
}
|
|
|
|
model SegmentMembership {
|
|
// Relations
|
|
contact Contact @relation(fields: [contactId], references: [id], onDelete: Cascade)
|
|
contactId String
|
|
|
|
segment Segment @relation(fields: [segmentId], references: [id], onDelete: Cascade)
|
|
segmentId String
|
|
|
|
// Membership tracking
|
|
enteredAt DateTime @default(now())
|
|
exitedAt DateTime? // null = currently in segment
|
|
|
|
// Timestamps
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@id([contactId, segmentId])
|
|
@@index([segmentId, exitedAt]) // Active members (exitedAt = null)
|
|
@@index([contactId, exitedAt])
|
|
@@index([enteredAt])
|
|
@@map("segment_memberships")
|
|
}
|
|
|
|
// ============================================
|
|
// CAMPAIGNS (One-time broadcasts)
|
|
// ============================================
|
|
|
|
model Campaign {
|
|
id String @id @default(uuid())
|
|
|
|
// Details
|
|
name String
|
|
description String?
|
|
status CampaignStatus @default(DRAFT)
|
|
|
|
// Email content
|
|
subject String
|
|
body String @db.Text
|
|
from String
|
|
fromName String?
|
|
replyTo String?
|
|
|
|
// Audience selection
|
|
audienceType CampaignAudienceType @default(ALL)
|
|
audienceCondition Json? // For FILTERED: manual filter condition with AND/OR logic (same structure as Segment.condition)
|
|
|
|
segment Segment? @relation(fields: [segmentId], references: [id])
|
|
segmentId String? // For SEGMENT: reference to saved segment
|
|
|
|
// Scheduling
|
|
scheduledFor DateTime?
|
|
|
|
// Stats (computed)
|
|
totalRecipients Int @default(0)
|
|
sentCount Int @default(0)
|
|
deliveredCount Int @default(0)
|
|
openedCount Int @default(0)
|
|
clickedCount Int @default(0)
|
|
bouncedCount Int @default(0)
|
|
|
|
// Relations
|
|
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
|
projectId String
|
|
|
|
emails Email[]
|
|
|
|
// Timestamps
|
|
sentAt DateTime?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@index([projectId, status])
|
|
@@index([scheduledFor])
|
|
@@index([segmentId])
|
|
@@index([audienceCondition(ops: JsonbOps)], type: Gin) // GIN index for campaign audience condition queries
|
|
@@map("campaigns")
|
|
}
|
|
|
|
// ============================================
|
|
// WORKFLOWS (Automated sequences)
|
|
// ============================================
|
|
|
|
model Workflow {
|
|
id String @id @default(uuid())
|
|
|
|
// Details
|
|
name String
|
|
description String?
|
|
enabled Boolean @default(false)
|
|
|
|
// Entry point
|
|
triggerType WorkflowTriggerType
|
|
triggerConfig Json? // { eventName: "user.signup" } or { schedule: "0 9 * * *" }
|
|
|
|
// Re-entry behavior
|
|
allowReentry Boolean @default(false) // If true, contacts can enter workflow multiple times
|
|
|
|
// Relations
|
|
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
|
projectId String
|
|
|
|
steps WorkflowStep[]
|
|
executions WorkflowExecution[]
|
|
|
|
// Timestamps
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@index([projectId])
|
|
@@index([projectId, enabled])
|
|
@@map("workflows")
|
|
}
|
|
|
|
model WorkflowStep {
|
|
id String @id @default(uuid())
|
|
|
|
// Step details
|
|
type WorkflowStepType
|
|
name String
|
|
position Json // { x: 100, y: 200 } for visual editor
|
|
|
|
// Step configuration (flexible JSON)
|
|
config Json
|
|
// Examples by type:
|
|
// TRIGGER: { eventName: "user.signup" }
|
|
// SEND_EMAIL: { templateId: "uuid", from: "team@example.com" }
|
|
// DELAY: { amount: 24, unit: "hours" }
|
|
// WAIT_FOR_EVENT: { eventName: "email.clicked", timeout: 86400 }
|
|
// CONDITION: { field: "contact.data.plan", operator: "equals", value: "pro" }
|
|
// EXIT: { reason: "unsubscribed" }
|
|
|
|
// Relations
|
|
workflow Workflow @relation(fields: [workflowId], references: [id], onDelete: Cascade)
|
|
workflowId String
|
|
|
|
template Template? @relation(fields: [templateId], references: [id])
|
|
templateId String? // For SEND_EMAIL steps
|
|
|
|
// Graph connections
|
|
outgoingTransitions WorkflowTransition[] @relation("FromStep")
|
|
incomingTransitions WorkflowTransition[] @relation("ToStep")
|
|
|
|
// Execution tracking
|
|
executions WorkflowStepExecution[]
|
|
currentWorkflowExecutions WorkflowExecution[] // Executions currently on this step
|
|
|
|
// Timestamps
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@index([workflowId])
|
|
@@index([templateId])
|
|
@@map("workflow_steps")
|
|
}
|
|
|
|
model WorkflowTransition {
|
|
id String @id @default(uuid())
|
|
|
|
// From -> To
|
|
fromStep WorkflowStep @relation("FromStep", fields: [fromStepId], references: [id], onDelete: Cascade)
|
|
fromStepId String
|
|
|
|
toStep WorkflowStep @relation("ToStep", fields: [toStepId], references: [id], onDelete: Cascade)
|
|
toStepId String
|
|
|
|
// Conditional routing
|
|
condition Json? // null = always follow, or { branch: "yes" } for condition steps
|
|
priority Int @default(0) // Order to evaluate transitions
|
|
|
|
// Timestamps
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@index([fromStepId])
|
|
@@index([toStepId])
|
|
@@map("workflow_transitions")
|
|
}
|
|
|
|
model WorkflowExecution {
|
|
id String @id @default(uuid())
|
|
|
|
// Relations
|
|
workflow Workflow @relation(fields: [workflowId], references: [id], onDelete: Cascade)
|
|
workflowId String
|
|
|
|
contact Contact @relation(fields: [contactId], references: [id], onDelete: Cascade)
|
|
contactId String
|
|
|
|
// Execution state
|
|
status WorkflowExecutionStatus @default(RUNNING)
|
|
|
|
// Current position
|
|
currentStep WorkflowStep? @relation(fields: [currentStepId], references: [id])
|
|
currentStepId String?
|
|
|
|
// Exit information
|
|
exitReason String?
|
|
|
|
// Context data (merged with contact.data when executing)
|
|
context Json? // Additional variables for this execution
|
|
|
|
// Relations
|
|
stepExecutions WorkflowStepExecution[]
|
|
emails Email[]
|
|
|
|
// Timestamps
|
|
startedAt DateTime @default(now())
|
|
completedAt DateTime?
|
|
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@index([workflowId, contactId]) // Index for querying executions by workflow and contact
|
|
@@index([workflowId, status])
|
|
@@index([contactId, status])
|
|
@@index([status, currentStepId])
|
|
@@map("workflow_executions")
|
|
}
|
|
|
|
model WorkflowStepExecution {
|
|
id String @id @default(uuid())
|
|
|
|
// Relations
|
|
execution WorkflowExecution @relation(fields: [executionId], references: [id], onDelete: Cascade)
|
|
executionId String
|
|
|
|
step WorkflowStep @relation(fields: [stepId], references: [id], onDelete: Cascade)
|
|
stepId String
|
|
|
|
// Execution state
|
|
status StepExecutionStatus @default(PENDING)
|
|
|
|
// Delay/scheduling
|
|
scheduledFor DateTime? // When this step should execute (for DELAY steps)
|
|
executeAfter DateTime? // Don't execute before this time (for WAIT_FOR_EVENT timeout)
|
|
|
|
// Result tracking
|
|
output Json? // Step execution result
|
|
error String?
|
|
|
|
// Relations
|
|
emails Email[] // Emails sent by this step execution
|
|
|
|
// Timestamps
|
|
startedAt DateTime?
|
|
completedAt DateTime?
|
|
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@index([executionId, status])
|
|
@@index([stepId])
|
|
@@index([status, scheduledFor]) // For delay queue processor
|
|
@@index([scheduledFor])
|
|
@@map("workflow_step_executions")
|
|
}
|
|
|
|
// ============================================
|
|
// EMAILS (Unified tracking for all sends)
|
|
// ============================================
|
|
|
|
model Email {
|
|
id String @id @default(uuid())
|
|
|
|
// Recipient
|
|
contact Contact @relation(fields: [contactId], references: [id], onDelete: Cascade)
|
|
contactId String
|
|
toName String? // Recipient display name for To header
|
|
|
|
// Content (denormalized for history)
|
|
subject String
|
|
body String // Rendered HTML
|
|
from String
|
|
fromName String?
|
|
replyTo String?
|
|
headers Json? // Custom email headers
|
|
attachments Json? // Array of {filename: string, content: string (base64), contentType: string}
|
|
|
|
// AWS SES Message ID (for tracking webhooks)
|
|
messageId String? @unique
|
|
|
|
// Source - identifies how this email was triggered
|
|
sourceType EmailSourceType
|
|
|
|
// Relations to source (one will be set based on sourceType)
|
|
template Template? @relation(fields: [templateId], references: [id])
|
|
templateId String? // For TRANSACTIONAL
|
|
|
|
campaign Campaign? @relation(fields: [campaignId], references: [id], onDelete: Cascade)
|
|
campaignId String? // For CAMPAIGN
|
|
|
|
workflowExecution WorkflowExecution? @relation(fields: [workflowExecutionId], references: [id], onDelete: Cascade)
|
|
workflowExecutionId String? // For WORKFLOW
|
|
|
|
workflowStepExecution WorkflowStepExecution? @relation(fields: [workflowStepExecutionId], references: [id], onDelete: Cascade)
|
|
workflowStepExecutionId String? // For WORKFLOW (specific step)
|
|
|
|
// Delivery
|
|
status EmailStatus @default(PENDING)
|
|
|
|
// Event timestamps
|
|
sentAt DateTime?
|
|
deliveredAt DateTime?
|
|
openedAt DateTime? // First open
|
|
clickedAt DateTime? // First click
|
|
bouncedAt DateTime?
|
|
complainedAt DateTime?
|
|
|
|
// Event counts
|
|
opens Int @default(0)
|
|
clicks Int @default(0)
|
|
|
|
// Error tracking
|
|
error String?
|
|
|
|
// Relations
|
|
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
|
projectId String
|
|
|
|
events Event[]
|
|
|
|
// Timestamps
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@index([projectId, contactId])
|
|
@@index([contactId])
|
|
@@index([campaignId])
|
|
@@index([workflowExecutionId])
|
|
@@index([workflowStepExecutionId])
|
|
@@index([status])
|
|
@@index([createdAt])
|
|
@@index([projectId, sourceType, createdAt]) // For billing limit queries
|
|
@@index([contactId, openedAt]) // For email activity segment queries
|
|
@@index([contactId, clickedAt]) // For email activity segment queries
|
|
@@index([contactId, bouncedAt]) // For email activity segment queries
|
|
@@index([contactId, complainedAt]) // For email activity segment queries
|
|
@@map("emails")
|
|
}
|
|
|
|
// ============================================
|
|
// EVENTS (Webhook tracking & workflow triggers)
|
|
// ============================================
|
|
|
|
model Event {
|
|
id String @id @default(uuid())
|
|
|
|
// Event details
|
|
name String // e.g., "email.opened", "email.clicked", "user.signup"
|
|
data Json? // Event payload
|
|
|
|
// Relations
|
|
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
|
projectId String
|
|
|
|
contact Contact? @relation(fields: [contactId], references: [id], onDelete: Cascade)
|
|
contactId String?
|
|
|
|
email Email? @relation(fields: [emailId], references: [id], onDelete: Cascade)
|
|
emailId String?
|
|
|
|
// Timestamps
|
|
createdAt DateTime @default(now())
|
|
|
|
@@index([projectId, name])
|
|
@@index([contactId])
|
|
@@index([emailId])
|
|
@@index([createdAt])
|
|
@@index([projectId, contactId, name, createdAt]) // For event-based segment queries (fast!)
|
|
@@index([contactId, name, createdAt]) // For per-contact event lookups
|
|
@@index([projectId, name, createdAt]) // For event stats and analytics by type over time
|
|
@@index([contactId, createdAt]) // For contact activity timeline (recent events)
|
|
@@index([data(ops: JsonbOps)], type: Gin) // GIN index for event data queries
|
|
@@map("events")
|
|
}
|
|
|
|
// ============================================
|
|
// ENUMS
|
|
// ============================================
|
|
|
|
enum AuthMethod {
|
|
PASSWORD
|
|
GOOGLE_OAUTH
|
|
GITHUB_OAUTH
|
|
}
|
|
|
|
enum Role {
|
|
OWNER
|
|
ADMIN
|
|
MEMBER
|
|
}
|
|
|
|
enum TemplateType {
|
|
TRANSACTIONAL
|
|
MARKETING
|
|
}
|
|
|
|
enum TrackingMode {
|
|
ENABLED // Track opens and clicks for all emails
|
|
DISABLED // No tracking for any emails
|
|
MARKETING_ONLY // Track only marketing/campaign emails, not transactional
|
|
}
|
|
|
|
enum CampaignStatus {
|
|
DRAFT
|
|
SCHEDULED
|
|
SENDING
|
|
SENT
|
|
CANCELLED
|
|
}
|
|
|
|
enum CampaignAudienceType {
|
|
ALL // All contacts
|
|
FILTERED // Based on audienceFilter
|
|
SEGMENT // Predefined segment
|
|
}
|
|
|
|
enum WorkflowTriggerType {
|
|
EVENT // Triggered by an event (e.g., user.signup)
|
|
MANUAL // Manually started via API
|
|
SCHEDULE // Runs on a schedule (cron)
|
|
}
|
|
|
|
enum WorkflowStepType {
|
|
TRIGGER // Entry point
|
|
SEND_EMAIL // Send an email
|
|
DELAY // Wait for X time
|
|
WAIT_FOR_EVENT // Wait for specific event (with timeout)
|
|
CONDITION // If/else branching
|
|
EXIT // Early exit point
|
|
WEBHOOK // Call external webhook
|
|
UPDATE_CONTACT // Update contact fields
|
|
}
|
|
|
|
enum WorkflowExecutionStatus {
|
|
RUNNING // Currently executing
|
|
WAITING // Waiting for event or delay
|
|
COMPLETED // Finished normally
|
|
EXITED // Exited early (via EXIT step)
|
|
FAILED // Failed with error
|
|
CANCELLED // Manually cancelled
|
|
}
|
|
|
|
// ============================================
|
|
// API REQUEST LOGGING
|
|
// ============================================
|
|
|
|
model ApiRequest {
|
|
id String @id // Request ID (UUID from X-Request-ID header)
|
|
|
|
// Request metadata
|
|
method String // HTTP method (GET, POST, etc.)
|
|
path String // Request path (/v1/send, /contacts, etc.)
|
|
statusCode Int // HTTP response status code
|
|
duration Int // Response time in milliseconds
|
|
|
|
// Authentication context
|
|
projectId String? // Project that made the request (if authenticated)
|
|
project Project? @relation(fields: [projectId], references: [id], onDelete: SetNull)
|
|
userId String? // User that made the request (if JWT auth)
|
|
authType String? // "apiKey" or "jwt"
|
|
|
|
// Request details
|
|
ip String? // Client IP address
|
|
userAgent String? // User-Agent header
|
|
|
|
// Error information (if status >= 400)
|
|
errorCode String? // Machine-readable error code (VALIDATION_ERROR, etc.)
|
|
errorMessage String? // Human-readable error message
|
|
|
|
// Size tracking (for analytics)
|
|
requestSize Int? // Request body size in bytes
|
|
responseSize Int? // Response body size in bytes
|
|
|
|
// Timestamps
|
|
createdAt DateTime @default(now())
|
|
|
|
// Indexes for efficient querying
|
|
@@index([projectId, createdAt(sort: Desc)]) // Query requests by project, most recent first
|
|
@@index([userId, createdAt(sort: Desc)]) // Query requests by user
|
|
@@index([statusCode, createdAt(sort: Desc)]) // Query errors (WHERE statusCode >= 400)
|
|
@@index([path, createdAt(sort: Desc)]) // Query by endpoint
|
|
@@index([createdAt(sort: Desc)]) // General time-based queries
|
|
@@index([projectId, statusCode, createdAt(sort: Desc)]) // Composite for project error queries
|
|
@@map("api_requests")
|
|
}
|
|
|
|
// ============================================
|
|
// ENUMS
|
|
// ============================================
|
|
|
|
enum StepExecutionStatus {
|
|
PENDING // Not yet started
|
|
SCHEDULED // Scheduled for future execution (DELAY)
|
|
WAITING // Waiting for event (WAIT_FOR_EVENT)
|
|
RUNNING // Currently executing
|
|
COMPLETED // Successfully completed
|
|
SKIPPED // Skipped (e.g., condition not met)
|
|
FAILED // Failed with error
|
|
}
|
|
|
|
enum EmailSourceType {
|
|
TRANSACTIONAL // Sent via API call
|
|
CAMPAIGN // Sent as part of broadcast
|
|
WORKFLOW // Sent via workflow automation
|
|
}
|
|
|
|
enum EmailStatus {
|
|
PENDING // Queued for sending
|
|
SENDING // Currently being sent
|
|
SENT // Successfully sent to provider
|
|
DELIVERED // Confirmed delivered
|
|
OPENED // Recipient opened email
|
|
CLICKED // Recipient clicked link
|
|
BOUNCED // Bounced (hard or soft)
|
|
COMPLAINED // Marked as spam
|
|
FAILED // Failed to send
|
|
}
|