Elevatus: AI-Powered Multi-Tenant Recruitment SaaS
Led roadmap execution and microservices migration for Elevatus — a cloud recruitment platform serving enterprises and governments across the Middle East.
Elevatus is real. It’s running today at elevatus.jobs. Governments in the Middle East use it for civil-service hiring rounds. Enterprises with a thousand-person HR operation use it. Both of them — different tenants, wildly different compliance envelopes — run on the same core platform.
I was Technical Team Leader and IC Architect on the Roadmap team at Talentera (now Elevatus) from 2018 to 2020. What I built, and what I led, and where I made mistakes — that’s what this case study is about.
The starting point: TCL/TK on AOL/Navi Servers
No, that’s not a typo.
When I joined, part of the codebase still ran on Tcl/Tk atop AOL/Navi web servers — a stack that was aging out of support while the product was expected to handle enterprise SLAs. We were also carrying a PHP 7 monolith, a growing Vue.js frontend, and early Java 8 services with no consistent inter-service contract.
The migration wasn’t greenfield. It never is. You’re serving live tenants — government ministries that cannot tolerate a Monday-morning outage — while simultaneously decoupling a system that was never designed to be decoupled. The approach was Strangler Fig: new capabilities landed as bounded-context microservices from day one; legacy surfaces were progressively replaced. The routing layer (Nginx, then later an API gateway) made the cuts invisible to clients.
Domain-Driven Design as a forcing function
DDD was the language we used to negotiate service boundaries with the product team. It wasn’t an academic exercise — it was the mechanism that let engineers say “no, that feature belongs in the Candidate domain, and if you put it in the Recruiter domain we’ll be untangling it for two years.” In a multi-product, multi-squad environment, that vocabulary is worth more than the architecture diagrams.
The bounded contexts that emerged — Candidate, Application, Workflow, Identity, Analytics — mapped cleanly to team ownership. When a government client asked for a custom hiring stage, the Workflow team owned that without touching Candidate or Analytics. That’s the win you’re looking for: change isolation.
Camunda BPMN: workflows per tenant, not globally
This is the decision I’m most satisfied with from that period.
Recruitment workflows are genuinely variable. A government ministry’s civil-service hiring flow might have 14 stages, three approval committees, and a mandatory waiting period mandated by law. A startup’s flow might be 5 stages and run in a week. You cannot hardcode either. You cannot build a configuration UI that covers both without making it unusable for one of them.
What you can do is model both as BPMN process definitions, store them per-tenant, and execute them on a shared Camunda engine. Adding a new workflow variant for a new enterprise client became a BPMN file deployment, not a sprint. That sentence does a lot of heavy lifting when your sales team has promised a large government client that the platform is flexible enough to handle their procurement requirements.
The Camunda decision also gave us auditability for free. Government clients care about auditability in a way that enterprise clients don’t, not because their developers are more demanding but because their legal teams are. Every workflow transition is a Camunda event. The audit trail is a query, not a forensics exercise.
Keycloak and the federated identity problem
Multi-tenancy gets complicated the moment a user exists in more than one tenant context.
In the MENA recruitment market, a candidate applies broadly. A qualified engineer might be active in three simultaneous hiring rounds — one at a tech company, one at a government ministry, one at a regional bank — all on the same platform. The same email address. The same person. Three completely separate tenant contexts, three completely separate application states, and an explicit requirement that none of the three tenants can know the candidate is also applying to the others.
The answer was Keycloak with per-tenant realms and cross-realm token exchange. A candidate’s core profile lived in a shared identity space. Their application data lived in the tenant realm. Authentication was tenant-scoped — you couldn’t accidentally carry credentials from one tenant’s session into another. Keycloak’s realm federation made this tractable without building identity federation from scratch, which is the kind of work that generates security incidents in year two when the edge cases emerge.
”AI-powered” in 2018, honestly
The Elevatus pitch included AI-powered hiring. That was true. It was also slightly more complicated than it sounds.
GPT-3 didn’t exist. What we had was ML-driven CV parsing and scoring, candidate ranking on weighted criteria, and early video interview analysis. The CV parsing was the piece that worked well in production — structured extraction from unstructured resumes in Arabic, English, French, and Hindi, normalized into candidate profiles that the ranking model could score against job criteria.
The per-tenant model personalization was the architecturally interesting part. A government ministry’s recruiter decisions (credential verification and regulatory compliance dominate) shaped a ranking model that was meaningfully different from the one trained on a tech company’s decisions (portfolio and demonstrated skill dominate). That’s the correct outcome — but it’s also a storage and serving problem that doesn’t exist if you run one global model. Multi-tenancy applied to ML infrastructure is its own layer of constraints.
The video interview analysis worked. It was also, in retrospect, a system that required much more rigorous bias and fairness tooling than we had in 2018. Our government clients required explicit answers to “does this model treat candidates equitably across demographic groups” — not because they were philosophically ahead of the field, but because their diversity mandates made it a contractual question. “The model said so” is not an answer a minister’s office will accept. We learned this early. The field has since caught up.
The mobile layer: the thing I’d do differently
Ionic first, Flutter later. Both were adaptations of the web experience to mobile, and it showed.
Candidate experience on mobile — applying, tracking status, doing video interviews on a phone — has different UX constraints than recruiter experience on desktop: reviewing hundreds of candidates, making batch decisions, running hiring rounds. We built mobile as a port. It was never as good as a product designed mobile-first would have been.
If I were doing this today, candidate UX and recruiter UX would be separate products that share a backend. The API layer would have been designed with both consumers in mind from the start. This is the API-first lesson that everyone learns exactly once, the hard way. I have now learned it.
What I owned
- Roadmap execution and sprint planning across multiple squads
- DDD facilitation and bounded-context boundary decisions
- Camunda BPMN integration architecture and per-tenant workflow model
- Keycloak realm federation design
- Microservices migration sequencing (strangler fig execution)
- RabbitMQ event topology for cross-service communication
- Jenkins pipeline standardization across squads
Governments actually use this. That sentence lands differently when you’ve been through the procurement process, the security reviews, the customization negotiations, and the integration work that “actually use” requires. It’s not an MVP with a friendly early adopter. It’s production software running critical processes for institutions that cannot tolerate downtime.
That’s what we built.