Sales Lead Machine — A CRM Built for Lead Data at Scale
A production multi-tenant CRM platform live at salesleadmachine.uk, engineered for high-velocity sales teams who import messy spreadsheets, run sub-second TSVECTOR searches across millions of records, and launch email & SMS outbound from one unified system.
Search Engine
PostgreSQL TSVECTOR
Schema Safety
JSONB Raw Row Store
Activation
Email, SMS & Expo

Zero Data Loss
Dynamic Column + JSONB Store
Sales Lead Machine is a production CRM platform for importing, searching and working large volumes of people and company data. It runs live at salesleadmachine.uk, supporting sales teams who buy, build and work lead lists at a scale where spreadsheets stop coping.
Its users spend their day finding the right contacts, organising them into lists and reaching out, so the platform is judged on import, search and outbound rather than on dashboards.
Ingestion
Any .XLSX / .CSV Layout
Discovery
Indexed Full-Text Search
Execution
Native Email, SMS & Expo
Challenges Faced
Lead data arriving as spreadsheets in inconsistent formats, with no two files sharing a column layout.
Duplicates accumulating with every import, and no reliable way to detect them before records are written.
Search that has to stay fast across large record volumes rather than degrading as the database grows.
Data that does not fit a fixed schema, and would be lost if forced into one.
Multiple teams needing isolation from one another's data.
Outbound activity — email and SMS — running outside the system that held the contacts.
Event and expo lead capture handled separately from the CRM it should feed.
Our Strategy
Import that copes with real files
Resilient Ingestion & JSONB Preservation
Upload of .xlsx, .xls and .csv with drag and drop
Automatic header detection and dynamic column mapping, so each file is mapped rather than reformatted by hand
A preview before anything is written, and live progress on long imports
Duplicate prevention at import rather than cleanup afterwards
The original row retained as JSONB, so nothing is lost to a schema that did not anticipate it
Search that holds up
Sub-Second Indexed Discovery
PostgreSQL full-text search using TSVECTOR indexes rather than pattern matching
Advanced filtering across people and companies, including industry and size
Custom lists with add and remove, so a working set can be built and kept
Company and contact records linked, so either side can be the starting point
Access control and tenancy
Zero-Leakage Multi-Tenant Security
JWT authentication with password reset and protected routes
Role-based access control, with a permission request flow for exceptions
API key authentication and management for programmatic access
Rate limiting middleware across the API surface
Multi-tenant company isolation, so teams cannot reach one another's records
Outbound and events
Unified Campaign Execution & Expo Capture
Email and SMS outbound driven from the same records that hold the contacts
Dedicated expo and event sales tooling, so leads captured at an event land in the CRM directly
An AI assistant embedded in the interface for in-context help
An admin dashboard with activity logging and user role management
Transformational Results
Lead lists in any spreadsheet layout imported without manual reformatting.
Duplicates prevented at the point of import rather than cleaned up later.
Search that stays usable as record volumes grow, because it is indexed rather than scanned.
Data that does not fit the schema retained rather than discarded.
Teams isolated from one another by the platform rather than by convention.
Outbound activity recorded against the contact it relates to, instead of living in a sent folder.
Most CRM problems are data problems. A platform that makes contacts easy to add but hard to find, or that silently accumulates duplicates, becomes a place data goes rather than a place work happens. The decisions that matter here are unglamorous: full-text indexes rather than pattern matching, duplicate detection at import rather than as a cleanup job, and keeping the original row so a future question can still be answered from it.