Back to Projects

    GetNotes.ai — AI Meeting Notes SaaS

    GetNotes.ai is a shipped AI meeting-notes SaaS I built and priced myself. It records or ingests meeting audio, transcribes it with ElevenLabs Scribe in 90+ languages with speaker labels, then has Claude write structured minutes: summary, decisions and action items. The free tier is one 30-minute meeting; Pro is $19 per month.

    2026
    Founder & Full-Stack Engineer
    11 Technologies
    Next.js 16 (App Router)React 19TypeScriptSupabase (Postgres, Auth, Storage, RLS)ElevenLabs Scribe (scribe_v1)Anthropic ClaudeStripeExpo / React Nativeexpo-audiomusic-metadataVercel
    GetNotes.ai — AI Meeting Notes SaaS

    Verify it yourself

    Introduction

    Most meeting-notes tools are bots that join a video call. GetNotes.ai records the room you are actually sitting in, from a laptop or a phone, and turns it into minutes you can send. I built it as a product with a price on it, not a demo, which meant every decision had to survive contact with real billing and real API costs.

    The Challenge

    The interesting constraint on a product like this is not the AI, it is the unit economics. Transcription and notes both cost money per minute of audio, and a single user with a 3-hour recording habit can quietly outspend their subscription. The other constraint is trust: people record meetings they care about, so losing a recording to a mid-pipeline failure is worse than producing mediocre notes. I also needed the phone case to work properly, and mobile browsers suspend MediaRecorder the moment the screen locks, which is fatal for a 45-minute meeting in someone's pocket.

    The Solution

    I built a single Next.js 16 codebase that serves the marketing site, the app UI and the REST API. Audio goes straight from the browser to a private Supabase Storage bucket under a per-user folder; the process route only ever receives a storage path, which it validates against the caller's user id. That route then runs the pipeline in order: authenticate, check the plan, check the meeting quota, check duration and file size, check the monthly fair-use cap, transcribe, write notes, persist. Every gate that costs nothing runs before the two calls that cost money. A separate Expo React Native app covers the pocket-recording case with background-capable audio and reuses the same API.

    Technical Deep Dive

    1

    Plan limits live in one file, lib/plans.ts, read by the process route, the usage endpoint and the UI. Free is one meeting ever (a lifetime quota window, not monthly), 30 minutes, with a 60 MB upload backstop. Pro is unlimited meetings, 3 hours per recording, a 400 MB backstop and a 25-hour monthly audio cap. One source of truth is why the client can pre-check limits and still be honest: the server enforces the identical numbers. The fair-use cap is a duration cap, not a meeting cap, because meetings vary wildly in length and a meeting count says nothing about cost. The route sums duration_seconds across the user's non-error meetings since the start of the calendar month and rejects with a 402 and a machine-readable monthly_audio_exceeded before it calls ElevenLabs or Claude.

    2

    The meeting row is inserted as status processing before any audio is downloaded. That row holds the quota slot, so two parallel requests from the same user cannot both pass a count check that neither has yet incremented. It also gives the UI something to poll and gives failures somewhere to record an error_message instead of vanishing. Duration is then measured server-side with music-metadata against the downloaded buffer, because browser-recorded WebM often ships without usable metadata, and over-limit audio is marked errored and deleted from storage rather than left in a bucket I pay for.

    3

    Transcription uses ElevenLabs Scribe with diarize set to true, and I post-process the word-level response myself rather than taking the flat text. Words are grouped into lines when the speaker changes or after a silence gap of more than 8 seconds, and each line is stamped as [MM:SS] Speaker N. That structure is what makes the minutes attributable, and it is what Claude sees.

    4

    Notes generation is deliberately non-fatal. A transcription failure returns a 502 and stops the pipeline, but if Claude errors or returns stop_reason refusal, the transcript is still saved and the response carries a notesGenerated flag plus a human-readable notesError. Input and output token counts are stored per meeting so cost per meeting is measurable rather than guessed. Because the prompt asks Claude to wrap critical phrases in mark tags, model output reaches the DOM, so the markdown converter escapes all HTML first and then re-enables only that one tag.

    5

    The browser recorder mixes the microphone with optional tab audio captured through getDisplayMedia, joined in an AudioContext destination stream, so an online meeting can be captured without a bot joining the call. iOS Safari cannot record audio/webm, so the recorder falls back to audio/mp4 when the type is unsupported.

    6

    Subscription state is written only by the Stripe webhook, through the service-role client, keyed on stripe_customer_id with fallbacks to client_reference_id and customer metadata. The endpoint subscribes to exactly the three events the handler implements. Row-level security on profiles allows reads of your own row and no user writes at all, so a user cannot promote themselves to Pro.

    7

    Mobile reuses the web API instead of duplicating it. The route-handler Supabase client accepts either scheme: session cookies for web, or an Authorization bearer token for the Expo app. Because quotas, plan gating and processing all key off the authenticated user rather than the client type, that one fallback was the only backend change the app needed.

    Key Features

    Record the room or upload a file

    A browser recorder with a live waveform, pause and resume, plus optional tab-audio capture for online calls. Or drag in existing audio: MP3, M4A, WAV, WebM and MP4 are accepted, with a client-side duration probe before upload.

    Speaker-labelled transcripts in 90+ languages

    ElevenLabs Scribe runs with diarization on, and the word-level output is reassembled into timestamped speaker lines. The detected language code is stored on the meeting and passed to the notes model.

    Structured minutes, not a blob of summary

    Claude writes to a fixed contract: a 3 to 6 bullet summary, decisions, action items formatted as owner plus task plus deadline, and key discussion points. Decisions and action items return None recorded when there are none, instead of being padded.

    Two tiers with limits the server actually enforces

    Free gives one meeting up to 30 minutes with no credit card. Pro is $19/month with unlimited meetings, up to 3 hours per recording, and a 25-hour monthly fair-use allowance surfaced in the app as a usage meter that resets on the 1st.

    Private by construction

    Audio lives in a non-public Supabase bucket under a per-user folder, with storage policies that check the folder name against auth.uid(). Meetings and profiles are behind row-level security, and deleting a meeting removes its audio too.

    Native mobile companion

    An Expo app with background-capable recording (iOS audio background mode, Android foreground-service microphone permission), share-sheet ingest for audio files, and the same account as the web app. Billing stays on the web in v1.

    Results & Impact

    • The product is live at www.getnotes.dev with the full loop working: sign up, record or upload, transcribe, generate minutes, browse history, manage the subscription through the Stripe billing portal.
    • Pricing is implemented, not aspirational: a $19/month recurring price, a Checkout flow, a billing portal configured to cancel at period end, and a signature-verified webhook that is the only writer of plan state.
    • The mobile app was built against the shipped backend with a single additive server change, which is the practical proof that the API contract was designed for more than one client.
    • The marketing surface is programmatic across five axes (languages, professions, meeting types, competitor comparisons and markets), with 81 URLs in the published sitemap as of July 2026.
    • Stripe is currently provisioned in test mode. Going live means recreating the product, price, webhook and portal config in live mode, which the repo documents as a CLI script rather than leaving as tribal knowledge.

    Lessons Learned

    "Unlimited is a pricing bug when your cost of goods is per minute. I chose a fair-use cap measured in hours of processed audio rather than a meeting count, because meeting counts are uncorrelated with cost. Saying 25 hours a month out loud is also a better user experience than throttling quietly once someone gets expensive."

    "Order the pipeline by cost, not by narrative. Auth, quota, duration and the monthly cap are all cheap checks, and every one of them runs before the first paid API call. The only check that cannot run early is true duration, which is why the audio download sits between the quota gates and transcription."

    "Decide in advance which stage of an AI pipeline is allowed to fail. Transcription is the irreplaceable artifact, so it is fatal. Notes are regenerable from the transcript, so a refusal or an API error downgrades the result instead of destroying it. That single decision removed most of the support surface."

    "The web and mobile split was not a rewrite because the API was already the product. Cookies for browsers, bearer tokens for native, one set of route handlers. Copying the plan-limits module into the mobile app verbatim, so both clients pre-check against the same numbers the server enforces, is what made that cheap."

    Conclusion

    GetNotes.ai is what I reach for when someone asks whether I can ship a product rather than a prototype: real auth, real row-level security, real subscription state, real limits, and an AI pipeline whose failure modes were designed before its happy path. The engineering that matters here is not the model call. It is everything around it that decides whether the model call is affordable and safe to run.

    Frequently asked questions

    What does GetNotes.ai cost?
    The free tier is a one-time trial: one meeting, up to 30 minutes, no credit card. Pro is $19 per month for unlimited meetings, up to 3 hours per recording, with a fair-use allowance of 25 hours of processed audio per month. Those numbers are enforced in code, not just written on the pricing page.
    Why a fair-use cap instead of genuinely unlimited?
    Because transcription and note generation cost money per minute of audio. A meeting count would not bound that, since one user's meeting can be five minutes and another's three hours. Capping processed audio hours ties the limit directly to cost, and stating it plainly beats silently throttling heavy users.
    How does it handle long recordings?
    Pro allows 3 hours per recording, with duration measured server-side from the audio file rather than trusted from the client. The processing route runs on the Node runtime with an extended maxDuration, and the repo documents raising that limit for full 3-hour audio. The plan for flaky mobile networks is a poll-based flow so the client is not holding a long request.
    Is the source code open?
    No. Both the web and mobile repositories are private, because this is a commercial product rather than a library, and the Stripe and Supabase configuration lives alongside the code. The running application is public at www.getnotes.dev, free trial included, so you can verify the transcription and notes pipeline yourself without needing to read the source.
    Does it identify who said what?
    Yes. ElevenLabs Scribe runs with diarization enabled, and the word-level response is reassembled into timestamped lines that start a new block whenever the speaker changes or after a silence gap longer than 8 seconds. The minutes are generated from that speaker-labelled transcript, so action items can carry owners.
    What happens if the AI fails partway through?
    It depends on the stage. A transcription failure marks the meeting errored and returns a 502, because there is nothing to salvage. A notes failure or a model refusal is non-fatal: the transcript is saved, the response flags that notes were not generated, and it explains why. You never lose the recording's transcript to a model problem.
    Can this architecture work with my stack?
    The pattern transfers. Direct-to-storage uploads, a single plan-limits module read by both server and client, quota checks ordered before paid API calls, and one REST surface serving both a web app and a native client are all stack-agnostic. I have implemented the same shape on Next.js with Supabase and can do it on a Python backend.

    Interested in a Similar Project?

    Let's discuss how I can help bring your ideas to life.

    Get in Touch

    Let's Create a Revolution