Skip to content
Sales & Marketing Built by us

Custom CRM

A sales pipeline shaped around how your team actually sells — for companies whose process does not fit a generic CRM, and who would rather own the code than rent the seats.

The pipeline board. Stages are defined per team, and deals ageing past their stage SLA surface before the weekly review rather than during it.

Who it's for

Built for a specific kind of business

Sales teams of roughly 5 to 50 people who have hit the wall on a generic CRM — either on cost per seat, or on the fact that their sales process genuinely does not look like the one the tool assumes. Distributors with territory rules, agencies with multi-stage proposals, and B2B teams with long approval chains are the common cases.

Business type
B2B sales team, distributor, agency, services firm
Team size
5–50 sales users
Trigger
Per-seat cost, or a process that will not fit the tool
Runs today on
HubSpot, Salesforce, Zoho — or a shared spreadsheet and inboxes
Decision maker
Sales director, COO, or founder

The problem

What this actually costs you today

In the words operators use, not the words software vendors use.

  • Leads arrive in six places and get lost in four of them

    Website form to one inbox, WhatsApp to a rep's phone, a trade-show list in a spreadsheet, referrals mentioned in a meeting. There is no single arrival point, so there is no way to know what the actual conversion rate is.

  • Nobody trusts the pipeline number

    Deals sit in stages long after they died because closing them as lost is a small admission of failure. The forecast is consequently optimistic in a way everyone knows about and nobody corrects.

  • Reps hate data entry, so the data is bad

    Every CRM asks for information that benefits the manager and costs the rep. When logging a call takes ninety seconds, calls do not get logged, and the activity history that reporting depends on is fiction.

  • Reporting is a monthly manual export

    Someone pulls a CSV, pivots it, and emails a deck. By the time it is read it is three weeks stale, and nobody can drill into a number to see what it is made of.

  • The generic CRM cannot express your actual process

    Territory rules, split commissions, distributor tiers, approval chains, quote versioning — the things that make your business specific are exactly the things a generic CRM makes you work around with custom fields and discipline.

What it does

Grouped by who uses it

Feature lists organised by technical module tell you nothing about whether the software fits your team. These are grouped by the person who lives in them.

Lead capture and routing

Marketing

One arrival point for every lead, whatever channel it came from, with assignment that happens automatically rather than in a morning meeting.

  • Website form endpoint with spam filtering
  • Email parsing from a shared inbox
  • Bulk import with duplicate detection against existing records
  • REST API and webhooks for any other source
  • Assignment rules by territory, product line, round-robin or load
  • Response-time SLA tracking from arrival to first contact

Contact and company records

Sales rep

The account model most B2B teams actually need — companies with several contacts, several open deals, and a relationship history that outlives any one of them.

  • Company hierarchies — parent, subsidiary, branch
  • Multiple contacts per company with roles and decision-maker flags
  • Custom fields defined per record type, without a developer
  • Merge and de-duplication with a full history of what was merged
  • Complete relationship timeline across deals, activities and documents

Pipeline and deal management

Sales rep

Stages that describe your process, not a vendor's idea of one.

  • Multiple pipelines, each with its own stages, for different products or segments
  • Kanban board and table views over the same data
  • Weighted value by stage probability, adjustable per pipeline
  • Stage-entry requirements — a deal cannot reach Proposal without a decision maker
  • Deal ageing and stalled-deal detection against a per-stage SLA
  • Loss reasons as a controlled list, so lost-deal analysis is possible

Activity and communication log

Sales rep

Designed around the fact that reps will only log what takes seconds. Everything that can be captured automatically is.

  • Calls, meetings, emails and notes on one timeline
  • Two-way email sync, so correspondence appears without copy-paste
  • Logging from mobile in a couple of taps
  • Automatic capture of email opens and replies where consented
  • WhatsApp conversation logging for markets where that is the channel
  • Full-text search across every activity

Tasks and follow-up

Sales rep

The single feature that most reliably increases close rate, because most deals are lost to silence rather than to competitors.

  • Follow-up tasks attached to deals and contacts
  • Automatic task creation on stage change
  • Overdue escalation to the manager
  • Daily digest of what is due
  • Sequences — a defined cadence of touches for a lead type

