System Architecture
High-level architecture and design decisions for the CRM & Marketing Platform.
Architecture Diagram
┌─────────────────────────────────────────────────────────────────────────────┐
│ EXTERNAL CLIENTS │
│ (Admin UI, Mobile App, Staff Portal, Club Dashboard) │
└─────────────────────────────────────┬───────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ CRM & MARKETING PLATFORM │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ REST/ │ │ Identity │ │ Campaign │ │ Social │ │
│ │ GraphQL │ │ Resolution │ │ Orchestrator│ │ Publisher │ │
│ │ API │ │ Engine │ │ │ │ │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Activity │ │ Engagement │ │ Journey │ │ Event │ │
│ │ Timeline │ │ Scoring │ │ Engine │ │ Promoter │ │
│ │ Aggregator │ │ Engine │ │ │ │ │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │ │ │ │ │
│ └────────────────┴────────────────┴────────────────┘ │
│ │ │
│ ┌───────▼───────┐ │
│ │ CRM Database │ │
│ │ (PostgreSQL) │ │
│ └───────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
│ │ │
▼ ▼ ▼
┌───────────────┐ ┌───────────────┐ ┌───────────────┐
│ Messaging │ │ Social APIs │ │ SCL / TeeTime │
│ Service │ │ (FB, IG, X) │ │ (Events) │
└───────────────┘ └───────────────┘ └───────────────┘
Technology Stack
| Component | Technology | Rationale |
|---|---|---|
| API | NestJS + GraphQL | Consistent with existing services |
| Database | PostgreSQL | Shared SCL database with schema=crm |
| ORM | Prisma | Consistent with existing services |
| Event Transport | Redis + BullMQ | Already in use |
| Cache | Redis | Fast profile lookups |
| Search | PostgreSQL FTS | Start simple |
| Social APIs | Official SDKs | Facebook, Instagram, Twitter |
| Job Scheduling | BullMQ + cron | Campaign scheduling |
Database Architecture
CRM uses the SCL PostgreSQL database with a dedicated crm schema for data isolation.
Why Shared Database?
- Operational simplicity: Single database instance to manage
- Cost efficiency: No separate database provisioning
- Schema isolation: CRM tables are fully isolated in
crmschema - Cross-schema queries: Future potential for direct joins if needed
Connection URL Pattern
The CRM_DATABASE_URL includes the schema parameter:
postgresql://user:pass@scl-db.internal:5432/scl?schema=crm
Prisma Schema Configuration
// libs/prisma/crm-client/prisma/schema.prisma
datasource db {
provider = "postgresql"
url = env("CRM_DATABASE_URL") // Includes ?schema=crm
}
Schema Boundaries
| Schema | Owner | Tables |
|---|---|---|
public | SCL | Members, Clubs, Tiers, Payments |
crm | CRM | CustomerProfiles, Segments, Campaigns, Activities |
Important: CRM should never directly query SCL tables. All data flows via events.
Service Boundaries
| Service | Responsibility | Owns |
|---|---|---|
| CRM/Marketing Platform | WHO, WHAT, WHEN, WHERE | Profiles, Segments, Campaigns, Automation, Social |
| Messaging Service | HOW (delivery) | Templates, Multi-channel delivery, Consent, Providers |
| SCL Service | Membership data | Members, Tiers, Payments |
| TeeTime Service | Golf data | Players, Bookings, Tournaments |
Key Design Decisions
1. 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 │ │ │
└─────────────────────┘ └─────────────────────┘
2. Consent Management
Messaging service owns consent (GDPR/POPIA compliance). CRM denormalizes opt-in flags for query convenience but does NOT manage consent.
3. Event-Driven Sync
All data flows via events from source services. CRM never queries SCL/TeeTime directly.
4. Denormalization
Frequently queried data (activity counts, opt-in flags, golf handicap) denormalized onto CustomerProfile for performance.
Data Flow
Inbound Events
- Source service (SCL/TeeTime/Messaging) publishes event
- Event lands in
crm-eventsBullMQ queue - Worker processes event:
- Resolves identity
- Updates CustomerProfile
- Creates Activity record
- Triggers scoring (async)
- Updates segment membership (async)
Outbound Events
| Event | Trigger | Consumers |
|---|---|---|
crm.profile.created | New profile | Analytics |
crm.profile.merged | Duplicate merge | Messaging |
crm.segment.membership.changed | Segment refresh | Messaging |
crm.churn.risk.high | Risk score > 80 | Alerting |
Performance Requirements
| Metric | Target |
|---|---|
| Profile lookup | Under 100ms p99 |
| Timeline query | Under 500ms p99 |
| Segment refresh | Under 30 seconds for 10k members |
| Campaign scheduling | Under 5 seconds |
| Event processing | Under 5 seconds |