Skip to main content

Architecture

GTM Clarity is a multi-tenant B2B SaaS scoring platform built with domain-driven design principles. This page covers the system architecture, data flow, and key design decisions.

System Overview

Tech Stack

LayerTechnologyPurpose
FrontendNext.js 16.1.6, React 19.2.3, Tailwind CSS 4, Recharts 3.7Server-rendered UI with App Router
APItRPC 11.10Type-safe RPC between frontend and backend
AuthClerk 6.39Multi-tenant authentication and RBAC
DatabasePostgreSQL (Neon) + Drizzle ORM 0.45.1Serverless PostgreSQL with type-safe ORM
WorkflowsTemporal.io 1.15Durable workflow orchestration for syncs
CRMJSForce 3.10Salesforce API integration
ValidationZod 4.3Runtime type validation at boundaries
TestingVitest 4.0, Playwright 1.58Unit and end-to-end testing

Domain-Driven Design

The codebase is organized into bounded contexts, each with clear responsibilities and minimal coupling:

Context Boundaries

ContextResponsibilityCoupling Rules
ConnectorsExternal data ingestion, normalizationWrites to DB via canonical schema only
ScoringPure-function score computationNo DB access, no side effects, no API calls
Buying GroupsRole detection, completeness, discoveryPure functions; DB access only via server layer
ServerDB access, API routing, middlewareGateway to all contexts
Key Design Principle

The scoring engine is completely pure -- it takes data in and returns scores. It never touches the database, makes API calls, or has side effects. This makes it trivially testable and deterministic.

Multi-Tenancy Strategy

GTM Clarity uses row-level tenant isolation. Every table includes a tenant_id column, and every query must filter by it:

The tenantColumns helper from src/server/db/schema/base.ts ensures every table definition includes the tenant_id column:

export const tenantColumns = {
tenantId: text("tenant_id").notNull(),
};

Key invariants:

  • No cross-tenant queries -- every SELECT, UPDATE, DELETE includes tenant_id
  • Clerk organization = tenant -- the clerk_org_id maps to one tenant row
  • Indexes include tenant_id -- all composite indexes lead with tenant_id for query performance

Data Flow

Inbound (Connector Sync)

Outbound (Score Writeback)

Scores computed by the engine can be pushed back to the source CRM:

  1. Writeback workflow reads scored entities from the engagements table
  2. Maps canonical score fields to CRM-specific fields via writeback_configs
  3. Updates CRM records via the connector's API (e.g., Salesforce custom fields)

Scoring Pipeline

Security Architecture

LayerMechanism
AuthenticationClerk (session tokens, SSO)
AuthorizationRBAC middleware (org:admin, org:manager, org:viewer)
Tenant isolationRow-level filtering on tenant_id
Token storageAES-256-GCM encryption at rest (CONNECTOR_ENCRYPTION_KEY)
AuditAppend-only audit_log for all mutations
Input validationZod schemas at all system boundaries
Server isolationserver-only imports prevent client bundling of secrets

Directory Structure

src/
app/ # Next.js App Router (pages, layouts, API routes)
components/ # React UI components by domain
connectors/ # Connector framework + implementations
salesforce/ # Salesforce connector
temporal/ # Temporal workflow orchestration
buying-groups/ # Buying group intelligence (pure functions)
scoring/ # Pure-function scoring engine
fit/ # ICP match scoring
engagement/ # Behavioral engagement with decay
rollup/ # Account/opportunity aggregation
server/ # Backend infrastructure
db/ # Drizzle ORM + schema (17 tables)
trpc/ # tRPC routers + middleware
lib/ # Shared utilities
types/ # TypeScript type definitions