Case Study

Customer Relationship and Workflow Management

A customer workspace that gives teams room to work their own way.

Contacts, companies and opportunities are the three records this custom CRM development case study starts from. Administrators can take it further than those. They add fields, define relationships and create new record types of their own, then organise the daily work through saved views and dashboards.

Each change connects to the workspace database and its API, and a custom record then becomes something the whole team can use right across the application.

The challenge

Adding a record type means more than adding a form. A new type needs somewhere to store its data and a place to appear in navigation. People also need to search it, and permissions have to cover it. When its settings change but the database does not follow, a user can be left with a field that has nowhere to save its value.

The hard part was keeping all of those pieces together while the workspace stayed open to change.

What we built

Every new record type arrives with a default view and a navigation entry, and teams can load existing data through CSV files before cleaning it up with search and duplicate merging.

Email and calendar connections place messages next to the customer records they belong to. A message links to its contact, and people on a thread can become contacts automatically.

A new record can also start a workflow. The workflow might send an email, update another record or ask a person for information. Conditions, branches and delays set the order of those steps. Operators can inspect each one through its versions and run histories, and a failed step can retry after a delay without restarting the whole run.

Technical approach

Metadata describes the records in each workspace and how they relate. Those definitions build the database structure, the views and the API. Every workspace keeps its own PostgreSQL schema inside the shared database, and foreign keys tie its records together.

Change the definition and storage together

Record settings and database changes ride in one transaction, so they commit as a pair or roll back as a pair. Validation gathers every error before the migration runs. Objects are created in dependency order, so a table exists before its fields and indexes.

Developers can read a dry-run migration plan before they apply it. The runner notes how long each phase takes and which database sessions are blocking, while a lock timeout caps how long it waits. Once the change commits, the cached definitions refresh and the browser receives the update.

Permissions follow a second shared path. GraphQL and REST requests reach the same query runners, which apply role-based limits to whole record types and to single fields. A connected application therefore goes through the same access checks as the workspace itself.

Project success

The client can grow its customer data without pushing custom records outside the normal CRM flow. Views, relationships and permissions travel with each new structure. Because configuration and storage change in one transaction, the two stay aligned. Conversations and automated follow-up stay attached to the records a team is working on, and run history is there when an action needs attention.

Technology stack

Frontend

React, TypeScript

Backend

NestJS, TypeScript

Database

PostgreSQL

API interfaces

GraphQL Yoga, REST

Queue infrastructure

BullMQ, Redis

Recognize the pattern?

If one of these looks like your situation, the discovery call is where we find out how close.