Case study

Receipts Platform

An image can finish uploading and still fail during processing. I built the platform around those intermediate states, so the product can explain what is happening and the work has a recorded path through failure.

Context
Receipts / Founder / 2026
Focus
Go, ConnectRPC, Protobuf, Postgres, background jobs
Project
receipts.beauty

Media processing, AI analysis, and indexing can outlive the request that starts them. They also fail differently. The system needs to retain the work, decide which failures are retryable, and keep incomplete media out of public results.

AreaWhat I built and why
One API contract

Built a Go backend with Protobuf definitions and generated ConnectRPC clients.

The web application consumes defined product behaviour rather than duplicating it in routes.

Recorded work

Built Postgres-backed job workflows for processing and other background tasks.

Jobs retain state, attempts, and failure information beyond a single request or worker process.

Publication state

Connected media processing and publication status to the API response.

An image can be reported as processing or failed while it remains private.

ChoiceWhy it matters
Put product rules behind the APIGo owns the backend workflows and persistence; generated clients carry the contract to the web application. A change to the contract has explicit consumers to check.
Make retry a policy decisionWorkers claim jobs with database leases and schedule eligible failures with backoff. Retryable errors and exhausted attempts have different transitions, so a permanently failing job does not just loop.
Separate upload from publicationFinalisation verifies the uploaded object and enqueues processing. The public status distinguishes processing, failure, and publication readiness; successful transfer of bytes is only one step.

The platform records the stages between an upload and a publishable image. Retryable work can be scheduled again, terminal failures keep a status, and the client has a specific state to show.

The important boundary is often between two kinds of success. Uploading a file does not mean processing finished, and processing does not mean it is ready to publish. Naming those stages gives both the interface and the operator a better next step.