How I Automated Ticket-to-Order Matching and Cut Manual Work by 90%
A case study on building an internal Shopify and Zendesk matching tool with React, Bun, Effect, and Redis that reduced manual work by 90% and improved match accuracy by 10%.
Manual Work
A neighboring team to mine was spending around 1,000 hours per year copy-pasting data between two browser tabs, looking for similarities in data. I took initiative and built a tool for them during my normal workload. They now spend 100 hours a year on the same task, which also catches 10% more matches than they used to.
Here’s how I did it.
The Problem
Our sales team answers calls, emails, and chats from potential customers across several brand sites. One of their daily tasks is to figure out which Zendesk tickets turned into actual Shopify orders, so we have real records of what sales conversations converted to revenue.
Yes, the workflow was exactly as tedious as it sounds. Each salesman would open a ticket: grab the name, email, and/or phone number. They would then jump to Shopify, paste, search, and scan. If something looks like a match, they’d verify it, write it down, and move on to the next ticket. This happened with roughly 90 tickets per day, split across the team.
This process ate half of their working week. Half of this time, the team could have spent on helping more customers instead of reconciling the records of which customers they’d already helped. It wasn’t anyone’s job to fix this, so I picked it up in between the cracks of my normal work.
Early Decisions
With the knowledge I’d collected up to this point, I scoped this as a lightweight internal dashboard that would pull data from Shopify and Zendesk, and then process the matches on demand. This approach didn’t last after a day’s worth of research.
Shopify’s auth model wouldn’t work as a single-tenant design. Access is tied to a per-store app install. I couldn’t treat multiple brand stores as one shared data source. I planned to rebuild it as a proper Shopify app with per-store installs, sessions, and tokens. This required more scaffolding up front, but it was the only option I had that would scale with multiple brands.
I also learned that multi-account Zendesk apps require explicit approval from their team to be installed. While discussing these limitations with my co-workers, I realized we only used one Zendesk account, so this wouldn’t be a problem.
With this new architecture in mind, I moved the backend to Effect, powered by Bun. More on this later.
The Matching Algorithm
This became the real tricky part of the project, which required careful engineering.
A ticket gives you maybe a name, an email, and a phone number. A Shopify order gives you multiple names, emails, and phone numbers: billing vs. shipping, account vs. order, gifts sent to other people. The data also wouldn’t be clean data. Names get misspelled (confirmed to me by the sales team), phone numbers arrive in countless formats, and emails even get the occasional misspelling, too.
The algorithm had to be forgiving enough to catch real matches and strict enough not to invent fake ones.
Here’s what it does per ticket:
- Narrow the candidate pool. Only consider orders within a time window around the ticket’s creation date and discard the rest.
- Normalize everything. Lowercase, strip formatting from phone numbers, and collapse the whitespace.
- Score each field with weights. An email match is worth the most, then the phone number, and finally the name. The name was often the messiest signal.
- Fuzzy-match as a fallback. If an exact match fails, fall back to the edit-distance comparison. This would allow “Kathryn” to still match “Katheryn”.
- Handle the name problem carefully. Orders might have the same name, a different first name (gift orders), or just a first or last name. Several sub-checks run and provide an overall name score.
- Apply time decay. An order placed one hour after the ticket was created is far more likely to be a match than one placed five days later. The score is multiplied by a decay factor based on the gap.
- Clamp, theshold, and sort. Drop anything below a confidence floor, then sort the survivors high-to-low. Tickets may be flagged with multiple viable candidates, so a human can decide.
The confidence scores get mapped to UI labels (low,fair,medium,high) so the sales team doesn’t have to guess what the raw numbers mean.
Finally, the results land in Redis, where the data is refreshed daily against the last seven days' worth of tickets and orders.
The App
I built the frontend as a React SPA inside Shopify’s admin, using Polaris components so it feels native to the rest of the dashboard. Salespeople sign in per store and land on a table of matches from the last seven days. They are able to filter their own tickets, sort by different column types, and rank by confidence.
The backend is powered by Effect on the Bun runtime. The short version of this is that since the project stopped being a simple dashboard and became a server handling Shopify auth, Zendesk OAuth, encryption tokens, retries, and scheduled background jobs, the system needed a more robust codebase to reduce developer friction. Effect gave everything I needed: typed errors, consistent dependency management, and clean service boundaries.
One of the best things Effect allowed me to do was create swappable data services. One implementation pulled from Faker.js, while the other from the real APIs. I could then iterate on the algorithm against mock data that closely mirrored real data shapes. This way, I could avoid rate limits, authentication complexity, and waiting. The feedback loop stayed tight, which is why the matching logic converged once I started tuning the weights on production data.
Results
For the first few weeks, the sales team and I watched matches carefully. After a few fine-tunings of the algorithm weights, the matches began flowing through with a 80% accuracy rate. As confidence grew with the system, we saw a drop in hours spent recording the matches.
- 20 hours/week became 2 hours/week, A 90% reduction in time spent on the task.
- 10% more matches were caught than the manual process found. Records got more accurate, not just faster.
- Payback period: a few weeks. The time I put into building this was earned back by the team within the first month of real use.
The reclaimed 18 hours a week now go back into actually helping customers.
What I’d Tell Another Engineer
This task had existed for years, and nobody built this tool because it wasn’t anyone’s job to: the sales team couldn’t scope it, and engineering couldn’t see it. I think the gap between “whose problem is it” and “who can solve it” is where a lot of improvements can be made. So start looking at neighboring teams.
Second, I went into this project hoping for a 25% reduction and got 90%. I believe it’s better to project lower and be surprised than to pitch a higher reduction up front and miss.
Effect also proved to be a robust solution for this problem, too. I’ll definitely be using it again for more projects like this.