Roles, territories and visibility

Admin

The permission model is where generic CRMs get expensive, because visibility rules are usually a paid tier.

  • Role-based permissions on every object and field
  • Territory definitions by geography, product, account size or named list
  • Visibility scoped to own / team / territory / all
  • Field-level restrictions — a rep sees the margin or does not
  • Delegated access for cover during leave, with an expiry
  • Full audit log of record access and changes

Dashboards and forecasting

Sales manager

Numbers that are current, and that you can click into until you reach the individual deal.

  • Pipeline by stage, owner, territory and product
  • Weighted and committed forecast side by side
  • Conversion rate at every stage transition
  • Activity leaderboards and response-time reporting
  • Cohort analysis by lead source
  • Every chart drillable down to the record

Integration and export

Admin

A CRM that cannot talk to the rest of your stack becomes another silo.

  • Outbound webhooks on every meaningful event
  • REST API with per-key scopes and rate limits
  • Email, calendar and accounting-system integration
  • Scheduled exports to CSV or a data warehouse
  • No export restrictions — it is your data, in your database

Screenshots

The actual interface

Every screenshot uses generated demo data. No real customer, student or member record appears anywhere on this site.

  • A deal timeline. Emails and stage changes land here automatically; only the notes required a human. That ratio is what determines whether a CRM's data is trustworthy.

  • Territory rules as configuration. In most generic CRMs this specific screen is either a paid tier or a consultant engagement.

  • Weighted against committed. Showing both is what stops the forecast conversation from being an argument about optimism.

  • Mobile logging. Two taps to record a call outcome, because anything longer than that does not get done between meetings.

  • The permission editor. Field-level visibility matters more than people expect — margin is the usual example.

Roles & permissions

Who can do what

Publishing this table is unusual. It is also the fastest way to show that the system was designed for a real deployment rather than for a demo.

Visibility is enforced at the query layer, so a rep outside a territory does not receive the record and then have it hidden — the record never leaves the database.

Capabilities by role. Each row is a capability; each column is a role.
Capability Admin Full configuration Sales Manager Own team and territory Sales Rep Own records Executive Read-only, all data
Own leads and deals Admin: Full Sales Manager: Full Sales Rep: Full Executive: View
Team leads and deals Admin: Full Sales Manager: Full Sales Rep: Executive: View
All leads and deals Admin: Full Sales Manager: Sales Rep: Executive: View
Reassign an owner Admin: Full Sales Manager: Edit Sales Rep: Executive:
Edit deal value Admin: Full Sales Manager: Edit Sales Rep: Own Executive:
View margin and cost fields Admin: Full Sales Manager: View Sales Rep: Executive: View
Apply a discount above threshold Admin: Approve Sales Manager: Approve Sales Rep: Executive:
Change pipeline stages Admin: Full Sales Manager: Sales Rep: Executive:
Define territories Admin: Full Sales Manager: View Sales Rep: Executive: View
Bulk export Admin: Full Sales Manager: Own Sales Rep: Executive: View
Delete a record Admin: Approve Sales Manager: Sales Rep: Executive:
API keys and webhooks Admin: Full Sales Manager: Sales Rep: Executive:
Audit log Admin: View Sales Manager: Sales Rep: Executive: View
  • Full Full access
  • Edit Create and edit
  • View View only
  • Own Own records only
  • Approve Requires approval
  • No access
  • Enforced server-side, not by hiding UI

Technical architecture

How it is built

Skip this section if you are not technical — nothing later depends on it.

ASP.NET Core over PostgreSQL with a React front end. The two decisions that shape everything are the activity feed's write pattern — append-only, denormalised for read — and the permission model, which is applied as a query filter rather than as a post-fetch check.

  1. Clients

    • Web appReact, virtualised lists
    • Mobile webTwo-tap activity logging
    • Public APIScoped keys, rate limited
  2. Application

    • ASP.NET Core APIREST + OpenAPI
    • Permission resolverGrants → query filter
    • Territory engineRules evaluated at assignment
  3. Async work

    • Email & calendar syncGraph / Gmail, incremental
    • Webhook dispatcherSigned, retried, dead-lettered
    • SLA & digest jobsHangfire
  4. Data

    • PostgreSQLRecords + JSONB custom fields
    • Activity feedAppend-only, partitioned by month
    • Search indexPG full-text, ES above ~5M rows
