Skip to main content

Data Sync Operations

How the CRM processes events from source services.

Event Architecture

┌───────────────────────────────────────────────────────────────────┐
│ SOURCE SERVICES │
├───────────────┬───────────────────┬───────────────────────────────┤
│ SCL Service │ TeeTime Service │ Messaging Service │
│ DomainOutbox │ DomainOutbox │ EventEmitter │
└───────┬───────┴─────────┬─────────┴───────────────┬───────────────┘
│ │ │
│ member.* │ player.* │ contact.*
│ tier.* │ teetime.* │ message.*
│ payment.* │ handicap.* │ consent.*
└─────────────────┼─────────────────────────┘


┌─────────────────────────────────────┐
│ REDIS / BULLMQ │
│ Queue: crm-events │
└─────────────────┬───────────────────┘


┌─────────────────────────────────────┐
│ CRM WORKER │
│ 1. Parse event │
│ 2. Resolve identity │
│ 3. Update profile │
│ 4. Create activity │
│ 5. Trigger scoring │
│ 6. Update segments │
└─────────────────────────────────────┘

Events Consumed

From SCL

EventCRM Action
member.createdCreate/link profile, activity
member.updatedUpdate profile, activity
tier.changedUpdate tier, activity, rescore
payment.receivedUpdate LTV, activity
payment.failedActivity, flag for follow-up

From TeeTime

EventCRM Action
player.createdCreate/link profile, activity
teetime.bookedActivity, update frequency
teetime.completedActivity, trigger review journey
competition.publishedAuto-promote (if enabled)
handicap.updatedUpdate handicap, activity

From Messaging

EventCRM Action
message.sentActivity
message.deliveredActivity
email.openedActivity, engagement score
link.clickedActivity, engagement score
consent.changedUpdate opt-in flags

Monitoring Queue

Check Queue Depth

redis-cli LLEN crm-events

Check Processing Rate

kubectl logs -l app=crm-worker -n crm | grep "processed"

View Failed Events

SELECT id, event_type, error_message, retry_count, created_at
FROM "SyncLog"
WHERE status = 'FAILED'
ORDER BY created_at DESC
LIMIT 20;

Retry Failed Events

-- Retry recent failures
UPDATE "SyncLog"
SET status = 'PENDING', retry_count = 0
WHERE status = 'FAILED'
AND created_at > now() - interval '1 hour';

Idempotency

Events are deduplicated by event ID:

-- Check if event was already processed
SELECT * FROM "SyncLog" WHERE event_id = 'event-uuid-here';

Backpressure

When queue is too deep:

  1. Scale workers: kubectl scale deployment/crm-worker --replicas=4 -n crm
  2. Check for stuck events
  3. Monitor processing rate

Event Envelope

All events follow this format:

interface CrmEvent {
id: string; // Unique event ID
type: string; // e.g., "scl.member.created"
version: string; // Schema version
timestamp: string; // ISO 8601
tenantId: string;
source: string; // Source service
identity: {
authUserId?: string;
email?: string;
phoneNumber?: string;
sclMemberId?: number;
teetimePlayerId?: string;
messagingContactId?: string;
};
payload: Record<string, unknown>;
}