Inspiration

Meeting-notes apps ask for two things we weren't willing to give: our audio, and our knowledge. They upload the recording, and the result lives inside their product. And almost all of them charge a monthly subscription whether you had twenty meetings that month or none.

We wanted the opposite: audio that never leaves the phone, notes that land in the knowledge base you already own, and a price that is zero in a month you don't use it.

What it does

  1. Set up a project once — what kind of document, which folder, and (optionally) a recurring schedule so the right project is picked automatically.
  2. Press record, press stop. That's the whole interaction.
  3. After a 20-second grace period (cancel if you didn't mean it), the audio is transcribed on-device.
  4. Only the transcript text is sent to generate the document.
  5. A Markdown note lands in your Obsidian vault (any folder in iCloud Drive). You can also send it to Notion or share the .md file.

English, Japanese, and Spanish — for the UI and the output. The spoken language and the output language are chosen separately.

How we built it

  • iOS: Swift / SwiftUI, SwiftData, SpeechAnalyzer (iOS 26, on-device), XcodeGen.
  • Purchases: RevenueCat — consumable in-app purchases feeding a Virtual Currency balance. Only RevenueCat grants credits; the app never does. Sign in with Apple, but we store only a hash of Apple's user identifier — no name, no email.
  • Backend: AWS (CDK) — API Gateway, Lambda, SQS, DynamoDB, S3, AppConfig — separate dev and prod accounts.
  • AI: Claude Sonnet 5 via the Anthropic API, through the Message Batches API (half the cost), with a fallback to a normal call if a batch is slow.

Challenges we ran into

  • Pricing by characters would have overcharged non-Japanese speakers by 1.5×. The same 30-minute meeting is 9,042 characters in Japanese but 18,084 in English and 18,308 in Spanish — while the actual model cost is nearly identical. So we charge by tokens: the same meeting costs the same 2 credits in all three languages.
  • AVAudioRecorder writes its index only when you call stop(). When the app was killed mid-recording, we lost 50 minutes of audio that was on disk but unreadable. We now finalize a segment every minute and record it in a ledger the moment it opens — a crash loses at most one minute. A 108-minute recording (110 segments) went all the way through to a document.
  • Double charging when only the response is lost. If the server accepts and charges but the reply never arrives, the phone retries — and a server-generated job ID would create a second charge. The job ID is now derived deterministically from a key the device holds, so a retry is recognized as the same job.

Accomplishments that we're proud of

  • Audio that structurally cannot leave the phone. "We don't upload your audio" is not a policy here, it's the shape of the app: there is no code path that sends audio anywhere, and a test reads the source to keep it that way.
  • A recording that survives the app dying. AVAudioRecorder only writes its index when you call stop(), so a crash used to leave audio on disk that nothing could open — we lost 50 minutes that way. Now a segment is finalized every minute and written to a ledger the moment it opens. A 108-minute recording came back as 110 segments, merged, transcribed, and turned into a document.
  • Pricing that is fair across languages. Billing by characters would have charged English and Spanish speakers 1.5x more for the same meeting. Billing by tokens makes the same 30-minute meeting cost the same 2 credits in Japanese, English, and Spanish.
  • A price you can trust. The app shows "up to N credits" before it runs and the server never charges more; it refunds automatically when we fail to deliver; and a retry after a lost response cannot double charge, because the job ID is derived from a key the device already holds.
  • Notes that don't invent people. In the demo you can see the minutes leave the Owner column empty — the talk never named anyone, so neither did we.
  • One 8-minute talk becoming four different documents — a seminar report with analogies and counterarguments, lecture notes with review questions, an idea memo, and minutes — all from a single recording.
  • Shipped solo: iOS app, AWS backend, RevenueCat purchases, and legal pages, in three languages, as a first release.

What we learned

  • The dangerous bugs are the quiet ones. Fifteen times we fixed one place and left another saying the old thing. Not one of those was caught by a test — the code was right and only the explanation was wrong. The screen said "split into parts (because of interruptions)" long after we had started splitting every recording every minute on purpose.
  • A check that cannot fail checks nothing. Our pack checker reported "amounts match" while nothing was actually purchasable, because it never looked at the storefront. Our pricing test vectors all sat on the same side of the threshold, so we could change the formula and nothing went red. We now ask "what would make this fail?" before trusting a green result.
  • Guards you add for people break the paths where nobody is standing. Adding sign-in to document creation silently broke scheduled, unattended creation: you cannot present an authentication sheet when no one is there. The fix was to remove the ability to ask on that path, not to add a condition.
  • "The right size" is not "the right picture." Our screenshot checker passed three times on images showing the wrong screen. Machines can only check shape; a person still has to look.
  • RevenueCat specifics worth knowing: virtual currency does not transfer between App User IDs, so sign-in has to come before purchase or the credits land nowhere; an idempotency key lets you grant a welcome bonus without keeping a ledger of who already got one; and a blocked duplicate grant still returns 200, so report the change in balance, not the amount you asked for.
  • Apple will reject a privacy manifest for a comment. Our first build came back as ITMS-91056 because an XML comment contained "--" inside a Markdown table. plutil had happily accepted it. Lenient parsers hide what strict ones reject.

What's next for Pochi and Moji

  • Let people choose "nothing retained" over "fast and cheap". We run generation through the batch API at half price today; a zero-data-retention path costs exactly 2x that, and we would rather make it a visible choice than decide for everyone.
  • Finish the legal work in the open. Two questions are with specialists: whether purchased credits count as a prepaid payment instrument under Japanese law, and whether a contact form satisfies the e-mail requirement in commercial-transaction disclosures. The terms exist in Japanese; English and Spanish are next.
  • Fix what this submission's own screenshots revealed: an English cancel button that reads "Cancelled" and a "Up to 1 credits" that needs a singular.
  • Measure instead of sampling. We say the app is designed not to attribute action items to people who were never named — we've watched it, but we haven't measured it across runs. That is a number we owe ourselves.
  • More destinations, and faster feedback while a long recording is still running.

Built With

Share this project:

Updates

Submission history