Tazzina

Reducing the waste and guesswork of dialing in espresso

Context

Tazzina is the first app I've made in years. I built it because getting into espresso exposed a practical problem: every new bag starts from a guess. Grind size, yield, time, and sometimes temperature all affect the result, and changing the wrong variable can make the next shot worse.

At first I only wrote down grind size on masking tape and stuck it to the bag. It proved the need, but not the solution. Once I had multiple beans in rotation, the variables became harder to remember, the settings changed as the beans aged, and buying the same bean again meant starting from memory.

Masking tape on coffee bag with grind size written on it

The bet

The obvious solution was to use the Notes app. That solved part of the problem, but only as a generic place to write things down. Espresso recipes have a shape: dose, grind, yield, time, and a few variables that only make sense in relation to each other.

Therefore I built Tazzina around the espresso workflow. A user saves the recipe that worked for a bean, archives the bean when it runs out, and sees the recipe again when they buy that bean later.

The bigger bet is this: every saved recipe can make the next user's first shot better. If enough people scan and save the same bean, Tazzina should be able to say "start here" instead of leaving every person to waste the same two to five shots finding the right variables.

Tazzina app concept

Technical decisions

I started with a lightweight web app, but the workflow kept pointing back to native iOS. Scanning a bag, checking a widget, or glancing at a watch are small interactions but they matter when the user is standing next to their machine.

I chose Convex instead of iCloud because Tazzina is not only a private utility. It needs sync, a real backend for data aggregation, and a path to other platforms without rebuilding the product around a different data model.

The guided dial-in flow also had two jobs. It helps beginners by changing one variable at a time and avoiding overcorrection. Strategically, it also produces cleaner recipes, which makes future suggestions more accurate.

Tazzina technical detail

Strategic direction

The most interesting technical problem was hidden inside the simplest field: grind size. A recipe that says "grind 6" only makes sense if I know which grinder produced that number. A Breville might go from 1 to 16, a Niche from 0 to 100, another grinder from 0.0 to 10.0, and another from 1A to 10F.

The first version of the recommendation loop is already valuable—Tazzina can say that five people with the same grinder tend to use grind size 7 for this bean. The recommendation is not generic. It is anchored to the grinder the user actually owns.

The big ambition is cross-grinder translation. As more recipes connect the same beans to different grinders, Tazzina can start to infer how a setting on grinder A maps to a setting on grinder B. That means the app can suggest a starting point even when no one has used that exact bean with that exact grinder yet.

What did not work

Bean recognition through scanning was a convoluted problem. I first tried extracting only the roaster and bean name from OCR, with blocked words like roast levels or tasting notes that yielded unreliable results. I tested LLM-based extraction which was more reliable but slower and more expensive than the job warranted.

The simpler approach proved to be the best: save all OCR-extracted words as signals to match, and ask users to enter roaster and bean names at bean-creation time. Those fields can be verified later, while the broader bag text compounds future scanning accuracy.

Managing the library

One thing I underestimated was how much workflow there would be around bean verification. Convex was a good backend, but the console was not made for this kind of management. To verify one bean, I might need the submission open in one tab, the existing bean record in another, Google in a third, a color picker somewhere else, and a contrast checker after that. Even simple decisions like "is this the same roaster?" were awkward when I was cross-checking names inside JSON rows.

So I built a small admin console for myself. It puts the review queue, bean details, merge options, web search, color selection, and contrast checking into one place, shaped around the actual steps I take when cleaning up submissions.

It is not a public feature, but it is one of the things that makes the public feature work.

Tazzina admin console showing a bean verification review queue Tazzina admin console showing bean card color selection and contrast checking

Scope control

Some of the most interesting product decisions were not about what to add, but about what Tazzina should refuse to become.

I tried designing a dialing in mode through a tasting compass, but the classic espresso compass has always felt almost unintelligible to me. It assumes users can have enough taste vocabulary to describe what is wrong. I wanted the app to guide judgment without making users pretend to be professional tasters.

I also decided against puck prep guidance and shot logging. Both were useful adjacent ideas, but they would make Tazzina an espresso teacher or a daily tracking app. I only care about the recipe that worked. The app should help users get there, save it, and then get out of the way.

There were other cuts. Official bean photos had copyright implications, temperature tracking was too advanced for v1, sharing was useful for distribution but premature, and broader brew methods like V60 or Aeropress belonged to a later version.

Tazzina scope decisions

Design

I wanted the beans to be the hero of the app. Coffee bags already have a strong visual language, so I wanted that to define the visual identity of the product. Bean cards use user photos presented like small polaroids, colors identified from the bag, and subtle 3D motion to make the objects feel tactile.

Tazzina design detail

Impact

The app is too new to claim market impact, so I instrumented only actions that would teach me something: whether people see and use recipe suggestions, how many dial-in attempts a bean takes, where search or scan fails, and how often users return to check recipes.

My own dogfooding has already validated the shape of the product. I have ignored a grind nudge and later realized it was right, used the watch app when my phone was elsewhere, and scanned a bean I had forgotten buying before. Tazzina surfaced my previous recipe: grind 6, 36g yield.

The long-term outcome is moving espresso from two to five wasted shots toward a credible starting recipe. The near-term outcome is more modest—scattered notes become reusable bean memory, and beginner uncertainty becomes a guided sequence of small, intelligible decisions.