Skip to content
CASE STUDY

Leading Smart Metrify

One interface over several meter vendors, what happens when a payment works but the meter doesn’t, and getting pages from 4.2s to 1.6s.

Smart Metrify is an estate and electricity management platform built around the meters CWG manufactures. Residents buy meter units through it, estate admins manage their estates and meters, and a super-admin view reports revenue and activity.

I led its development with a team of two engineers. It now runs across 22 active estates, serves more than 5,000 users, and has processed 27,421 completed transactions.

One interface over several vendors

The platform didn’t talk to meters directly. It integrated with the external APIs of the meter vendors in use, and each one behaved differently.

So the system absorbed those differences behind one consistent interface of its own, and the rest of the platform never had to care which vendor was involved. We also went the other way, providing a vendor-facing REST API with API keys and Swagger documentation so other vendors could integrate with us.

When the payment works but the meter doesn’t

Payments go through Paystack and an internal wallet. The hard case is a payment that succeeds at Paystack while crediting the meter units fails.

The answer we built is simple: retry automatically first, then give the user a clear human path. They can raise a support ticket directly from the platform, so a person can resolve anything the retry doesn’t. That isn’t full idempotency, and I’d rather say so plainly. It’s automatic recovery first, and a person when that isn’t enough.

Making it fast, in order of measured cost

As the platform grew, pages reached about 4.2 seconds to load and Lighthouse scores sat in the low 60s. Users were complaining about sluggish dashboards. I profiled first, then worked through the biggest costs rather than applying every technique available.

PAGE LOAD, BEFORE AND AFTER, AT REAL SPEED
Before4.2s
After1.6s
  • Route-level code splitting, so the first load carried only what the first screen needed. I drew the boundaries at routes rather than per component, because over-splitting creates request waterfalls.
  • TanStack Query replaced ad-hoc fetching, giving us request deduplication and far fewer round trips, in exchange for briefly stale reads.
  • Zustand for local UI state, kept apart from server state to avoid re-render cascades.
  • The cheap wins last: modern image formats, font subsetting and deferred third-party scripts.

Page load came down to 1.6 seconds and Lighthouse rose to 90+, confirmed with Lighthouse, WebPageTest and real-user monitoring.

Then the APIs

Core endpoints averaged about 480ms and degraded under concurrent load. Instrumenting them showed the time was mostly going to repeated database round trips and N+1 queries.

I fixed the N+1s at the source by batching and eager-loading, because caching a bad query only hides it. I added Redis for read-heavy data, trimmed payloads to the fields clients really used, and moved notifications and report generation onto background workers. Latency came down to under 200ms, measured with load tests and endpoint timings.

Not every decision from that work survived, as I wrote about in The cache I pulled back out.

All case studies and posts