Building an EV Rental Operating System for Last-Mile Delivery
Why the unit of billing was 'active rental day,' not 'monthly subscription' - and how that single choice rewrote the architecture.
Most product engineers think of "subscription" as a billing concept. On this engagement, it was a fleet-management concept first and a billing concept second.
The client operated a fleet of electric two-wheelers leased to last-mile delivery riders. Riders worked for food and grocery platforms whose hourly delivery volume was unpredictable. The leasing model had to absorb that volatility - a rider who lost a delivery shift on Tuesday couldn't be locked into a fixed monthly plan they couldn't pay. So the unit of billing wasn't "monthly subscription." It was "active rental day," metered upstream from the billing system.
Context
Electric two-wheelers cost the operator capital. They earn back via daily lease fees from riders. Rider income depends on delivery shift availability, which is stochastic. A misaligned billing model leaves either the operator with idle assets (rider stops paying) or the rider in arrears (operator's billing assumes shifts that didn't happen).
The platform needed to:
- Model "rental days" as the primitive, with subscription wrappers (daily / weekly / monthly) on top.
- Coordinate the physical asset side: battery swapping at neighborhood swap points, vehicle assignment from showrooms, return checks.
- Give different stakeholders different views - rider needs a dead-simple booking + payment app, showroom manager needs vehicle assignment and condition tracking, admin needs fleet-wide visibility, battery point operator needs swap logging.
- Handle the financial edge cases: failed payment, missed renewal, over-mileage, vehicle damage, theft.
- Stay operationally sane in a country with intermittent connectivity at neighborhood battery swap points.
The product challenge
Four user roles, four distinct apps, but one underlying state machine for each rental.
The state machine had to cleanly track which rider had which vehicle, which battery, when they last paid, whether the rental was active, and where the rider, vehicle, and battery were physically located. Race conditions appeared everywhere - the rider could finish a shift and arrive at a swap station while the showroom manager was simultaneously updating their vehicle assignment in the back-office app.
My role
I led product and engineering execution end-to-end: state-machine design, backend architecture, the four mobile and web frontends, payment integration, the battery operator's offline-tolerant app, deployment, and the operating playbook around the platform.
Core features
- Subscription engine: daily, weekly, and monthly plans on top of a single "rental days" primitive. Plan upgrades and prorations were derived, not duplicated.
- Multi-role apps: Rider (mobile), Showroom (tablet/web), Admin (web), Battery operator (offline-tolerant mobile).
- Vehicle and battery state machine: one source of truth across rider check-in, swap events, return events, and damage reports.
- Payment flows: recurring debits, missed-payment grace logic, manual override for the showroom team.
- Operations console: fleet utilization, idle asset alerts, late-return alerts, low-battery-density alerts at swap points.
- Audit trail: every state transition logged, with reason codes, for dispute resolution.
Technical highlights
The rental state machine was implemented as an append-only event log with derived current-state views. Every dispute resolved by replaying events. No "current state" field anywhere in the database that could drift out of sync with what actually happened.
The battery operator's app was the most interesting piece. It had to function in poor connectivity at swap points with intermittent backhaul. We built it with optimistic local writes plus reconciliation on reconnection. The state machine's idempotent event keys made replay safe - a swap logged twice from the same operator on the same battery resolved to one event, not two.
Payments tied to "rental day completion" events, not calendar days. A rider whose plan paused (vehicle in service, awaiting repair) didn't get charged for those days. The same primitive handled both daily prepay and monthly postpay plans.
Role-based access wasn't a permission table - it was a different app per role. Admin couldn't see what the rider saw because they didn't need to. This sounds inefficient until you watch what happens when a single overgrown app tries to serve four different jobs.
What this taught me
The hard part wasn't the subscription. It was the physical world. Every "edge case" in the billing system was actually a real-world event - battery dying mid-shift, vehicle in for service, swap point closed for the night. The product was a thin software shell over physical operations. The model that worked was making physical events first-class data, not retrofitting them into a SaaS schema.
Internal tools are the platform. The rider's app got the polish and the App Store listing. But the showroom dashboard and the battery operator's app were what kept the business running. When the back-office is fast, the front-office can scale.
The operating playbook took longer to stabilize than the code. What took longer was working out how a showroom manager handles a damaged vehicle, what reason code a battery operator picks when a battery is over-temperature. Software encoded the workflow. The workflow encoded the business.
Outcome
The platform digitized what had been a clipboard-and-WhatsApp operation. Showroom teams gained fleet-level visibility instead of asking around. Riders could sign up, pay, and start working without a physical visit. Battery operators logged swaps in seconds instead of paper. The operator could see fleet utilization in near-real-time and pull capital allocation decisions from data instead of intuition. The platform survived multiple capacity expansions without re-architecting.
If your operations involve a fleet of physical assets and a fleet of human users moving them, the question is not "what software do we need." It is: what state machine governs the asset, and what events update it? Build the state machine first. Build the apps after.