A 60-truck cement-haulage SMB in Nagpur was paying for a third-party GPS-tracking SaaS that they hated. The tracker showed truck positions on a 30-second refresh map. It did not show ETAs, did not flag fuel anomalies, did not integrate with their dispatch sheet. The owner asked us a simple question: "if I am paying for a map, can I have something that actually helps me run the business?" Twelve weeks later we shipped a custom MQTT + TimescaleDB + WebSockets + Mapbox dashboard. This is what we built and the cost-control choices that kept us under budget.
The Answer in 60 Words
Each truck pings GPS + speed + fuel level every 5 seconds via an existing OBD-II tracker (we kept the hardware they had). MQTT broker on a single EMQX node receives 720 pings/min/truck — 43,200/min total. TimescaleDB stores raw, aggregates 1-min and 1-hour rollups. Next.js dashboard subscribes via WebSockets, renders Mapbox tiles.
Why This Matters For Indian Logistics SMBs
The Indian logistics SMB market — ~250,000 firms operating fewer than 200 trucks — is the underserved middle. Enterprise GPS-tracking SaaS (KPIT, Trakzee, Webfleet) charges per truck per month and scales linearly forever. Tier-1 telematics integrations (FleetX, Locus, LogiNext) offer richer dashboards at a higher per-truck rate. For a chain doing ₹15-30 crore annual revenue with 5-8% net margin, that spend is real money. Custom builds have a 2-3 year payback against tier-1 SaaS, plus the data and roadmap stay in-house.
The other reason: open-source telematics infrastructure has matured. EMQX is genuinely production-grade as a free MQTT broker. TimescaleDB is the right database for time-series GPS data. Mapbox's pricing tiers at our volume are actually cheaper than Google Maps. The build cost has dropped roughly 60% since 2022.
The Client (Specific Details)
The Architecture
The Stack (And Why)
| Layer | Choice | Why |
|---|---|---|
| MQTT broker | EMQX 5 (open-source) | Free, handles 100K+ connections per node. We are at 60. Headroom for 1000x. |
| Time-series DB | TimescaleDB on Postgres 16 | Continuous aggregates removed 80% of dashboard query work. Compression saved storage cost meaningfully. |
| Cache + pub-sub | Redis (ElastiCache t4g.micro) | Fan-out from one MQTT message to N WebSocket subscribers. |
| Backend API | Node.js 20 + Fastify | The team knows it. Fastify outperformed Express by ~22% in our load tests. |
| WebSocket | ws library directly (no Socket.io) | Socket.io's protocol overhead was unnecessary for our use case. ws gives raw control. |
| Frontend | Next.js 14 + Mapbox GL JS | Server components for the static dashboard chrome; Mapbox handles the map layer with its own state. |
| Hosting | AWS Mumbai (ap-south-1) | Owner wanted INR billing through an Indian AWS partner; ap-south-1 keeps GPS data in-region. |
| Geocoding | Mapbox + IndianMaps fallback | Mapbox sometimes misses small Indian villages; IndianMaps fills gaps. Free for our volume. |
The 12-Week Plan
The Cost-Control Choices
This is the meat of the post. Below are the specific decisions that kept the cloud bill low.
Choice 1: Single-region, no cross-AZ replication
We run the entire stack in ap-south-1a. The owner accepted that an AZ outage would cause ~30 min of dashboard downtime (the ingest queue absorbs the gap). Cross-AZ data transfer would have added cost for no business value. Easy call.
Choice 2: TimescaleDB compression after 14 days
Raw GPS pings compress 92% with TimescaleDB's columnar compression. We run "compress chunks older than 14 days" as a continuous job. Engineering cost: 4 lines of SQL.
SELECT add_compression_policy('truck_telemetry', INTERVAL '14 days');That's it. The continuous aggregates remain uncompressed (they are queried often), the raw pings compress aggressively (they are queried rarely).
Choice 3: EMQX self-hosted, not AWS IoT Core
AWS IoT Core charges per message. At 43,200 msg/min × 1440 min/day × 30 days = 1.87 billion msg/month. Self-hosted EMQX on 2 c6i.large costs a fraction of that. The engineering cost of running EMQX: 4 hours/month for upgrades and monitoring.
Choice 4: Mapbox pay-as-you-go, not Google Maps
Mapbox charges per map load above the free tier. Our dashboard generates ~120,000 loads/month. Google Maps for the same volume would have cost far more. Mapbox API is comparable in quality for Indian roads (sometimes better — they have better coverage of MP-CG industrial corridors).
Choice 5: WebSocket through a single Node process, not Lambda
API Gateway WebSocket + Lambda would have charged per-message. At 60 connections × 100 messages/sec = 6000 msg/sec sustained, plus 80,000 GB-seconds Lambda compute, we estimated a far higher bill than self-hosted Node ws on a single t4g.medium. The trade-off: we manage one EC2 instance. Worth it.
The Cost Breakdown
The Outcome (Numbers That Mattered)
The Pre-Production Checklist
- EMQX broker tested at 10x peak load (we did 432K msg/sec, deployed at 720)
- TimescaleDB compression policy active and verified (chunk older than 14 days = compressed)
- Continuous aggregates refresh every 60 seconds (not on-query — that kills performance)
- WebSocket reconnect tested with 30-second internet drop on dispatch supervisor's machine
- Geofence alert latency < 12 seconds end-to-end (truck enters → Slack ping)
- Fuel anomaly threshold calibrated against 30 days of baseline (not arbitrary)
- Backup of TimescaleDB taken nightly to S3 cross-region (Mumbai → Hyderabad)
- Per-truck data export pipeline for regulatory needs (we ship CSV per VIN per quarter)
- Load test: 60 simultaneous browser dashboards open without degrading WebSocket fan-out
- Rollback plan: re-enable old SaaS tracker for 7 days as a safety net (we kept it during pilot)
What We Deliberately Did Not Build
1. A driver-facing mobile app. Drivers would not have used it. Owner's framing: "if I give them a screen, they will look at it instead of the road." We respected operator wisdom. The truck-level data is sufficient; no driver UI needed.
2. ML-based ETA prediction. We considered it. The simple "current speed × distance + traffic factor" model hits ±9 minutes median. ML might get to ±6 minutes. Not worth the engineering. Maybe in v3 with 18 months of training data.
3. Payment integration with cement plants. The dispatch supervisor uses WhatsApp to send arrival photos to customers, who release payment. Pulling that into the dashboard would have required a 3-month sales conversation with each of 14 cement plants. v2 candidate, not worth the upfront friction.
Common Mistakes (Each One Costs Real Money)
Symptom: "TimescaleDB queries get slower over time." Cause: missing chunk compression policy. Without it, the hypertable grows linearly and indexes get slow at ~50 GB. Fix: add_compression_policy after 14 days. Compression rate is roughly 92% on GPS data.
Symptom: "MQTT broker memory keeps climbing." Cause: stale subscribers. Browser tabs left open for days hold connections. Fix: enable EMQX's session expiry (session.expiry_interval = 7200) and persistent-session limits. Saved us 2 broker restarts in 6 months.
Symptom: "ETA spikes to '6 hours late' when a truck stops at a fuel station." Cause: ETA engine treats 0 km/h as "stopped forever." Fix: filter known fuel stops + customer geofences from the speed-averaging window. Apply a "minimum recent moving speed" floor.
Symptom: "Dashboard freezes when 60 trucks update simultaneously." Cause: React re-rendering 60 markers per WebSocket message. Fix: debounce updates to 250ms windows, mutate the Mapbox source data directly instead of re-rendering React components on every ping.
Symptom: "GPS pings go missing in poor coverage areas." Cause: tracker buffers locally then bursts when coverage returns. Fix: timestamp-on-tracker (not on ingest). Process out-of-order pings using TimescaleDB's natural ts ordering.
A Detail That Surprised Us
In week 8 of the pilot, the dispatch supervisor noticed his dashboard showed truck 47 was idle for 4 hours at a non-customer GPS location near Itarsi. He called the driver. Driver was at a dhaba waiting for the next load assignment from the plant — standard practice. Supervisor cancelled the alert. Two days later, same truck at the same spot for 3 hours. He called the driver again. Driver had run out of work and was unsure what to do. Supervisor reassigned the truck to a Bhopal pickup, saving 11 hours of dead time. That single conversation — enabled by the live dashboard — paid for a month of run cost.
Where Logistics Fits In Our Wider Work
We have built variants of real-time tracking dashboards for:
If you have a fleet, a dispatch operation, or any moving-asset tracking problem, the playbook above transfers. The only piece you need to redesign per client is the fuel-anomaly threshold and the geofence library. Everything else is identical.
FAQ
Why not buy FleetX or Locus?
For 60 trucks on FleetX's mid-tier (2025), the per-truck fee adds up every year. Custom build has 14-month payback. After year 2, the custom build is cheaper. Plus, we control the roadmap.
Can the same architecture handle 1000 trucks?
Yes, with two changes: (1) shard EMQX by truck-ID-modulo to 4 broker nodes, (2) move TimescaleDB to a multi-node cluster. We have load-tested up to 4000 simulated trucks on a 3-node TimescaleDB. The Postgres scaling story for time-series is genuinely good.
What about offline tracking when trucks lose 4G?
The Concox AT4 firmware buffers up to 12 hours of pings locally and bursts when coverage returns. We process bursts using the timestamp-on-tracker, so out-of-order pings are inserted correctly into TimescaleDB.
Can the system support drivers' weekly trip-sheet generation?
Yes. We generate per-truck per-week summary PDFs from the continuous aggregates: distance covered, average speed, fuel consumed, time at customer sites, time idle, alerts triggered. Email out every Monday morning.
What about integration with Tally for fuel cost reconciliation?
We export a CSV per month with truck-level fuel data, which the accountant manually loads into Tally. A direct Tally bridge is on the v2 wishlist; for now, the CSV-and-paste workflow is fast enough.
Did the old GPS tracker contract cancel cleanly?
30-day notice in the contract. Old vendor offered a 25% discount to retain. Owner declined. Migration was clean — both systems ran in parallel for 4 weeks, no data lost.
What is the team composition?
Two backend engineers (one on the MQTT and TimescaleDB side, one on the API and WebSocket fan-out), one frontend engineer for the Next.js + Mapbox dashboard, one DevOps engineer at 0.4 FTE for AWS setup and EMQX hardening, our project lead at 0.2 FTE. Total: 4 people, ~9 person-weeks.
Want a fleet/logistics dashboard built?
We build real-time tracking dashboards for Indian logistics SMBs running 20–500 vehicles. Typical project: 10–14 weeks, fixed scope. The first call is technical, with the engineer who would lead your build.
Book a Build Call
