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 Type | Confidence | Action |
|---|---|---|
| authUserId exact | 100% | Auto-link |
| email exact | 95% | Auto-link |
| phone exact | 85% | Auto-link |
| email + phone | 99% | Auto-link |
| name fuzzy + email domain | 60% | Flag for review |
Activity Timeline
Event Sources
| Source Service | Events | Category |
|---|---|---|
| SCL | member.created, member.updated, tier.changed, payment.received | MEMBERSHIP, FINANCIAL |
| TeeTime | teetime.booked, teetime.completed, handicap.updated, competition.entered | BOOKING, GOLF |
| Messaging | message.sent, message.delivered, email.opened, email.clicked | COMMUNICATION, ENGAGEMENT |
| CRM | note.added, tag.changed, profile.merged, campaign.sent | SUPPORT, 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
| Component | Weight | Data Source | Calculation |
|---|---|---|---|
| Recency | 30% | Last activity | Days since last interaction |
| Frequency | 25% | Activity count | Activities per month |
| Monetary | 20% | SCL payments | Total spend / LTV |
| Breadth | 15% | Channel usage | Unique channels engaged |
| Depth | 10% | Engagement | Open rate, click rate |
Churn Risk Factors
| Risk Factor | Weight | Indicator |
|---|---|---|
| Activity decline | 35% | 50%+ drop in activity |
| Payment issues | 25% | Failed payments, overdue |
| Engagement drop | 20% | Stopped opening emails |
| Tier downgrade | 10% | Recent tier reduction |
| Complaints | 10% | 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
| From | To | Trigger |
|---|---|---|
| SUBSCRIBER | LEAD | First engagement (open, click) |
| LEAD | PROSPECT | Multiple engagements |
| PROSPECT | CUSTOMER | First transaction |
| CUSTOMER | MEMBER | Active membership |
| MEMBER | ADVOCATE | Referral or high engagement |
| Any | CHURNED | No activity 90+ days |