Building the Foundation: Lead & Contact Data Schema in v1.0.1
Building the Foundation: Lead & Contact Data Schema in v1.0.1
v1.0.1 ships the core data layer for NurtureHub. Before any email can be generated, any lead score computed, or any CRM sync triggered, the platform needs a well-defined model for contacts, their categories, and how they relate to the CRMs agents already use. That is what this release delivers.
The 12-Category Contact Model
Every contact in NurtureHub is assigned to one of twelve predefined categories. These categories reflect the full range of relationships a UK property agent manages:
| Category | Description |
|---|---|
seller | Prospective seller not yet on market |
buyer | Prospective buyer not yet under offer |
landlord | Prospective landlord not yet managed |
tenant_applicant | Applicant not yet in a tenancy |
property_investor | Investor prospect |
btl_investor | Buy-to-let investor prospect |
active_seller | Vendor with a live instruction |
active_buyer | Buyer with an accepted offer or under offer |
managed_landlord | Landlord with a managed property |
active_tenant | Current tenant in an active tenancy |
active_property_investor | Investor with an active relationship |
active_btl_investor | BTL investor with an active relationship |
Contacts are additionally grouped as either prospect or existing_client, allowing segmentation across all twelve categories.
Assignment History
Category assignments are not simply a field on the contact record. They are stored in a dedicated lead_category_assignments table with full history. When a contact is re-categorised, the previous assignment is marked with supersededAt and a new active assignment is created. This means you always have a complete audit trail of how a contact's status evolved over time.
GDPR-First Contact Storage
Every contact record carries:
consentStatus— one ofgranted,denied,unknown, orpendingconsentTimestamp— when consent was recordedimportSource— how the contact entered NurtureHub (crm_sync,manual,apify, orland_registry)
The contacts.delete tRPC procedure performs a hard delete of all personal data and is restricted to org admins only. This is the compliance endpoint for handling data subject erasure requests under UK GDPR.
The raw CRM payload is preserved on the contact record for debugging and re-sync purposes, but is never exposed in URLs or logs.
CRM Integration Architecture
The release introduces two tables that together model the full lifecycle of a CRM integration.
crm_providers — The CRM Registry
This table is a static registry of the CRM platforms NurtureHub supports. The initial seed populates five entries:
| Provider | Auth Type |
|---|---|
| agentOS | API key / webhook push |
| Reapit | OAuth2 |
| Alto | API key |
| Street | API key |
| Loop | API key |
Each provider record includes its supported auth type and webhook path.
crm_connections — Per-Tenant Connections
When an agent connects their CRM, a row is created in crm_connections scoped to their tenantId (organisation). Key fields:
encryptedCredentials— API keys or OAuth tokens stored AES-256 encrypted at rest.syncCursor(jsonb) — Provider-specific delta cursor, enabling incremental sync rather than full re-fetches.status— One ofactive,paused,error, orpending.- Error tracking — Last error message and timestamp for surfacing connection health in the UI.
crm_writeback_queue — Outbound Activity Sync
NurtureHub writes engagement events back to the source CRM so that agents have a full view without leaving their existing tool. The queue handles six event types:
email_sentemail_openedemail_clickedlead_score_updatejourney_completedhot_alert_fired
Each queue entry tracks its own processing status (pending → processing → success / failed), enabling retries and observability.
The contacts tRPC Router
All contact operations are exposed through a type-safe tRPC router. Here is a summary of every available procedure:
contacts.list — paginated list with filters
contacts.get — single contact + active assignment
contacts.create — create with optional category assignment
contacts.update — update; handles re-categorisation history
contacts.assignCategory — explicit (re)assignment
contacts.getCategoryHistory — full assignment history
contacts.delete — GDPR hard-delete (admin only)
contacts.listCrmProviders — available CRM providers for connection UI
Every mutation produces an audit log entry. The delete procedure is guarded by adminProcedure — only org owners and admins can invoke it.
Running the CRM Provider Seed
After running your initial database migration, populate the crm_providers table with the five supported integrations:
npx tsx src/db/seeds/crm-providers.ts
This is a one-time operation. The seed is idempotent — running it again will not create duplicate rows.
What This Enables
With this data layer in place, subsequent releases can build directly on top of it:
- Nurture sequence generation — the AI email writer reads the contact's category to tailor content.
- Lead scoring — score updates are written back through the
crm_writeback_queue. - Hot lead alerts — the
hot_alert_firedevent type is already wired into the writeback queue. - CRM sync — incremental sync via
syncCursorkeeps contact data current without full re-fetches.