← Back to projects
Personal Project
AI Voice Journaling (pre-launch)
Designing an AI product to run at $0 per entry, with a planned path to production
I'm building a voice journaling app on my own. Users speak, and AI turns each entry into a structured, searchable summary. Before writing any code, I wrote a full spec, 23 user stories and 7 architecture decision records, then designed the MVP to run entirely on free tiers while it tests the product's key assumptions.
- Role
- Solo: product, architecture, build
- Timeline
- June 2026, about two weeks
- Stack
- React Native + Expo, Supabase, Groq
- Status
- Working MVP, pre-launch
$0
per entry, vs. about $0.021 on the production setup
3–6 s
designed processing time, inside a 10-second target
7
architecture decision records, written before code
The problem
Learn what matters before paying for it
An AI journaling app has real running costs: every entry goes through speech-to-text, an LLM and an embedding model. Paying for all of that before knowing whether anyone wants the product is the wrong order. The MVP needed to answer the product's open questions first:
- Does speaking an entry and getting back a structured summary feel valuable?
- Is a free, open-weight model's output good enough, or does the product need a paid model?
- Does searching past entries by meaning, not keywords, feel meaningfully better?
- Does the upgrade flow work end to end, tested with sandbox purchases?
That set two constraints: $0 variable cost while testing, and nothing thrown away when it's time to pay for production.
The approach
Design production first, then a zero-cost variant of it
I wrote the production architecture first, with paid providers, and then a separate MVP spec that swaps only the AI provider layer for free equivalents. The mobile app, database schema, security rules and backend function contracts are the same in both. Each swap is recorded in its own decision record, with the alternatives I rejected and the exact steps to move back to the paid option.
Every feature went into a user story with acceptance criteria before it was built. Most stories then shipped as their own pull request.
How it works
From voice to searchable entry
Times are the design budget for a three-minute recording on a warm backend.
-
1
Voice recording
Capped at 10 minutes on the phone, with a countdown from 9:30, to keep files well under the transcription size limit.
React Native · Expo
1–2 s upload
-
2
Upload automatically triggers processing
A storage webhook starts the pipeline as soon as the file lands. The phone never has to make a second call.
Supabase Storage · Edge Functions
0–0.5 s
-
3
Speech-to-text
Files over 20 MB are rejected before any API call. The raw transcript is saved before the next step starts.
Groq · whisper-large-v3-turbo
1–2 s
-
4
LLM turns the entry into a structured summary
JSON output, checked field by field before it's saved.
Groq · Llama 3.3 70B
0.5–1 s
-
5
Embeddings for semantic search
Generated inside the backend function, so journal text never goes to an outside embedding service.
transformers.js · all-MiniLM-L6-v2 · pgvector
0.1–0.3 s
-
6
Results pushed live to the app
The app follows each status change (uploading, transcribing, summarizing, done or failed) over a live connection instead of polling.
Supabase Realtime
0.2 s
Total: 3–6 seconds warm, inside a 10-second target. The first request after the backend goes idle adds 3–5 seconds while the embedding model downloads. That's a known MVP cost, and it goes away on the production setup, which uses a hosted embedding API.
Key decisions
What I chose, and what it cost
Five decisions from the architecture records, each with the trade-off I accepted.
01
Groq for speech-to-text and the LLM
- Why
- It's free, it runs the same Whisper model family as the paid production option, and its API follows the OpenAI format. Moving speech-to-text to production means changing a URL and a key, not rewriting the function.
- Trade-off
- The free transcription quota, 7,200 audio-seconds a day or about 40 three-minute recordings, is the MVP's real capacity limit. The LLM quota, 14,400 requests a day, never comes close. Groq also lacks strict JSON-schema mode, so the schema lives in the prompt and every response is validated before saving.
- Considered
- On-device Whisper (10–30 s per entry and a 150 MB+ app download), Google Speech-to-Text (60 free minutes a month), Gemini Flash (capable, but a bigger code change from production), smaller 8B models (noticeably weaker summaries).
02
Treat the model choice as a product experiment
- Why
- Production is planned on Claude Haiku 4.5. Running the MVP on Llama 3.3 70B turns the model gap into something to learn: if testers find the free model's summaries compelling, the paid migration is low risk; if not, that's direct evidence the paid model is needed.
- Trade-off
- Testers see output that may differ in tone from the final product, so feedback on summary quality has to be read with that in mind.
03
Run embeddings inside the backend
- Why
- A small open model (all-MiniLM-L6-v2, 384 dimensions) runs inside the Supabase function through transformers.js. That costs nothing and keeps journal text out of third-party embedding services.
- Trade-off
- A 3–5 second delay on the first request after the backend goes idle, and lower-dimension vectors than production (384 vs. 1,536), which means a one-time re-embedding job when migrating.
- Considered
- OpenAI embeddings (a fraction of a cent per entry, but not zero, and another vendor), Hugging Face's hosted API (rate-limited and sends data out), and skipping search (which would leave a core question untested).
04
Let the upload trigger processing, and never lose the user's words
- Why
- If the phone had to call "process this" after uploading, a dropped connection in between would strand the recording with nothing processing it. A storage webhook fires because the file arrived, whether or not the phone is still online.
- Also
- The raw transcript is saved before the LLM step, so an LLM failure never loses what the user said. A failed embedding doesn't fail the entry either; it just won't show up in search.
05
Stay on free tiers, and defer what doesn't test a core question
- Why
- Supabase's $25-a-month plan is only needed for scheduled jobs. A daily GitHub Actions workflow calls the cleanup function instead, which is free and a pattern worth keeping in production too.
- Deferred
- A weekly email digest needs both scheduling and a paid model, and it tests retention, a question that only matters once the core loop works. It's specced and the database is ready for it, but it isn't built.
Pricing the free tier
15 entries, for good
I set the free tier at 15 entries per account for good, not per month. That's enough for a tester to experience the core record, summarize and search loop over a couple of weeks of journaling, while staying within the free transcription quota (about 40 three-minute recordings a day across all users).
The limit will be enforced on the server, with the app mirroring it to trigger the paywall. That's part of the paywall work still to build.
Path to production
A provider swap and one data migration
Everything outside the AI provider layer is shared, so moving to production doesn't touch the app or the database structure.
| Layer | MVP | Production |
| Speech-to-text | Groq whisper-large-v3-turbo | OpenAI Whisper API |
| Summary LLM | Groq Llama 3.3 70B | Claude Haiku 4.5 |
| Embeddings | all-MiniLM-L6-v2, 384d, in-function | OpenAI text-embedding-3-small, 1,536d |
| Backend | Supabase free tier | Supabase Pro |
| Scheduled jobs | GitHub Actions | Postgres pg_cron |
| Payments | RevenueCat sandbox | RevenueCat live |
| Cost per entry | $0.00 | ~$0.021 |
Speech-to-text is a URL and key change. The LLM swap changes the request format in one function. The only real migration is the embeddings: add a 1,536-dimension column, re-embed existing entries in batches, move the index, then drop the old column. It's a data job, and the app doesn't change.
Where it stands
18 of 23 stories done
Built
- Email magic-link sign-in
- Recording with a 10-minute cap
- Full AI pipeline with live status updates
- Entry archive, detail view and failed-entry handling
- Semantic search
- Soft delete, trash and scheduled cleanup
- Settings
Still to build
- Subscriptions and the purchase webhook
- The paywall and the 15-entry free limit
- Feature flags
- Apple Sign-In, deferred because magic links cover MVP sign-in
The spec sets clear criteria for moving to production: the record-to-summary loop validated with real users, semantic search judged useful, the paywall tested with sandbox purchases, and the free model's output either accepted or its gap to the paid model identified.
Want to see the spec, the decision records or the app itself?