All work
Enterprise SaaS
HR technology
In active development

Tenure

Multi-tenant enterprise HRMS & recruitment platform

Platform guide

One platform covering the entire employee lifecycle — requisition to offboarding — for many client organisations at once. Twenty independently owned domain modules over a shared datastore, with tenant scoping enforced at the data-access layer, enterprise SSO, and automated user provisioning from day one.

The problem

Mid-size and enterprise HR teams run the employee lifecycle across a dozen disconnected tools: one for hiring, another for attendance, spreadsheets for payroll inputs, email for approvals. Nothing reconciles, and every new client organisation means another isolated deployment to maintain. Service length — the thing every entitlement, gratuity and vesting calculation depends on — ends up recomputed by hand, differently, in each tool.

Our approach

Tenure is a single multi-tenant NestJS application composed of twenty independently owned domain modules over a shared MongoDB, with tenant scoping enforced at the data-access layer rather than trusted to callers. Long-running work — imports, document generation, webhook delivery — is pushed onto a BullMQ queue so request latency stays flat. A single Flutter codebase serves web, iOS, Android and desktop clients, which keeps the surface area of the UI work proportional to one product rather than four.

The outcome

A platform that onboards a new client organisation as a configuration change instead of a deployment, and integrates into an existing enterprise identity estate on day one via SAML, Entra ID and SCIM.

What is inside

20 domain modules: recruitment, onboarding, employees, attendance & leave, payroll, performance, LMS, engagement, service desk, offboarding, documents, billing and more

Strict multi-tenant isolation with per-tenant configuration, org structure and billing

Enterprise identity: SAML 2.0, Azure AD / Entra ID through MSAL, and SCIM 2.0 user provisioning

Background pipeline on BullMQ + Redis for bulk imports, exports and document generation

Outbound webhooks with retry/backoff plus a scoped client API secured by rotatable integration keys

Real-time notifications and presence over Socket.IO

Immutable audit log across privileged actions, with a super-admin control plane

One Flutter codebase targeting web (embeddable), iOS, Android and desktop


Engineering notes · 5 min read

Twenty modules, one deployment

20

Domain modules

independently owned

4

Client platforms

one Flutter codebase

Config

New tenant

not a deployment

The usual way to serve many client organisations is to deploy the application many times. It works until the eleventh customer, at which point every patch is eleven releases and every incident is a question about which version someone is on.

Tenure is one deployment. That decision moves the entire risk profile onto one thing: tenant isolation. If scoping is a convention that callers are trusted to follow, one forgotten filter is a cross-tenant data leak in a system holding salary and disciplinary records.

Isolation belongs below the callers

  • Tenant scoping enforced at the data-access layer, not in each service that remembers to ask
  • Per-tenant configuration, organisation structure and billing, so tenants can genuinely differ
  • Immutable audit log over privileged actions, with a separate super-admin control plane
  • Integration keys are per-tenant and rotatable, so revoking one customer’s access is not a deploy

Service length is the number everything else depends on

Gratuity, leave accrual, notice periods and vesting all resolve from one quantity: how long someone has actually worked here. In a fragmented HR estate that number is recomputed in four tools, four different ways, and they disagree by a few days each.

Modelling it once — with joining date, confirmed probation, unpaid leave and any break in service as explicit events — is why the tenure timeline is the demo’s signature screen. It is the one place where you can see the calculation rather than being told its result.

Why long-running work goes on a queue
OperationNaiveTenure
Import 4,000 employeesRequest times outQueued job, progress polled
Generate 300 contractsBlocks a worker for minutesQueued, retried on failure
Deliver a webhook to a slow endpointTies up the requestQueued with backoff

Enterprise identity was treated as a launch requirement rather than a later integration: SAML 2.0, Entra ID through MSAL, and SCIM 2.0 provisioning. An HR platform that cannot join an existing identity estate on day one does not get evaluated twice.

Technology

Back end

NestJS
TypeScript
Mongoose
Passport / JWT
Argon2
class-validator

Data

MongoDB
Redis
BullMQ

Client

Flutter
Dart

Integrations

SAML 2.0
Azure MSAL
SCIM 2.0
Socket.IO
Webhooks
XLSX

Platforms

Web · iOS · Android · Desktop

Discuss a similar build

Need a platform like Tenure?

We can walk you through how this was built, what it cost to run, and what the equivalent looks like for your business.

Or email us directly at [email protected]