CTI ERP: A custom inventory and order management system for a telecom distributor
Replacing a 100,000-row spreadsheet and three disconnected tools with a single ERP: SIM card inventory, multi-stage orders, FedEx integration, invoicing, and audit trails across multiple locations.
- CTI
- Telecom
- Feb 2026
- shipped

The problem
CTI moves SIM cards. A lot of them. A single order to a regional reseller can include 10,000 individual cards, each with a unique ICCID that has to be tracked from intake through shipment to invoice.
When we met them, all of that was running on a spreadsheet. Orders lived in email threads. Shipping happened by hand in FedEx Ship Manager. Invoices were reconciled at the end of every month with a calculator and a stack of printouts. One duplicated ICCID could send the team on a multi-hour hunt across four systems that didn't talk to each other.
Off-the-shelf ERPs didn't fit. Salesforce and NetSuite assume your business looks like every other business. CTI's business is moving serialized telecom hardware at scale, and the tools that exist for that are either decades old or built for carriers, not distributors.
What we built
Inventory that handles ten thousand SIMs at once. Bulk ICCID import, automatic deduplication, and live status per card: in stock, reserved, allocated, shipped. The order detail view caps inventory display at 100 cards for performance; a separate export endpoint returns the full list when the team needs it.
Orders with real state. Every order moves through Pending → Ready to Ship → Shipped → Completed, with Cancelled available at any stage. Each transition is permission-gated and audit-logged. Order numbers (SO-####) use a max-sequence pattern that survives deletions without collisions.
FedEx, native. Direct API integration for rate quotes, label generation, and tracking. Address validation runs before label purchase. Rates are deduplicated by service type so the team doesn't see five flavors of FedEx 2Day.
Invoicing that ties back to the work. Auto-generated invoice numbers (same max-sequence pattern as orders, because a naive COUNT() breaks the moment an invoice is voided). PDF generation, payment tracking, and a clean line back to the source order. Month-end reconciliation that used to take a day now takes minutes.
Permissions that actually mean something. Permissions like MANAGE_SETTINGS, SHIP_ORDERS, and DELETE_ORDERS are granted individually. Every create, update, delete, and ship action lands in an audit log with the actor, the timestamp, and the values that changed.
Multi-location. Inventory and orders are scoped per warehouse. Reports roll up across locations or filter to one.
How it's built
The backend is Node.js with Express, Prisma against PostgreSQL. Service classes are singletons with explicit boundaries (OrderService, InventoryService, ShippingService), each owning its slice of business logic. Every mutation flows through the audit logger.
The frontend is React with Vite, talking to a single typed API client. UI state stays in the browser; everything durable lives in PostgreSQL.
It runs on a Proxmox LXC container inside the client's own environment, with PM2 managing the Node processes. Schema migrations run as the database owner, not the application role, a hard-learned constraint after early migrations silently failed against permission-locked tables.
We picked this stack for longevity. Internal software has to keep running five years from now, after the framework of the month has been replaced twice. Postgres handles complex relational queries (orders linked to SIMs linked to invoices linked to shipping records) without breaking a sweat. React and Node will keep working as long as browsers do.
What changed for the team
The chasing stopped. Order status, SIM allocation, shipping label, and invoice now live in one place, all updated in real time, all audit-trailed.
Onboarding new staff went from weeks of shadowing to a single day of training, because the system mirrors how the team already worked, just without the manual reconciliation tax. The mistakes that used to cost hours of investigation now surface immediately in the audit log, with the actor's name and the original values right there.
The system grew with the business. New product types, new pipeline stages, new integrations: each one ships as a feature update, not a SaaS-vendor support ticket.
The stack
- Frontend: React, Vite, custom design system
- Backend: Node.js, Express, Prisma
- Database: PostgreSQL
- Integrations: FedEx Shipping API, PDF invoice generation
- Auth: Session-based, with a role-permission system
- Infrastructure: Proxmox LXC, PM2, daily PostgreSQL backups
Doesn't fit Salesforce. Doesn't fit NetSuite. Doesn't fit any of the generic ERPs. Built around the workflow that actually exists, owned outright by CTI, with no per-seat fees and no licensing model.