Skip to main content

Core Concepts

This document covers the fundamental concepts of the CRM & Marketing Platform.


CustomerProfile

The central entity representing a single customer across all systems.

interface CustomerProfile {
id: string; // CRM's own UUID
tenantId: string; // Multi-tenant isolation

// Identity Keys (at least one required)
authUserId?: string; // IDP user ID (strongest link)
email?: string; // Normalized email
phoneNumber?: string; // E.164 phone

// Cross-Service Links
sclMemberId?: number; // SCL.Member.id
teetimePlayerId?: string; // TeeTime.Player.id
messagingContactId?: string; // Messaging.Contact.id

// External IDs
externalIds: {
mcaV1ZaId?: string; // MCA v1 ZA ID
mcaV1UkId?: string; // MCA v1 UK ID
golfRsaId?: string; // GolfRSA ID
};

// Denormalized Profile
firstName?: string;
lastName?: string;
displayName?: string;

// CRM Data
status: CustomerStatus;
lifecycleStage: LifecycleStage;
tags: string[];

// Computed Scores
engagementScore?: number; // 0-100
churnRiskScore?: number; // 0-100
lifetimeValue?: number; // Cents

// Activity Metrics (denormalized)
lastActivityAt?: Date;
activityCount30d: number;
bookingCount30d: number;

// Golf Data (from TeeTime)
handicap?: number;
homeClubId?: string;

// Membership Data (from SCL)
membershipTier?: string;
membershipStatus?: string;

// Marketing Consent (synced from Messaging - READ ONLY)
emailOptIn: boolean;
smsOptIn: boolean;
pushOptIn: boolean;
whatsappOptIn: boolean;
}

Activity

A single interaction or event in the customer's timeline.

interface Activity {
id: string;
customerId: string;

type: ActivityType; // e.g., TEETIME_BOOKED, MESSAGE_SENT
category: ActivityCategory; // e.g., BOOKING, COMMUNICATION
channel?: string; // e.g., SMS, EMAIL, IN_PERSON

title: string; // Human-readable summary
description?: string;
metadata?: object;

sourceService: string; // scl, teetime, messaging
sourceId?: string; // Original record ID

occurredAt: Date;
}

Identity Resolution

The Challenge

A single person may exist as:

  • Member in SCL (joined via membership signup)
  • Player in TeeTime (booked as a guest)
  • Contact in Messaging (received a campaign)

We need to determine: Are these the same person?

Resolution Strategy

┌─────────────────────────────────────────────────────────────────┐
│ IDENTITY RESOLUTION FLOW │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Incoming Event (e.g., member.created) │
│ │ │
│ ▼ │
│ ┌───────────────────────────────────────┐ │
│ │ 1. Extract identifiers │ │
│ │ - authUserId (if present) │ │
│ │ - email (normalized) │ │
│ │ - phoneNumber (E.164) │ │
│ └───────────────────┬───────────────────┘ │
│ │ │
│ ▼ │
│ ┌───────────────────────────────────────┐ │
│ │ 2. Match by authUserId (exact) │◄─── Strongest │
│ └───────────────────┬───────────────────┘ │
│ │ No match? │
│ ▼ │
│ ┌───────────────────────────────────────┐ │
│ │ 3. Match by email (normalized) │◄─── Strong │
│ └───────────────────┬───────────────────┘ │
│ │ No match? │
│ ▼ │
│ ┌───────────────────────────────────────┐ │
│ │ 4. Match by phone (E.164) │◄─── Moderate │
│ └───────────────────┬───────────────────┘ │
│ │ No match? │
│ ▼ │
│ ┌───────────────────────────────────────┐ │
│ │ 5. Create new CustomerProfile │ │
│ └───────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘

Match Confidence

Match TypeConfidenceAction
authUserId exact100%Auto-link
email exact95%Auto-link
phone exact85%Auto-link
email + phone99%Auto-link
name fuzzy + email domain60%Flag for review

Activity Timeline

Event Sources

Source ServiceEventsCategory
SCLmember.created, member.updated, tier.changed, payment.receivedMEMBERSHIP, FINANCIAL
TeeTimeteetime.booked, teetime.completed, handicap.updated, competition.enteredBOOKING, GOLF
Messagingmessage.sent, message.delivered, email.opened, email.clickedCOMMUNICATION, ENGAGEMENT
CRMnote.added, tag.changed, profile.merged, campaign.sentSUPPORT, MARKETING, SYSTEM

Activity Categories

enum ActivityCategory {
MEMBERSHIP // Tier changes, signup, cancellation
BOOKING // Tee times, facility bookings
COMMUNICATION // Messages sent/received
ENGAGEMENT // Opens, clicks, responses
FINANCIAL // Payments, invoices
GOLF // Handicap, competitions, scores
MARKETING // Campaign interactions
SUPPORT // Notes, tickets
SOCIAL // Social media interactions
SYSTEM // Merges, imports, syncs
}

Engagement Scoring

Score Components

ComponentWeightData SourceCalculation
Recency30%Last activityDays since last interaction
Frequency25%Activity countActivities per month
Monetary20%SCL paymentsTotal spend / LTV
Breadth15%Channel usageUnique channels engaged
Depth10%EngagementOpen rate, click rate

Churn Risk Factors

Risk FactorWeightIndicator
Activity decline35%50%+ drop in activity
Payment issues25%Failed payments, overdue
Engagement drop20%Stopped opening emails
Tier downgrade10%Recent tier reduction
Complaints10%Support tickets, negative feedback

Lifecycle Stages

enum LifecycleStage {
SUBSCRIBER // Email only
LEAD // Showed interest
PROSPECT // Engaged
CUSTOMER // Has transacted
MEMBER // Active member
ADVOCATE // Refers others
CHURNED // Lapsed
}

Stage Transitions

FromToTrigger
SUBSCRIBERLEADFirst engagement (open, click)
LEADPROSPECTMultiple engagements
PROSPECTCUSTOMERFirst transaction
CUSTOMERMEMBERActive membership
MEMBERADVOCATEReferral or high engagement
AnyCHURNEDNo activity 90+ days