Skip to main content

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

StatusDescriptionEnrolling?
DRAFTBeing configuredNo
ACTIVERunningYes
PAUSEDTemporarily stoppedNo
ARCHIVEDPermanently disabledNo

Trigger Types

TriggerDescription
scl.member.createdNew member signup
teetime.bookedTee time booked
teetime.completedTee time finished
payment.failedPayment failed
engagement.droppedEngagement score dropped
MANUALManual 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

StatusDescription
ACTIVECurrently in journey
COMPLETEDReached END step
EXITEDLeft early (manual or rule)
FAILEDError during processing

Exit Reasons

  • completed - Reached END step
  • manual - Admin removed
  • unsubscribed - Customer opted out
  • merged - Profile was merged
  • error - 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

StatusDescription
PENDINGNot yet reached
IN_PROGRESSCurrently executing
COMPLETEDSuccessfully completed
FAILEDError during execution
SKIPPEDBypassed (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 PatternIndex
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)