Inspiration Alarm sounds are designed to be hated. That is the whole trick — they work because they are unpleasant enough to force you out of bed, and the cost is that the first thing you feel every morning is irritation.

We wanted the opposite: an alarm you actually want to hear. Not a nicer beep, but a voice saying something written for you, at the moment you are least able to ignore it.

What it does Self is an iOS alarm that wakes you to an AI voice reading your own script.

Choose from 12 voices, write up to 300 characters, and the app generates the audio and installs it as the actual alarm sound — not a notification played after you unlock the phone. Premium adds this morning's real weather, read aloud in the same voice, so you know what to wear before you get out of bed. Wake-up streaks are only confirmed when you open the app shortly after stopping the alarm, so going back to sleep does not count as getting up. Shipped in 10 languages, with the recommended voices chosen per language. How we built it SwiftUI for the whole iOS front end. AlarmKit (iOS 26) for scheduling. Generated audio is written into the app's Sounds directory and registered as a named alert sound, with ActivityKit for the Live Activity and App Intents for the Stop and Snooze buttons. WeatherKit for the forecast sentence that gets spliced into the script. ElevenLabs for text-to-speech, called only from the server. Firebase — Auth (Sign in with Apple, Google, email, plus anonymous accounts so onboarding never starts with a signup wall), Firestore, Storage, and Cloud Functions on Node 22. RevenueCat for subscriptions and entitlements. Sentry for crash and error reporting, with dSYM upload wired into the build. How we used RevenueCat The important decision was where the paywall lives. Voice generation costs real money per character, so checking the entitlement on the device was never going to be enough — the check had to sit next to the spend.

The app configures the SDK and calls logIn(firebaseUID), so app_user_id is always the Firebase UID. One identity across the client, the webhook, and Firestore. A RevenueCat webhook hits a Cloud Function that writes isPremium into Firestore. Firestore rules make that field server-writable only, so a user cannot grant themselves Premium and bypass the free quota. The generation function reads isPremium before it ever calls ElevenLabs, and enforces the free tier there. Free accounts get all 12 voices and unlimited template scripts, but only three custom scripts a month and no preview before saving. Premium removes the cap, adds preview, and adds the weather readout, with a 7-day free trial. TRANSFER events are handled explicitly — the source user loses Premium and the destination gains it. Acting on a single app_user_id would have flipped the wrong account. CANCELLATION is deliberately ignored: turning off auto-renew is not the same as losing access until the period ends. We wrote a one-shot backfill endpoint against the RevenueCat REST API to sync existing subscribers before tightening the Firestore rules. Challenges we ran into Locking down entitlements without locking out paying users. Webhooks only carry events that happen from now on. If we had shipped the stricter Firestore rules first, every existing subscriber would have silently dropped to the free tier until their next renewal. The backfill had to run first, and we had to prove it ran before the rules went live.

A free tier that quietly shrank. The free voice IDs were listed in two places, and the app's list drifted out of sync with the catalog. Kai and Noah became Premium-only without anyone changing a price. It looked like a pricing decision and it was a typo.

Silent vendor failure. Voice generation was completely broken for six days before we noticed, because a billing failure upstream returned an error that looked like every other error in the logs. We now log that specific case loudly and cache generated audio so the same script never gets billed twice.

The platform says no. Users asked for a vibration toggle on alarm fire. AlarmKit exposes no haptics control at all, so the honest options were to ship a switch that does nothing or ship nothing. We shipped a settings section that explains where iOS actually controls it and deep-links to Settings instead.

What we learned Put the entitlement check where the money is spent. A client-side isPremium is for hiding UI. It is not a paywall. Subscription events are not a boolean. Transfers, cancellations and expirations mean different things, and treating them as one flag produces bugs you only find in someone else's account. Anonymous-first onboarding converts. Letting people build their first alarm before asking for an account, and framing the paywall as "try it free" rather than "buy now", moved completion noticeably. Gate the thing that actually costs money. We originally locked most of the voice cast behind Premium. That was the wrong lever: voices cost nothing to list, while every generation is billed per character. So we unlocked all 12 voices for everyone and capped custom scripts instead. Free users now meet the whole cast on day one and hit the paywall only at the point where we are genuinely paying. Don't let the trial spend the trial. Previewing a voice during onboarding used to consume a free generation, so a new user could exhaust their monthly quota before building a single alarm — and then got a network error that explained nothing. Onboarding greetings are now exempt server-side. Don't ship controls that do nothing. A toggle that cannot work is worse than an explanation of why. What's next for Self Your own voice. Clone it once, then write any script you want — a new line every morning, in a voice that is actually yours.

A marketplace for voices. Sell the voices you create, or wake up to one you bought. Creators get paid, and the payouts run through the same RevenueCat stack that already handles subscriptions — a second revenue stream on infrastructure we have already proven.

Android is on the list after that.

Built With

  • activitykit
  • alarmkit
  • app-intents
  • app-store-connect-api
  • cloud-firestore
  • cloud-functions
  • elevenlabs
  • firebase
  • firebase-auth
  • firebase-storage
  • google-sign-in
  • ios
  • node.js
  • revenuecat
  • sentry
  • sign-in-with-apple
  • swift
  • swiftui
  • weatherkit
  • xcode
Share this project:

Updates

Submission history