Freelance · Mumbai logistics firm

Freight CRM

Production freight-forwarding CRM for a Mumbai logistics firm — I built the React SPA framework the team rallied around, then shipped Buckets 6B–11: email drafts, enquiry board, sea/air enquiry forms, agent dispatch, and rates with L1 freeze. Built in a team of four (lead + three developers) over five weeks; live on a client-owned domain.

Timeline
May – Jun 2026 · ~5 weeks · paid freelance engagement
Team
Team lead + 3 developers · I shipped ~27 commits across Buckets 6B–11 + the SPA framework
Stack
React 19 · Vite 8 · Node.js 22 · Express 4.18 · better-sqlite3 12.10 · express-session · bcryptjs
Email pipeline
imapflow · mailparser · nodemailer · pdf-parse · xlsx · Python IMAP sidecar
Scope
My scope: ~50 of 124 endpoints · 13 of 31 tables · Buckets 6B–11 of 15 shipped by the team
Infra
GCP Compute Engine e2-small · Ubuntu 22.04 · Nginx + Certbot TLS · PM2 under systemd · daily SQLite snapshots to GCS
Status
Live deployment available on request

My contribution

What it does

End-to-end enquiry-to-quote pipeline. Gmail IMAP polls the client's ops inbox → an AI scorer ranks each new message → high-scoring messages auto-create enquiries, mid-scoring go to a review queue → field extraction lifts mode, ports, weight, and incoterm → the enquiry lands on a six-column Kanban → agents are dispatched over SMTP for rates → replies are parsed (PDF / Excel / text) into structured rate rows → quotes are built with a margin calculator that hard-locks below the threshold → L1 freeze snapshots the chosen rate and advances the stage with an immutable audit row.

Architecture

A Python IMAP sidecar (gmail_proxy.py) polls Gmail every 60 seconds and POSTs to a secret-guarded ingest webhook (INGEST_SECRET) on the Express backend, decoupling inbox polling from the request path. Express runs against a single SQLite file in WAL mode (31 tables, indexed foreign keys); the React SPA is served as static assets from the same Node process behind Nginx, with TLS via Certbot and PM2 keeping the process alive under systemd. A nightly cron snapshots the database to a GCS bucket.

AI scoring (Bucket 3, integrated)

Five weighted signals score each incoming message — sender domain (30), subject keywords like quote / RFQ / freight rate (25), body terms + quantity + incoterm (20), PDF/Excel/packing-list attachment (15), first-in-thread bonus (10). Thresholds: ≥75 auto-creates an enquiry, 50–74 routes to a review queue, <50 silently skips. Tunable through a sandbox UI on the Inbox screen — Shreshta built the scorer; I wired the sandbox into the React SPA.

Margin calculator + L1 freeze (Bucket 11)

The margin tab computes Buy(INR) = Buy(USD) × exchange_rate (default 83.50, mutable in settings). A slider sets the margin 0–30%; under 12% the route hard-locks the quote with a "needs approval" badge. L1 freeze is an idempotent endpoint — it snapshots the INR conversion using the current settings.exchange_rate, advances the enquiry stage, and writes one immutable row to enquiry_timeline. Re-firing the route doesn't double-snapshot.

Team & work split

Built in a team of four — a lead who brought the team together and three developers. My buckets are listed above; crediting the rest by name and bucket below, partly because it's accurate, partly because portfolio-grade transparency on freelance team work is the framing reviewers actually want.

The team shipped 15 buckets in total; my scope was Buckets 6B–11 plus the SPA framework and the integration of Buckets 2–5 into it.

Source Team repository on a teammate's account · my ~27 commits include the React SPA bootstrap, the Buckets 2–5 integration sequence, the Bucket 6B email-drafts pipeline, and Buckets 10 & 11
View on GitHub