Data Model

Version: 1.0.0

This document describes the database schema for notifito, including entity relationships, table structures, and indexes.


Entity Relationship Diagram

erDiagram TENANTS ||--o{ USERS : has TENANTS ||--o{ API_KEYS : has TENANTS ||--o{ ITEM_TYPES : has ITEM_TYPES ||--o{ ITEMS : has ITEM_TYPES ||--o{ ITEM_TYPE_SCHEMA_VERSIONS : has TENANTS ||--o{ CONSUMERS : has CONSUMERS ||--o{ CONTACT_POINTS : has CONSUMERS ||--o{ SUBSCRIPTIONS : has SUBSCRIPTIONS ||--o{ SUBSCRIPTION_TOKENS : has SUBSCRIPTIONS ||--o{ NOTIFICATIONS : has AVAILABILITY_EVENTS ||--o{ NOTIFICATIONS : triggers TENANTS ||--o{ AVAILABILITY_EVENTS : has TENANTS ||--o{ OUTBOX_MESSAGES : has TENANTS { uuid id PK string name string slug UK string timezone jsonb domains string from_name string from_email timestamp created_at timestamp updated_at } USERS { uuid id PK uuid tenant_id FK string name string email string password timestamp email_verified_at timestamp created_at timestamp updated_at } API_KEYS { uuid id PK uuid tenant_id FK string name string kind string prefix UK char hash string reusable_plaintext timestamp last_used_at timestamp revoked_at timestamp created_at timestamp updated_at } ITEM_TYPES { uuid id PK uuid tenant_id FK string name string slug jsonb attribute_schema int schema_version enum notify_policy int notify_factor enum subscription_mode string action_url_template timestamp created_at timestamp updated_at } ITEMS { uuid id PK uuid tenant_id FK uuid item_type_id FK string name string external_ref string action_url timestamp created_at timestamp updated_at } CONSUMERS { uuid id PK uuid tenant_id FK timestamp created_at timestamp updated_at } CONTACT_POINTS { uuid id PK uuid tenant_id FK uuid consumer_id FK enum channel string value timestamp verified_at timestamp unsubscribed_at timestamp invalidated_at string invalidation_detail timestamp created_at timestamp updated_at } CONSENT_EVENTS { uuid id PK uuid tenant_id FK uuid contact_point_id FK uuid subscription_id enum action enum source string ip_address string user_agent timestamp created_at } SUBSCRIPTIONS { uuid id PK uuid tenant_id FK uuid consumer_id FK uuid contact_point_id FK uuid item_type_id FK uuid item_id FK jsonb attributes int schema_version jsonb channels string fingerprint enum status timestamp last_notified_at timestamp cooldown_until timestamp expires_at timestamp created_at timestamp updated_at } SUBSCRIPTION_TOKENS { uuid id PK uuid tenant_id FK uuid subscription_id FK uuid contact_point_id FK enum purpose char token_hash string reusable_plaintext timestamp expires_at timestamp used_at timestamp created_at } AVAILABILITY_EVENTS { uuid id PK uuid tenant_id FK uuid item_type_id FK uuid item_id FK jsonb attributes int capacity timestamp available_from timestamp available_until enum source string idempotency_key UK string correlation_id timestamp occurred_at timestamp created_at } NOTIFICATIONS { uuid id PK uuid tenant_id FK uuid subscription_id FK uuid availability_event_id FK uuid contact_point_id FK uuid delivery_id enum status timestamp created_at timestamp updated_at } OUTBOX_MESSAGES { uuid id PK string topic jsonb payload string correlation_id timestamp published_at string error timestamp created_at }

Tenancy Tables

tenants

Seller organization. Every other table carries a tenant_id.

Column Type Constraints Description
id uuid PK Unique identifier
name string NOT NULL Display name
slug string UNIQUE, NOT NULL URL-friendly identifier
timezone string DEFAULT 'UTC' Seller's timezone
domains jsonb DEFAULT '[]' Registered domains for widget CORS
from_name string NOT NULL Email sender display name
from_email string NOT NULL Email sender address
created_at timestamp NOT NULL Creation timestamp
updated_at timestamp NOT NULL Last update timestamp

Indexes: - tenants_slug_unique — UNIQUE on slug


users

Seller staff members, scoped to a tenant.

Column Type Constraints Description
id uuid PK Unique identifier
tenant_id uuid FK → tenants, CASCADE DELETE Owning tenant
name string NOT NULL Display name
email string NOT NULL Email address
password string NOT NULL Hashed password
email_verified_at timestamp NULLABLE Email verification timestamp
remember_token string NULLABLE Remember me token
created_at timestamp NOT NULL Creation timestamp
updated_at timestamp NOT NULL Last update timestamp

Indexes: - users_tenant_id_email_unique — UNIQUE on (tenant_id, email)


api_keys

Authentication keys for widget and push API.

Column Type Constraints Description
id uuid PK Unique identifier
tenant_id uuid FK → tenants, CASCADE DELETE Owning tenant
name string NOT NULL Key name (e.g., "Widget key")
kind varchar(16) NOT NULL publishable or secret
prefix varchar(16) UNIQUE, NOT NULL First 16 chars for lookup
hash char(64) NOT NULL SHA-256 hash of full key
reusable_plaintext varchar(64) NULLABLE Plaintext for dashboard key only
last_used_at timestamp NULLABLE Last usage timestamp
revoked_at timestamp NULLABLE Revocation timestamp
created_at timestamp NOT NULL Creation timestamp
updated_at timestamp NOT NULL Last update timestamp

Indexes: - api_keys_prefix_unique — UNIQUE on prefix - api_keys_tenant_id_kind_index — on (tenant_id, kind)

Key Format: - Publishable: pk_live_<24 random chars> (total 32 chars) - Secret: sk_live_<24 random chars> (total 32 chars) - Prefix: First 16 chars used for indexed lookup - Hash: SHA-256 of full plaintext, compared in constant time


Catalog Tables

item_types

Product/service types defined by sellers.

Column Type Constraints Description
id uuid PK Unique identifier
tenant_id uuid FK → tenants, CASCADE DELETE Owning tenant
name string NOT NULL Display name
slug string NOT NULL URL-friendly identifier
attribute_schema jsonb NOT NULL Ordered list of attribute definitions
schema_version integer DEFAULT 1 Current schema version
notify_policy varchar(32) DEFAULT 'all' all or capacity_multiple
notify_factor integer DEFAULT 3 Multiplier for capacity policy
subscription_mode varchar(32) DEFAULT 'once' once or recurring
action_url_template string NULLABLE URL template for call-to-action
created_at timestamp NOT NULL Creation timestamp
updated_at timestamp NOT NULL Last update timestamp

Indexes: - item_types_tenant_id_slug_unique — UNIQUE on (tenant_id, slug)

attribute_schema Structure:

[
  {
    "key": "size",
    "label": "Size",
    "type": "enum",
    "options": ["40", "41", "42"],
    "required": true,
    "matchable": true,
    "multiple": false
  }
]

items

Named instances of item types (optional).

Column Type Constraints Description
id uuid PK Unique identifier
tenant_id uuid FK → tenants, CASCADE DELETE Owning tenant
item_type_id uuid FK → item_types, CASCADE DELETE Parent type
name string NOT NULL Display name
external_ref string NULLABLE External reference (e.g., SKU)
action_url string NULLABLE Direct link to item
created_at timestamp NOT NULL Creation timestamp
updated_at timestamp NOT NULL Last update timestamp

Indexes: - items_item_type_id_external_ref_unique — UNIQUE on (item_type_id, external_ref) WHERE external_ref IS NOT NULL


item_type_schema_versions

Archive of previous schema versions.

Column Type Constraints Description
id uuid PK Unique identifier
item_type_id uuid FK → item_types, CASCADE DELETE Parent type
version integer NOT NULL Version number
definitions jsonb NOT NULL Schema at this version
created_at timestamp NOT NULL Creation timestamp

Indexes: - item_type_schema_versions_item_type_id_version_unique — UNIQUE on (item_type_id, version)


People and Consent Tables

consumers

End users waiting for notifications, scoped to a tenant.

Column Type Constraints Description
id uuid PK Unique identifier
tenant_id uuid FK → tenants, CASCADE DELETE Owning tenant
created_at timestamp NOT NULL Creation timestamp
updated_at timestamp NOT NULL Last update timestamp

contact_points

Email/phone/WhatsApp addresses for consumers.

Column Type Constraints Description
id uuid PK Unique identifier
tenant_id uuid FK → tenants, CASCADE DELETE Owning tenant
consumer_id uuid FK → consumers, CASCADE DELETE Owning consumer
channel varchar(16) NOT NULL email, sms, whatsapp, voice
value string NOT NULL Normalized contact value
verified_at timestamp NULLABLE Verification timestamp
unsubscribed_at timestamp NULLABLE Unsubscription timestamp
invalidated_at timestamp NULLABLE Invalidation timestamp (hard bounce)
invalidation_detail string NULLABLE Reason for invalidation
created_at timestamp NOT NULL Creation timestamp
updated_at timestamp NOT NULL Last update timestamp

Indexes: - contact_points_tenant_id_channel_value_unique — UNIQUE on (tenant_id, channel, value)

Sendable Condition: - verified_at IS NOT NULL - unsubscribed_at IS NULL - invalidated_at IS NULL


consent_events

Append-only audit trail for consent changes.

Column Type Constraints Description
id uuid PK Unique identifier
tenant_id uuid FK → tenants, CASCADE DELETE Owning tenant
contact_point_id uuid FK → contact_points, CASCADE DELETE Related contact point
subscription_id uuid NULLABLE Related subscription
action varchar(16) NOT NULL granted or withdrawn
source varchar(32) NOT NULL widget, confirmation_link, unsubscribe_link, dashboard
ip_address string NULLABLE Client IP address
user_agent string NULLABLE Client user agent
created_at timestamp NOT NULL Event timestamp

Constraints: - Append-only: No UPDATE or DELETE operations allowed (enforced by database trigger)


Subscription Tables

subscriptions

Active waiting subscriptions.

Column Type Constraints Description
id uuid PK Unique identifier
tenant_id uuid FK → tenants, CASCADE DELETE Owning tenant
consumer_id uuid FK → consumers, CASCADE DELETE Owning consumer
contact_point_id uuid FK → contact_points Contact point
item_type_id uuid FK → item_types Target item type
item_id uuid FK → items, NULLABLE Target item (optional)
attributes jsonb NOT NULL Subscription attributes
schema_version integer NOT NULL Schema version at capture
channels jsonb DEFAULT '["email"]' Notification channels
fingerprint char(64) NOT NULL Deduplication hash
status varchar(32) NOT NULL Subscription status
last_notified_at timestamp NULLABLE Last notification timestamp
cooldown_until timestamp NULLABLE Cooldown expiry
expires_at timestamp NOT NULL Subscription expiry (90 days)
created_at timestamp NOT NULL Creation timestamp
updated_at timestamp NOT NULL Last update timestamp

Indexes: - subscriptions_tenant_id_fingerprint_unique — UNIQUE on (tenant_id, fingerprint) - subscriptions_tenant_id_item_type_id_status_index — on (tenant_id, item_type_id, status) - subscriptions_attributes_gin — GIN on attributes

Status Flow:

pending_confirmation → active → notified
                     → expired
                     → unsubscribed

Fingerprint: SHA-256 hash of normalized attributes, used to prevent duplicate subscriptions.


subscription_tokens

Confirmation and unsubscribe tokens.

Column Type Constraints Description
id uuid PK Unique identifier
tenant_id uuid FK → tenants, CASCADE DELETE Owning tenant
subscription_id uuid FK → subscriptions, CASCADE DELETE Related subscription
contact_point_id uuid FK → contact_points Related contact point
purpose varchar(16) NOT NULL confirm or unsubscribe
token_hash char(64) NOT NULL SHA-256 hash of plaintext
reusable_plaintext varchar(64) NULLABLE Plaintext for reusable tokens
expires_at timestamp NULLABLE Token expiry (72h for confirm)
used_at timestamp NULLABLE Usage timestamp
created_at timestamp NOT NULL Creation timestamp

Token Rules: - Confirm: Single-use, expires after 72 hours - Unsubscribe: Reusable, never expires, stored in plaintext


Event Tables

availability_events

Seller availability signals.

Column Type Constraints Description
id uuid PK Unique identifier
tenant_id uuid FK → tenants, CASCADE DELETE Owning tenant
item_type_id uuid FK → item_types Target item type
item_id uuid FK → items, NULLABLE Target item (optional)
attributes jsonb NOT NULL Event attributes
capacity integer NULLABLE Available capacity
available_from timestamp NULLABLE Availability window start
available_until timestamp NULLABLE Availability window end
source varchar(32) NOT NULL push_api, dashboard, scraper, schedule
idempotency_key string UNIQUE, NOT NULL Deduplication key
correlation_id string NOT NULL Request correlation ID
occurred_at timestamp NOT NULL Event timestamp
created_at timestamp NOT NULL Creation timestamp

Indexes: - availability_events_idempotency_key_unique — UNIQUE on idempotency_key


notifications

Sent notification records.

Column Type Constraints Description
id uuid PK Unique identifier
tenant_id uuid FK → tenants, CASCADE DELETE Owning tenant
subscription_id uuid FK → subscriptions Related subscription
availability_event_id uuid FK → availability_events Triggering event
contact_point_id uuid FK → contact_points Target contact point
delivery_id uuid NULLABLE Related delivery
status varchar(32) NOT NULL sent, delivered, failed
created_at timestamp NOT NULL Creation timestamp
updated_at timestamp NOT NULL Last update timestamp

Constraints: - notifications_subscription_id_availability_event_id_unique — UNIQUE on (subscription_id, availability_event_id)


outbox_messages

Transactional outbox for reliable event publishing.

Column Type Constraints Description
id uuid PK Unique identifier
topic string NOT NULL Event topic
payload jsonb NOT NULL Event payload
correlation_id string NOT NULL Request correlation ID
published_at timestamp NULLABLE Publication timestamp
error string NULLABLE Last error message
created_at timestamp NOT NULL Creation timestamp

Indexes: - outbox_messages_published_at_index — on published_at WHERE published_at IS NULL


Dispatch Tables (apps/dispatch database)

deliveries

Delivery state tracking.

Column Type Constraints Description
id uuid PK Unique identifier
tenant_id uuid NOT NULL Owning tenant
subscription_id uuid NOT NULL Related subscription
contact_point_id uuid NOT NULL Target contact point
channel varchar(16) NOT NULL Delivery channel
status varchar(32) NOT NULL Delivery status
provider_message_id string NULLABLE Provider's message ID
dedupe_key string UNIQUE, NOT NULL Deduplication key
error string NULLABLE Error message
attempt integer DEFAULT 1 Retry attempt count
queued_at timestamp NOT NULL Queue timestamp
sending_at timestamp NULLABLE Send start timestamp
accepted_at timestamp NULLABLE Provider acceptance timestamp
delivered_at timestamp NULLABLE Delivery confirmation timestamp
failed_at timestamp NULLABLE Failure timestamp
bounced_at timestamp NULLABLE Bounce timestamp
created_at timestamp NOT NULL Creation timestamp
updated_at timestamp NOT NULL Last update timestamp

Status Flow:

queued → sending → accepted → delivered
                 → failed
                 → bounced

Related Documentation