Add better segmenting

This commit is contained in:
Dries Augustyns
2025-12-02 09:03:05 +01:00
parent b521a405bb
commit fe2e038037
23 changed files with 2285 additions and 855 deletions
@@ -122,7 +122,7 @@ CREATE TABLE "segments" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"description" TEXT,
"filters" JSONB NOT NULL,
"condition" JSONB NOT NULL,
"trackMembership" BOOLEAN NOT NULL DEFAULT false,
"memberCount" INTEGER NOT NULL DEFAULT 0,
"projectId" TEXT NOT NULL,
@@ -156,7 +156,7 @@ CREATE TABLE "campaigns" (
"fromName" TEXT,
"replyTo" TEXT,
"audienceType" "CampaignAudienceType" NOT NULL DEFAULT 'ALL',
"audienceFilter" JSONB,
"audienceCondition" JSONB,
"segmentId" TEXT,
"scheduledFor" TIMESTAMP(3),
"totalRecipients" INTEGER NOT NULL DEFAULT 0,
+31 -10
View File
@@ -193,14 +193,29 @@ model Segment {
name String
description String?
// Filter conditions (evaluated dynamically)
filters Json
// Array of conditions with AND/OR logic:
// [
// { field: "data.plan", operator: "equals", value: "FREE" },
// { field: "subscribed", operator: "equals", value: true },
// { field: "emails.openedAt", operator: "within", value: 30, unit: "days" }
// ]
// 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)
@@ -267,8 +282,8 @@ model Campaign {
replyTo String?
// Audience selection
audienceType CampaignAudienceType @default(ALL)
audienceFilter Json? // For FILTERED: manual filter conditions
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
@@ -556,6 +571,10 @@ model Email {
@@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")
}
@@ -587,6 +606,8 @@ model Event {
@@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
@@map("events")
}