Case Study

Receipt Matching and Invoice Management

A financial operations workspace connecting receipts, bank activity and customer invoices.

The work started from one routine finance question, namely whether a given receipt belongs to a given bank transaction. We built a workspace where staff can look at the proposed link and confirm it or reject it. Receipt matching automation sits at the centre of that workspace. Customer invoices run alongside it, with their own flow for sending and tracking payment.

The challenge

A total that lines up is useful evidence, but it does not settle the question. A bank description can shorten a merchant's name. A converted amount can come out slightly different, and a receipt can turn up well after the payment.

We had to tell two cases apart. One is a pair worth putting in front of a person. The other is a pair the application can safely attach on its own, and that line shaped the matching rules.

What we built

Bank connections and CSV imports bring in the transactions. Connected inboxes and messaging channels bring in the receipts, which get parsed into fields the system can compare. Either record can start a search against what is already stored. Matching therefore does not depend on the receipt being the one that lands first.

Staff review each suggestion and record what they decide. For outgoing bills they can generate invoice PDFs, send them by email and attach payment links. Recurring invoices and reminders run as background jobs. Accounting exports then carry the records onward.

Technical approach

A SQL query first narrows the search to the team and a sensible date range. The matcher then compares names, amounts and dates on their own, and it handles currency as a separate check. Name checks allow for shared words and abbreviations. Amounts can also be compared in the team's base currency.

Recent confirmations, rejections and still-open decisions all nudge the team's thresholds, but only within fixed limits. Automatic attachment asks for more than a strong score. It also needs a history of repeated confirmations. Without that track record, even a convincing pair stays a suggestion, and a rejected pair picks up a penalty that fades over time.

Bank imports use upserts for transactions the system has seen before. Team identifiers scope every query, and a read can fall back to the primary database for a recent write or a permission check. Invoice payment notifications are signature-checked before any payment status changes.

Project success

The team can leave an uncertain receipt unassigned and still get on with the rest of its finance work. Reviewed decisions feed back into later suggestions, while automatic attachment answers to a stricter gate. Invoice preparation, reminders and payment updates all share the workspace, and none of them wait on every receipt having a settled match.

Technology stack

Application

TypeScript, Next.js, Bun

APIs

Hono, tRPC

Data

PostgreSQL, Drizzle

Background processing

Redis, BullMQ, scheduled job services

Connections

Bank-provider adapters, email and payment APIs

Desktop shell

Tauri

Recognize the pattern?

If one of these looks like your situation, the discovery call is where we find out how close.