Segmentation
This document covers the segmentation capabilities of the CRM & Marketing Platform.
Key Design: CRM Owns Segmentation
CRM defines segments using rich cross-service data. Messaging executes delivery.
CRM Messaging
┌─────────────────────┐ ┌─────────────────────┐
│ CustomerSegment │ │ Segment (simplified)│
│ ─────────────────── │ sync │ ─────────────────── │
│ Rules using: │ ──────────────▶│ Contact membership │
│ - SCL tier │ │ only (for delivery) │
│ - TeeTime bookings │ │ │
│ - CRM engagement │ │ │
│ - Messaging consent │ │ │
└─────────────────────┘ └─────────────────────┘
Segment Types
| Type | Description | Example |
|---|---|---|
| Static | Manual membership | "VIP List", "Board Members" |
| Dynamic | Rule-based, auto-computed | "Active members with handicap < 10" |
| Smart | ML-powered | "Likely to churn in 30 days" |
Dynamic Segment Rules
Segments can use data from ALL services:
const activeGolferSegment = {
name: "Active Golfers - Low Handicap",
rules: [
// From SCL
{ field: "membershipStatus", operator: "eq", value: "ACTIVE" },
{ field: "membershipTier", operator: "in", value: ["Gold", "Platinum"] },
// From TeeTime (denormalized)
{ field: "handicap", operator: "lt", value: 15 },
{ field: "bookingCount30d", operator: "gte", value: 2 },
// From CRM
{ field: "engagementScore", operator: "gte", value: 50 },
// From Messaging (denormalized consent)
{ field: "emailOptIn", operator: "eq", value: true }
],
combinator: "AND"
};
Rule Operators
| Operator | Description | Example |
|---|---|---|
eq | Equals | status eq "ACTIVE" |
neq | Not equals | status neq "INACTIVE" |
gt | Greater than | engagementScore gt 50 |
gte | Greater than or equal | bookingCount30d gte 2 |
lt | Less than | handicap lt 15 |
lte | Less than or equal | churnRiskScore lte 30 |
in | In array | membershipTier in ["Gold", "Platinum"] |
contains | String contains | email contains "@company.com" |
isNull | Is null | handicap isNull |
isNotNull | Is not null | lastActivityAt isNotNull |
Available Fields
From CRM (Direct)
| Field | Type | Description |
|---|---|---|
status | enum | ACTIVE, INACTIVE, MERGED |
lifecycleStage | enum | SUBSCRIBER → ADVOCATE |
engagementScore | number | 0-100 |
churnRiskScore | number | 0-100 |
lifetimeValue | number | Total spend (cents) |
lastActivityAt | date | Last interaction |
activityCount30d | number | Activities in 30 days |
tags | array | Customer tags |
From SCL (Synced)
| Field | Type | Description |
|---|---|---|
membershipStatus | string | ACTIVE, INACTIVE, etc. |
membershipTier | string | Gold, Silver, etc. |
memberSince | date | Membership start |
membershipExpiry | date | Membership end |
From TeeTime (Synced)
| Field | Type | Description |
|---|---|---|
handicap | number | Current handicap |
homeClubId | string | Home club |
bookingCount30d | number | Bookings in 30 days |
lastBookingAt | date | Last tee time |
From Messaging (Synced)
| Field | Type | Description |
|---|---|---|
emailOptIn | boolean | Email consent |
smsOptIn | boolean | SMS consent |
pushOptIn | boolean | Push consent |
whatsappOptIn | boolean | WhatsApp consent |
lastEmailOpenAt | date | Last email open |
lastEmailClickAt | date | Last email click |
Segment Refresh
Refresh Frequencies
| Frequency | Use Case |
|---|---|
| Manual | Static segments, one-off |
| Hourly | Time-sensitive campaigns |
| Daily | Regular newsletters |
| Weekly | Monthly reports |
Refresh Process
1. Parse segment rules
2. Build Prisma WHERE clause
3. Query CustomerProfile table
4. Update CustomerSegmentMembership
5. Update memberCount on segment
6. Emit segment.membership.changed events
Messaging Sync
When a segment is used for a campaign, CRM syncs membership to Messaging:
// CRM → Messaging sync
async syncToMessaging(segmentId: string): Promise<void> {
const segment = await this.getSegmentWithMembers(segmentId);
// Create/update segment in Messaging
const messagingSegment = await this.messagingClient.upsertSegment({
externalId: segment.id,
name: segment.name,
contacts: segment.memberships.map(m => ({
email: m.customer.email,
phoneNumber: m.customer.phoneNumber,
firstName: m.customer.firstName,
lastName: m.customer.lastName,
})),
});
// Store reference
await this.updateSegment(segmentId, {
messagingSegmentId: messagingSegment.id,
lastSyncedAt: new Date(),
});
}
System Segments
Pre-defined segments that are auto-managed:
| Segment | Description | Rules |
|---|---|---|
| All Customers | Everyone | status = ACTIVE |
| Active Members | Current members | membershipStatus = ACTIVE |
| High Engagement | Engaged customers | engagementScore >= 70 |
| At Risk | Likely to churn | churnRiskScore >= 70 |
| Email Subscribers | Email opted-in | emailOptIn = true |
| SMS Subscribers | SMS opted-in | smsOptIn = true |
AI Segment Builder
For creating segments using natural language, see the AI Segment Builder which converts queries like:
- "Find platinum members who haven't booked in 60 days"
- "High engagement customers at risk of churning"
- "Email subscribers in South Africa"
Into validated segment criteria JSON.