Bilel Abdelmoula

Home โ€บ Recruitment ATS/CRM case study

Case study ยท Soraia ยท 2024โ€“2026

Production ATS, CRM & AI recruitment platform

Built at Soraia, an Italian AI/software agency, for a Spanish recruitment company. I joined the project around November 2024 and, as a Full-Stack Developer, became the primary technical owner across major parts of the application until October 2026.

  • React 19 + TypeScript
  • Xano + PostgreSQL
  • OpenAI + Google Vision
  • ~2 years on the project

Results

Measured on real projects.

  • ~150kperson records, incl. ~35k candidates
  • ~100kcompanies ยท ~150k activities
  • ~4kjobs/projects
  • ~49internal users

What I built

One platform for recruiting, CRM, sales and analytics. The problems: finding good candidates by hand was slow, CV uploads made candidates wait ~32 seconds, important APIs reached 5โ€“40+ seconds under load, and the WeWeb frontend had become slow to load and hard to maintain.

Potential Match: AI candidate matching

Designed and built from scratch

  • Persisted OpenAI text-embedding-3-small vectors in a dedicated table; candidate changes trigger re-indexing.
  • Ranking: ~35k candidates โ†’ language filter โ†’ geographic/distance filter (configurable radius) โ†’ vector similarity โ†’ weighted signals โ†’ client-defined 0โ€“100 score.
  • The first version embedded noisy natural-language text and matched poorly; I redesigned it with cleaner, separated representations for role, skills and experience.
  • The score is not accompanied by AI-written explanations; recruiters see the ranked list and score.
  • OpenAI
  • Embeddings
  • PostGIS

Backend performance

APIs at 5โ€“40+ seconds under load

  • Traced the causes: missing indexes, inefficient filters and joins, repeated calls, heavy add-ons, looping triggers, large unpaginated responses.
  • Added PostgreSQL, GIN and geospatial indexes, pagination and direct database queries; rewrote joins and fixed triggers.
  • Some flows that generated 100+ database operations became a single consolidated query.
  • PostgreSQL
  • GIN
  • Pagination

CV ingestion

Don't make candidates wait for AI

  • Before: the upload waited ~32 seconds for OCR, OpenAI extraction and enrichment.
  • After: check duplicates, save minimum identity fields, create the candidate and answer immediately; Google Vision + OpenAI enrichment completes a few seconds later.
  • Prompt-injection sanitization, unsafe-markup handling and validation on public input.
  • Google Vision
  • OpenAI
  • Async

Security and RBAC

From frontend checks to backend authorization

  • Moved access control from partly frontend checks to the backend: reusable authorization functions and endpoint-level role checks for Admin, Back Office, Scrum, Squad Lead and recruiter roles.
  • Scoped, least-data responses; rate limiting; SQL-injection protection on custom queries.
  • Safer magic-link login tokens and hardened public endpoints.
  • RBAC
  • Rate limiting
  • SQL injection

Full frontend migrated to React + TypeScript

Every page replaced, with another developer

  • The WeWeb app had evolved in production for ~1.5 years; React was validated on an internal dashboard first, then agreed with the client.
  • React pages deployed through Cloudflare, mirroring Xano's dev, staging and production environments, and embedded in WeWeb during the transition.
  • Shipped in batches; WeWeb authentication stayed until final cutover; the app now runs fully on React.
  • Planned first: migration plan, page specs, data collections and role rules before rebuilding.
  • Claude Code accelerated implementation and refactoring; I owned integration, validation, debugging and production behavior.
  • React 19
  • TypeScript
  • Vite
  • TanStack Query
  • React Router
  • React Hook Form
  • Zod
  • Tailwind CSS
  • Cloudflare Workers

What the platform does

ATS + CRM + sales + analytics

  • ATS: candidates, jobs/projects, public job pages and applications, open applications, duplicate detection, recruitment Kanbans and candidate states.
  • CRM and sales: companies, contacts, calls, meetings, notes, target lists and outreach; business call โ†’ pre-opportunity โ†’ opportunity โ†’ won โ†’ recruitment project.
  • Analytics: user and team KPIs by squad, sprint and month, with forecasts.
  • Reports: named and anonymous candidate PDF reports (Informe).
  • Maintained and improved a LinkedIn profile clipper integrated with the ATS (originally built by an external developer).
  • ATS
  • CRM
  • Kanban
  • Reports

Data migration and outreach

~150k CRM activities

  • Led the initial migration of contacts, opportunities and activities from Clientify into the platform: mapping, cleanup and duplicate handling.
  • Outreach and email ran through Brevo earlier and Clientify later.
  • Clientify
  • Brevo
  • Migration

External APIs and AI: where each is used

Everything below ran inside this platform.

  • OpenAI text-embedding-3-small: Persisted candidate and job embeddings for Potential Match.
  • OpenAI language models: Structured extraction from CVs, run after the upload response.
  • Google Vision (OCR): Text from image and scanned CVs.
  • Google Places / Maps: Address and location lookup for distance filters.
  • Brevo, then Clientify: Outreach and email; Clientify data migrated into the platform.
  • Cloudflare Workers: React frontend deployments mirroring Xano's environments.

Tech stack

  • Front end: React 19, TypeScript, Vite, TanStack Query, React Router, React Hook Form, Zod, Tailwind CSS; first version in WeWeb
  • Backend: Xano, PostgreSQL, SQL, REST APIs, triggers, background tasks
  • AI: OpenAI embeddings and extraction, Google Vision OCR
  • Hosting and tools: Cloudflare Workers, Git, Claude Code, Loom

FAQ

Quick answers before we talk.

What was your role?

Formally Full-Stack Developer. In practice I was the primary technical owner across major parts of the application: data model and APIs, AI matching, CV ingestion, performance, security, CRM migration and, with another developer, the React frontend.

Can I see the code?

No. It's client work built at Soraia, so the code stays private. I'm happy to walk through the architecture on a call.

Which AI models and APIs did you use?

OpenAI text-embedding-3-small for matching and OpenAI language models for CV extraction, Google Vision for OCR, Google Places for locations, and Brevo/Clientify for outreach.

Can you build something similar for me?

Yes: ATS or CRM workflows, candidate matching, document extraction or a faster backend. Book a call and tell me what you need.

Let's build it properly

Tell me what's slow, messy or missing. A short call is enough to see if I can help.

Also see