Automation Models
This document covers the marketing automation (journey) data models for the CRM & Marketing Platform.
Journey
Automated multi-step journey definition.
model Journey {
id String @id @default(uuid())
tenantId String
clubId String?
name String
description String?
status JourneyStatus @default(DRAFT)
// Trigger
triggerType String // event type or MANUAL
triggerConfig Json? // Additional trigger conditions
// Optional campaign link
campaignId String? @unique
campaign Campaign? @relation(fields: [campaignId], references: [id])
// Settings
allowReentry Boolean @default(false)
maxEnrollments Int? // Limit concurrent enrollments
timezone String @default("UTC")
// Stats
totalEnrolled Int @default(0)
totalCompleted Int @default(0)
totalExited Int @default(0)
// Audit
activatedAt DateTime?
pausedAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
createdBy String
deletedAt DateTime?
// Relations
steps JourneyStep[]
enrollments JourneyEnrollment[]
@@unique([tenantId, name])
@@index([tenantId, status])
@@index([tenantId, triggerType])
}
Journey Status
| Status | Description | Enrolling? |
|---|---|---|
| DRAFT | Being configured | No |
| ACTIVE | Running | Yes |
| PAUSED | Temporarily stopped | No |
| ARCHIVED | Permanently disabled | No |
Trigger Types
| Trigger | Description |
|---|---|
scl.member.created | New member signup |
teetime.booked | Tee time booked |
teetime.completed | Tee time finished |
payment.failed | Payment failed |
engagement.dropped | Engagement score dropped |
MANUAL | Manual enrollment only |
Trigger Config Example
{
"conditions": [
{ "field": "membershipTier", "operator": "in", "value": ["Gold", "Platinum"] }
]
}
JourneyStep
Individual step in a journey.
model JourneyStep {
id String @id @default(uuid())
journeyId String
journey Journey @relation(fields: [journeyId], references: [id], onDelete: Cascade)
stepNumber Int
type JourneyStepType
name String?
// Configuration (varies by type)
config Json
// TRIGGER: { eventType, conditions }
// SEND: { channel, templateId }
// WAIT: { duration, until }
// CONDITION: { rules, combinator }
// SPLIT: { variants, weights }
// UPDATE: { field, value }
// Branching
nextStepId String? // Default next step
nextStepYesId String? // For CONDITION: if true
nextStepNoId String? // For CONDITION: if false
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
// Relations
enrollmentSteps JourneyEnrollmentStep[]
@@unique([journeyId, stepNumber])
@@index([journeyId])
}
Step Types and Config
TRIGGER
{
"type": "TRIGGER",
"config": {
"eventType": "scl.member.created",
"conditions": []
}
}
SEND
{
"type": "SEND",
"config": {
"channel": "EMAIL",
"templateId": "welcome-email-v2"
}
}
WAIT
{
"type": "WAIT",
"config": {
"duration": "3 days"
}
}
Supported durations:
"30 minutes""2 hours""3 days""1 week"
CONDITION
{
"type": "CONDITION",
"config": {
"rules": [
{ "field": "lastEmailOpenAt", "operator": "isNotNull" }
],
"combinator": "AND"
},
"nextStepYesId": "step_abc",
"nextStepNoId": "step_xyz"
}
SPLIT (A/B Test)
{
"type": "SPLIT",
"config": {
"variants": [
{ "name": "A", "weight": 50 },
{ "name": "B", "weight": 50 }
]
}
}
UPDATE
{
"type": "UPDATE",
"config": {
"updates": [
{ "field": "tags", "operation": "add", "value": "onboarded" }
]
}
}
END
{
"type": "END",
"config": {}
}
JourneyEnrollment
Customer enrollment in a journey.
model JourneyEnrollment {
id String @id @default(uuid())
tenantId String
journeyId String
journey Journey @relation(fields: [journeyId], references: [id], onDelete: Cascade)
customerId String
customer CustomerProfile @relation(fields: [customerId], references: [id], onDelete: Cascade)
status JourneyEnrollmentStatus @default(ACTIVE)
// Progress tracking
currentStepId String?
currentStepNumber Int @default(0)
waitingUntil DateTime? // For WAIT steps
// Timing
enrolledAt DateTime @default(now())
completedAt DateTime?
exitedAt DateTime?
exitReason String?
// Context
triggerData Json? // Event data that triggered enrollment
// Relations
steps JourneyEnrollmentStep[]
@@unique([journeyId, customerId]) // Prevent duplicate (unless allowReentry)
@@index([tenantId, journeyId, status])
@@index([customerId])
@@index([waitingUntil])
}
Enrollment Status
| Status | Description |
|---|---|
| ACTIVE | Currently in journey |
| COMPLETED | Reached END step |
| EXITED | Left early (manual or rule) |
| FAILED | Error during processing |
Exit Reasons
completed- Reached END stepmanual- Admin removedunsubscribed- Customer opted outmerged- Profile was mergederror- Processing failed
JourneyEnrollmentStep
Track which steps a customer has completed.
model JourneyEnrollmentStep {
id String @id @default(uuid())
enrollmentId String
enrollment JourneyEnrollment @relation(fields: [enrollmentId], references: [id], onDelete: Cascade)
stepId String
step JourneyStep @relation(fields: [stepId], references: [id], onDelete: Cascade)
status String @default("PENDING") // PENDING, IN_PROGRESS, COMPLETED, FAILED, SKIPPED
enteredAt DateTime @default(now())
completedAt DateTime?
result Json? // Step-specific result data
@@unique([enrollmentId, stepId])
@@index([enrollmentId])
}
Step Status
| Status | Description |
|---|---|
| PENDING | Not yet reached |
| IN_PROGRESS | Currently executing |
| COMPLETED | Successfully completed |
| FAILED | Error during execution |
| SKIPPED | Bypassed (branch not taken) |
Result Data Examples
SEND Result
{
"sent": true,
"channel": "EMAIL",
"messageId": "msg_123"
}
CONDITION Result
{
"result": true,
"evaluatedRules": [
{ "field": "lastEmailOpenAt", "matched": true }
]
}
Index Strategy
| Query Pattern | Index |
|---|---|
| List journeys by status | (tenantId, status) |
| Find journeys by trigger | (tenantId, triggerType) |
| Steps for journey | (journeyId, stepNumber) |
| Active enrollments | (tenantId, journeyId, status) |
| Customer's enrollments | (customerId) |
| Waiting enrollments | (waitingUntil) |