2012–Feb 2021 / Experience
Foundational Developer & Tech Lead · ReviewTree
2012–Apr 2014: part-time, alongside a mentor. Apr 2014–Feb 2021: full-time.
Customer sentiment at scale: reviews, ratings, and the eighteen systems they came from.
The current product website. This case study covers my work from 2012 to 2021; the later platform was built without me.
What it was
ReviewTree was a SaaS platform that collected customer sentiment — reviews and ratings — for businesses whose customers were already living in somebody else’s software. I joined alongside a mentor as a part-time side job, then moved to full-time when he left. For most of my roughly nine years there, the architecture was mine to decide and mine to live with: first as the foundational developer, then as tech lead.
My contribution
The problem was never reviews. It was ingestion.
Businesses do not type their customers into a review platform. Those customers already exist inside a practice-management system, a field-service scheduler or an EHR. To ask the right person for a review at the right moment, ReviewTree had to connect to whichever system the business already ran, understand its idea of a customer and an appointment, and keep doing it reliably.
Eighteen integrations, six areas
By the time I handed the platform over, I had built eighteen third-party integrations across the areas below. Appointment importers retrieved appointment and associated customer data only, never clinical records.
| Area | Systems |
|---|---|
| Veterinary | EzyVet · SpecVet · Covetrus |
| Dental | Curve Dental |
| Health records | Epic EHR · Greenway Health |
| Field service | ServiceTitan · FieldAware · mHelpDesk · Fleetmatics / Verizon Connect |
| Reputation | RepMan · ReputationIntelligence · StepRep |
| Surveys & delivery | SurveyGizmo / Alchemer · Twilio · Firebase · Google Places · URL shortener |
Each source brought its own authentication model, rate limits, failure modes, and idea of what a customer and an appointment were. Most offered no push mechanism, so ingestion ran as polling importers. OAuth callbacks were the only inbound calls from those third parties. Where no API existed, I used web scraping, with the fragility that implies.
The decision that mattered
I kept that variety out of the core. The platform used one internal model for customers, appointments and the events worth asking about, with a mapping per source.
The design goal was that adding a nineteenth integration would mean a new service and a new mapping, rather than another branch through code every other integration also ran. That separation was how I approached making new verticals manageable without making the whole platform more expensive to change.
Being the system other people integrated with
I built the first ReviewTree REST/JSON API, covering accounts, users, contacts, reviews, comments, surveys and source customers. It used account-scoped API keys. The initial model had a single key per account; I later expanded it to support multiple keys, with an admin interface for issuing and revoking them.
I shipped a hand-written JavaScript client alongside the API, with a standalone test harness so the endpoints could be exercised outside the application. That gave integrators a client and gave us a way to check the interface independently of the application UI.
I also built outbound webhooks: per-account subscriptions to specific event types, delivered asynchronously through background jobs. An in-app interface let each account configure its own subscriptions.
Then I made myself unnecessary
I recruited and onboarded the developers who came after me, set code-review standards, and transitioned the platform to a dedicated team. The goal was to give the team ownership rather than remain its single point of failure.
What came out of it
My contribution spanned building the product, shaping its integration architecture and API, and transferring responsibility to a dedicated team. The later version of the platform was built without me; this account describes the work I did before that handoff.
Stack
- Ruby on Rails
- PostgreSQL
- Background job processing
- REST/JSON APIs
- Outbound webhooks
- OAuth
- Web scraping where no API existed