Skip to content
Back to endeavors
Espresto logo

Espresto

Perk smarter, not harder

  • SvelteKit
  • Svelte
  • Supabase
  • Tailwind CSS
  • Cloudflare Pages

The idea

My wife was a barista for several years in college. When we’d go to a new coffee shop, I’d tell her what I was in the mood for (something hot, seasonal, half-sweet, oat milk) and she’d know exactly what to order. She did this for friends in college too. We wanted to capture that instinct and turn it into a tool anyone could use.

Who it’s for

  • The overwhelmed orderer. Stands in front of a coffee menu with 40 options and freezes. Wants someone to just tell them what to get based on their mood. That’s the core use case.
  • The creature of habit. Orders the same latte every day but is open to trying something new if the app earns their trust. Needs “My Usual” to work perfectly before they’ll tap “Something Different.”
  • The dietary-restricted. Dairy-free, vegan, sugar-free, nut allergy. Needs hard filters that never suggest something unsafe, not just soft preferences.
  • The coffee shop owner. Wants free visibility for their shop. Registers, curates their drink menu from 108 templates (or creates custom variations), and shows up in patron suggestions.

MVP suggestion engine

Problem: There’s no app that asks you what you’re feeling and just tells you what to order. Coffee menus are overwhelming, especially at a new shop.

Solution: Built a SvelteKit app with Svelte 5 and a 5-question flow: temperature, caffeine, milk preference, sweetness and adventurousness. Each answer feeds a scoring algorithm that evaluates 108 drink templates across five dimensions (temperature match worth 30 points, sweetness up to 30, milk preference 20, adventurousness up to 25, and inventory confidence up to 10). Returns a personalized suggestion with ingredients, a “how to order” phrase written for the barista, and allergen info.

Outcome: Working prototype with profile persistence via localStorage, a purple gradient theme, and PWA support. Deployed initially to GitHub Pages.

Auth, orders, and favorites

Problem: The app forgot everything between sessions and couldn’t track what you’ve tried before.

Solution: Added Supabase for auth (email/password with JWT sessions), user profiles, order history, and favorites with Row Level Security on every table. Returning users get a mood-based flow instead of the full questionnaire: “My Usual” surfaces drinks similar to past orders, “Something Different” tweaks one preference, “Seasonal Special” filters by current season (peppermint in winter, lavender in spring), and “Surprise Me” picks randomly from the top three matches.

Outcome: The app starts learning your preferences. It tracks what you order, what you favorite, and adjusts suggestions accordingly.

Post-order feedback

Problem: The suggestion engine had no idea whether its recommendations were actually available at the shop, or whether the user liked what they got.

Solution: Built a multi-step feedback flow after each order. Step 1: did you order this? Step 2: were all ingredients available? Step 3: which ones were missing? Step 4: why (out of stock, don’t carry, not sure)? Optional step 5: 1-5 star rating. Ingredient availability feeds an inventory learning system that tracks confidence per shop on a 0-100% scale. Starts at 50% (assumed available), gains 10% per confirmation, drops 25% per unavailability report.

Outcome: The app learns which shops carry which ingredients. Over time, suggestions for a specific shop avoid drinks that require ingredients the shop doesn’t stock.

Sharing

Problem: When you find a great drink, you want to tell someone about it. But copying a drink name and ingredients into a text message is annoying.

Solution: Built a stateless sharing system that Base64-encodes the drink data directly into the URL. No database lookup needed. Share via SMS or email with pre-filled messages and links. A shared drink landing page renders the full drink details. Short URLs keep the links clean.

Outcome: Share buttons appear on suggestions, orders and favorites. Recipients see the full drink without needing an account.

Learning and personalization

Problem: Early suggestions were generic. The algorithm didn’t know you yet.

Solution: Added a learning service that analyzes order history and favorites to build a preference profile. Orders count as 1 interaction, favorites as 0.5. After 3 effective interactions, the engine applies learning bonuses worth up to 115 additional points: exact drink match (up to 40), favorite match (50), base type affinity like “prefers lattes” (up to 15), and flavor affinity like “likes vanilla” (up to 10). Learning bonuses only apply to “My Usual” and “Seasonal Special” moods. Added ingredient-based hard filtering so dairy-free users never see milk-required drinks.

Outcome: The app gets meaningfully better the more you use it. A progress bar shows new users how close they are to personalized recommendations. Vitest covers the learning service with 540+ lines of tests across threshold calculations, recency weighting and edge cases.

Merchant marketplace

Problem: The suggestion engine knows drinks but not shops. We wanted local coffee shops to be part of the experience, featuring their actual menus so suggestions match what’s really available.

Solution: Built a two-sided merchant system. Shop owners register with name, description and location (automatic slug generation for public URLs). They manage team members through an email invitation system with Owner and Admin roles, ownership transfer, and role-based access enforced by Supabase RPC functions. Shops curate their drink menu by featuring drinks from the 108-template global library or creating custom variations. Duplicate detection uses SHA-256 hashes of normalized ingredients. Each drink gets base type ingredient validation (21 types with required ingredients) so a “latte” always has espresso and milk. Public shop profiles show the menu browsable by category with search and drink detail modals.

Outcome: The merchant side is built and functional: shop registration, drink management, team invitations, ingredient validation. Waiting on adoption. It’s a classic marketplace cold-start problem: deciding whether to push the merchant side or the patron side first.

Deployment journey

Problem: Finding the right hosting setup took a few tries.

Solution: Started on GitHub Pages (static SPA, routing issues). Moved to Cloudways (SFTP via GitHub Actions, Nginx SPA routing headaches). Switched to Vercel (zero-config SvelteKit deploys). Finally landed on Cloudflare Pages with the custom domain espresto.coffee using the static adapter.

Outcome: Each migration taught me something about deployment trade-offs. Cloudflare Pages is the right fit. Fast, free at current scale, and plays well with the rest of the Cloudflare stack. The app uses Svelte stores with 1-second debounced saves and user-specific localStorage keys so anonymous users get full functionality while authenticated users sync across devices.