Event-Driven Notification Platform  ·  View 12 of 26  ·  3 · Data

Core Data Model

The entities a decision reads and writes, and how they join.

Editable source SVG draw.io All views
tenant
tenant_id PK
name
tier
quota_events_sec
home_region
tenant...
event
event_id PK
tenant_id FK
event_type
source_app
subject_ref
correlation_id
priority
idempotency_key UQ
occurred_at
event...
notification_rule
rule_id PK
tenant_id FK
event_type
condition_cel
channels
enabled
version
notification_rule...
template
template_id PK
tenant_id FK
code
channel
state
template...
recipient
recipient_id PK
tenant_id FK
subject_ref UQ
locale
timezone
consent_state
recipient...
notification
notification_id PK
event_id FK
rule_id FK
recipient_id FK
channel
state
dedup_key UQ
template_version_id FK
scheduled_for
notification...
schedule
schedule_id PK
notification_id FK
fire_at
cron_expr
digest_window
schedule...
template_version
version_id PK
template_id FK
semver
locale
body_ref
published_at
template_version...
user_preference
preference_id PK
recipient_id FK
category
channel
enabled
quiet_start
quiet_end
frequency_cap
user_preference...
delivery_attempt
attempt_id PK
notification_id FK
provider_id FK
attempt_no
status
provider_ref
error_class
latency_ms
attempted_at
delivery_attempt...
channel_provider
provider_id PK
tenant_id FK
channel
vendor
rank
credential_path
tps_limit
channel_provider...
audit_record
audit_id PK
tenant_id FK
actor
action
target_ref
before_after
occurred_at
audit_record...
1 : N
1 : N
1 : N
1 : N
1 : N
1 : N
1 : N
1 : N
1 : N
1 : N
1 : N
1 : N
1 : N
1 : N
1 : 0..1
1 : 0..1
1 : 1
1 : 1
1 : N
1 : N
1 : N
1 : N
1 : N
1 : N
Core Data Model
Core Data Model
Three domains, deliberately separate: event is what happened, notification is what the platform decided, delivery_attempt is what happened while trying. Every table carries tenant_id and is protected by row-level security; those relationships are omitted for readability.
Three domains, deliberately separate: event is what happened, notification is what the platform decided, delivery_attempt is what happened while trying. Every table carries tenant_id and is protected by row-level security; those relationships are omitted for readability.
v 1.0 · owner Data & AI Global Practice · date 2026-08
v 1.0 · owner Data & AI Global Practice · date 2026-08
Text is not SVG - cannot display

Decisions

  • notification carries template_version_id, not template_id, so what was sent is always reproducible
  • dedup_key is unique and is the last line of defence when Redis and Flink state both miss
  • delivery_attempt is a separate high-volume table in ClickHouse, joined by notification_id rather than by foreign key

Multi-tenancy in the schema

  • Every table carries tenant_id; those relationships are omitted from the diagram for readability
  • Row-level security is enabled on every PostgreSQL table and the session tenant is set from the verified JWT claim
  • The application role has no BYPASSRLS, so a query bug cannot cross tenants

Deliberate omissions

  • Contact values live in the PII vault, keyed by recipient_id, and are not modelled here
  • Indexes, partition keys and the notification state enumeration are in the physical model, not this view