Skip to main content

Campaign Models

This document covers the campaign data models for the CRM & Marketing Platform.


Campaign

Marketing campaign definition.

model Campaign {
id String @id @default(uuid())
tenantId String
clubId String?

name String
description String?
type CampaignType @default(ONE_TIME)
status CampaignStatus @default(DRAFT)

// Targeting
segmentId String?
segment CustomerSegment? @relation(fields: [segmentId], references: [id])

// Channels enabled
channels Channel[]

// Schedule
scheduledAt DateTime?
startedAt DateTime?
completedAt DateTime?
timezone String? @default("UTC")

// For recurring campaigns
recurPattern String? // Cron expression
recurEndAt DateTime?

// Content references (per channel - IDs from Messaging service)
emailTemplateId String?
smsTemplateId String?
pushTemplateId String?

// Stats (denormalized)
targetCount Int @default(0)
sentCount Int @default(0)
deliveredCount Int @default(0)
openedCount Int @default(0)
clickedCount Int @default(0)
convertedCount Int @default(0)
unsubscribedCount Int @default(0)
bouncedCount Int @default(0)

// Computed rates
deliveryRate Float?
openRate Float?
clickRate Float?
conversionRate Float?

// Audit
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
createdBy String
deletedAt DateTime?

// Relations
interactions CampaignInteraction[]
socialPosts SocialPost[]
journey Journey?

@@index([tenantId, status])
@@index([tenantId, type])
@@index([tenantId, scheduledAt])
@@index([tenantId, clubId])
}

Campaign Types

TypeDescriptionSchedule
ONE_TIMESingle sendOnce at scheduledAt
RECURRINGRepeatingCron pattern
TRIGGEREDEvent-basedOn event match
JOURNEYMulti-stepManaged by Journey

Campaign Status Flow

DRAFT → SCHEDULED → ACTIVE → COMPLETED
↓ ↓
(cancel) PAUSED → ACTIVE

CANCELLED

Stats Calculation

// After campaign completion
const stats = await calculateCampaignStats(campaignId);

await prisma.campaign.update({
where: { id: campaignId },
data: {
deliveryRate: stats.deliveredCount / stats.sentCount,
openRate: stats.openedCount / stats.deliveredCount,
clickRate: stats.clickedCount / stats.openedCount,
conversionRate: stats.convertedCount / stats.clickedCount,
}
});

CampaignInteraction

Individual interaction with a campaign.

model CampaignInteraction {
id String @id @default(uuid())
tenantId String
campaignId String
campaign Campaign @relation(fields: [campaignId], references: [id], onDelete: Cascade)
customerId String
customer CustomerProfile @relation(fields: [customerId], references: [id], onDelete: Cascade)

channel Channel
type CampaignInteractionType

// Tracking
messageId String? // Messaging.MessageLog.id
linkId String? // Specific link clicked
linkUrl String?

// Context
deviceType String? // mobile, desktop, tablet
userAgent String?
ipAddress String?
location Json? // { city, country, etc. }

occurredAt DateTime @default(now())

@@index([campaignId, type])
@@index([customerId, occurredAt])
@@index([tenantId, occurredAt])
}

Interaction Types

TypeTriggerUpdates
SENTMessage queuedsentCount++
DELIVEREDDelivery confirmeddeliveredCount++
BOUNCEDDelivery failedbouncedCount++
OPENEDEmail openedopenedCount++
CLICKEDLink clickedclickedCount++
CONVERTEDConversion trackedconvertedCount++
UNSUBSCRIBEDOpt-outunsubscribedCount++
COMPLAINEDSpam reportHandle as complaint
REPLIEDSMS replyTrack as engagement

Tracking Flow

1. Campaign sends message
└→ CampaignInteraction(type: SENT)

2. Messaging confirms delivery
└→ CampaignInteraction(type: DELIVERED)

3. Customer opens email
└→ CampaignInteraction(type: OPENED)

4. Customer clicks link
└→ CampaignInteraction(type: CLICKED, linkUrl: "...")

5. Customer converts
└→ CampaignInteraction(type: CONVERTED)

Data Dictionary

Campaign Fields

FieldTypeDescription
typeEnumONE_TIME, RECURRING, TRIGGERED, JOURNEY
statusEnumDRAFT → SCHEDULED → ACTIVE → COMPLETED
segmentIdStringTarget segment
channelsArrayEnabled channels (EMAIL, SMS, etc.)
scheduledAtDateTimeWhen to execute
recurPatternStringCron expression for recurring
emailTemplateIdStringMessaging template reference
targetCountIntTotal in segment at send time
sentCountIntSuccessfully queued

Interaction Fields

FieldTypeDescription
channelEnumEMAIL, SMS, PUSH, etc.
typeEnumSENT, DELIVERED, OPENED, etc.
messageIdStringReference to Messaging.MessageLog
linkIdStringWhich link was clicked
deviceTypeStringDevice category

Index Strategy

Query PatternIndex
List campaigns by status(tenantId, status)
List campaigns by type(tenantId, type)
Find scheduled campaigns(tenantId, scheduledAt)
Filter by club(tenantId, clubId)
Interactions by campaign(campaignId, type)
Interactions by customer(customerId, occurredAt)