Permissions resolve into a query filter before the database is touched, so counts, forecasts and exports are correct by construction rather than filtered after the fact.
Stack, layer by layer
  • API ASP.NET Core 8 (C#), REST + OpenAPI

    A permission model this granular benefits from being expressed in a type system rather than in convention.

  • Database PostgreSQL 16, EF Core, JSONB for custom fields

    Relational core for the objects that need integrity, JSONB for the custom fields every CRM deployment invents.

  • Front end React 19 + TypeScript, TanStack Query, virtualised tables

    Reps live in lists of thousands of rows. Virtualisation is not optional at that size.

  • Search PostgreSQL full-text, with an Elasticsearch path above ~5M activities

    Start with the database you already run. Add a search cluster only when the data justifies operating one.

  • Email sync Microsoft Graph and Gmail API, incremental sync

    Two-way email capture is what makes the activity log honest, and both major providers have first-class APIs.

  • Background jobs Hangfire

    Assignment rules, SLA checks, digests and sync are all scheduled work that must be observable.

  • Multi-tenancy Shared schema with a tenant discriminator and enforced global filters

    Operationally simple at this scale, with isolation enforced somewhere a developer cannot bypass by accident.

Data model highlights
  • Company, Contact and Deal as separate aggregates, joined through explicit relationship rows rather than foreign keys alone
  • Activity as an append-only table — never updated, never deleted, only superseded
  • CustomFieldDefinition plus a JSONB value column, so new fields need no migration
  • Territory as a rule set evaluated at assignment time, with the result materialised for fast filtering
  • PipelineStage with entry requirements and an SLA, versioned so historical conversion rates stay meaningful
  • PermissionGrant resolved into a per-request filter expression, cached per user
  • WebhookSubscription and WebhookDelivery, with retry state and a dead-letter queue
Integrations
  • Microsoft 365 and Google Workspace — email and calendar
  • WhatsApp Business API for conversation logging
  • Xero, QuickBooks and ERP systems for invoice and payment status
  • Outbound webhooks on every domain event
  • Zapier / Make via the public REST API
Deployment topology

Deployed to your cloud account, containerised, with the database in a managed service. Single-tenant per customer by default; the multi-tenant path exists for firms reselling the CRM to their own clients.

Notable engineering decisions

The trade-offs we made, and why

This is the section that separates a system somebody built from a system somebody screenshotted.

Multi-tenant isolation: three patterns, and why we default to one

Every CRM build has to answer this early, because retrofitting it is expensive. There are three viable patterns and each is correct in a different situation.

Database per tenant. Strongest isolation, easiest compliance story, trivially simple per-tenant backup and restore. Costs: migrations must be applied N times, connection pooling gets awkward past a few hundred tenants, and cross-tenant reporting requires a separate pipeline. Right answer when tenants are few, large, and regulated.

Schema per tenant. A middle ground that in practice inherits the migration pain of the first pattern without inheriting all of its isolation benefits. We rarely choose it.

Shared schema with a tenant discriminator. One database, a TenantId on every table, one migration. Operationally by far the simplest, and the only pattern where cross-tenant analytics is a query rather than a project. The risk is obvious: one query that forgets its filter is a data breach.

We default to the third, with the filter enforced where a developer cannot forget it — a global query filter at the ORM level, driven by the tenant claim on the authenticated principal. Queries that omit the filter return nothing rather than everything. Bypassing it requires an explicitly named call that exists in exactly one place in the codebase, is covered by tests that assert on cross-tenant leakage, and is what the audit log watches hardest.

For a client with a hard regulatory isolation requirement, we use database-per-tenant and accept the operational cost. The point is that this is a decision with a real trade-off, not a default to be inherited from a tutorial.

The activity feed is a write-heavy denormalisation problem

The activity timeline is the most-read screen in any CRM and the most-written table. Every email sync, stage change, call log, task completion and field edit produces a row, and the volume outgrows the deal table by two orders of magnitude within a year.

Three decisions keep it fast:

Append-only. Activities are never updated or deleted. A corrected note is a new activity superseding the old one. This removes update contention entirely, makes the table safe to partition by time, and means the audit trail is the same object as the feed rather than a parallel structure that can drift from it.

Denormalised at write. The actor’s name, the record’s title and the activity’s display summary are written onto the activity row rather than joined at read time. Names change rarely; timelines are read constantly. The trade — a background job that rewrites denormalised names when a user is renamed — is worth it many times over.

Partitioned by month, with the hot partition indexed differently. Recent activity is queried by record; historical activity is queried by date range for reporting. Those want different indexes, and partitioning lets them have different ones.

The result is a timeline that opens in a few milliseconds on records with thousands of activities, which matters because it is the screen a rep looks at before every call.

Permissions as a query filter, not a post-fetch check

The tempting implementation of “a rep sees only their own deals” is to fetch and then filter. It is wrong for two reasons: the database does work that is thrown away, and sooner or later some endpoint returns a count, an aggregate, or an export that was computed before the filter was applied.

Instead, a user’s permission grants are resolved once per request into a filter expression that is composed into every query against a protected entity. A rep’s query for deals is physically different SQL from a manager’s. The consequences:

  • Counts, sums and forecasts are correct by construction, because the aggregate never sees rows the user cannot see.
  • Pagination is correct — no more “page 3 is empty because everything on it was filtered out after fetching”.
  • Adding a new endpoint cannot accidentally omit the check, because the filter is attached to the entity’s query root rather than to the endpoint.

Field-level permissions work the same way, projected at the query rather than nulled afterwards. When a rep is not permitted to see margin, the margin column is not in the SELECT — so it cannot leak through an API response, a CSV export, or a debug log.

Webhooks are a delivery problem

Every integration story starts with “we’ll fire a webhook” and gets interesting when the receiver is down for an hour.

Webhook deliveries are persisted before they are attempted, retried with exponential backoff, and moved to a dead-letter queue after a bounded number of failures — where they are visible and manually replayable rather than lost. Each delivery carries a signature and a monotonic sequence number per subscription, so a receiver can detect gaps and reordering rather than assuming it saw everything.

This is unglamorous and it is the difference between an integration that works in a demo and one that survives a year in production.

Demo access and customisation

See it running

A seeded demo with generated companies, contacts and a full activity history is available during a call. No real customer data is used anywhere.

Deploy as-is, or fit it to your process

As-is
4–6 weeks
Customised
10–14 weeks

Nobody deploys a CRM as-is, and we would be suspicious of a vendor who claimed otherwise. The core — records, pipeline, activities, permissions — transfers directly. The work is in your specific stage definitions, territory rules and integrations.

FAQ

CRM — questions we get asked

  • Should we really build a CRM instead of buying one?

    Often, no — and we will say so. If your process fits HubSpot or Pipedrive, buy it; you will get a better product for less money than any bespoke build. Custom becomes the right answer when the process genuinely does not fit (territory and commission rules are the usual culprits), when per-seat cost at your headcount exceeds the build cost within about two years, or when the CRM has to sit inside a larger system you already own. We would rather lose the project than build you something you should have rented.

  • How do you get reps to actually use it?

    By capturing automatically everything that can be captured automatically, and by making the remaining manual actions take seconds rather than minutes. Email sync, stage-change tasks and mobile-first logging do more for data quality than any training programme. If a rep has to open a laptop to log a call, the call does not get logged.

  • Can we migrate from our current CRM?

    Yes, and it is a scoped part of the project rather than an afterthought. Companies, contacts, deals, notes and — importantly — activity history all migrate. Attachments and email history are the parts that vary by source system, so we confirm what is exportable from yours during discovery.

  • What does multi-tenant mean for us?

    For most clients, nothing — you get a single-tenant deployment with your data in your database. The multi-tenant architecture matters if you intend to resell the CRM to your own customers, in which case the isolation model is already built rather than retrofitted later, which is much harder.

  • Who owns the code and the data?

    You own both. Source in your repository, database in your cloud account, no export restrictions and no per-seat licence. If you stop working with us, everything keeps running.

Book a walkthrough of CRM.

We will show you the working system, tell you honestly what would need changing for your operation, and give you a range before you commit to anything.

30 minutes · We reply within one business day.

WhatsApp Book a call