Inspiration
Every app on my phone wants something from me: a follow, a like, a reply, a streak. One day I caught myself wanting the opposite. I wanted one small thing to show up on my home screen from one random person. A song they had on repeat, a book they loved, a movie worth a Sunday, or a kind line for the day.
I added one rule: you send first, then you receive. That rule gave the app its feeling. You wake up and wonder what you'll get today, and where in the world it's coming from. oh? runs on that anticipation.
What it does
- Once a day, you make a "thing" on a small canvas: colors, words, stickers, a song, and with oh? plus, a photo stamp.
- oh? sends it to someone, somewhere in the world. Their thing lands on your home-screen widget.
- You can send back one reaction. oh? has no profiles, no follower counts, no feed and no chat.
- A passport collects the countries your things came from and flew to.
How I built it
- SwiftUI + WidgetKit. I treated the widget as the product and the app as the kitchen. The widget renders from an App Group cache and keeps today's thing until the next one replaces it.
- CloudKit stores designs and identity. Sign in with Apple is the only login.
- A Cloudflare Worker with a Durable Object runs the matchmaker. It keeps a FIFO queue, tracks send and claim slots against each user's local day, and fires an APNs push when a match lands. The Worker handles message IDs and never sees a design.
- Moderation in layers. The sender's device screens text before sending, and the recipient's device screens it again. Photos go through Apple Vision face detection on-device, then OpenAI's omni-moderation on the Worker. If a photo fails either gate, it stays home.
- RevenueCat powers purchases behind my own SwiftUI paywall. oh? plus costs $9.99 once, with no subscription. The loop stays free for everyone: one send, one receive, full widget quality. Plus adds fonts, backgrounds and photo stamps.
- Gift a stranger oh? plus. Apple doesn't let you gift a non-consumable, so I made the gift a consumable. Buying it mints a single-use token on the Worker. The token travels through the same matcher as a daily thing, and when someone redeems it, the Worker grants them a lifetime entitlement through RevenueCat's promotional API. The buyer never learns who got it.
- PostHog tracks the two funnels I care about: widget installed → first send, and paywall viewed → purchase.
Challenges I ran into
- The widget gate met an iPad. I block the app until you add the widget, since the widget is the product. App Review opened my iPhone build on an iPad Pro in compatibility mode, where iPadOS offers no widgets, and got stuck. I shipped a native iPad build in 1.4 and limited the bypass to devices with no widget surface at all.
- A metadata rejection. Apple rejected 1.6 under Guideline 1.1 over the store listing itself. "Stranger" and "anonymous" next to an 18+ rating read like an anonymous-contact app. I rewrote the listing as a daily art exchange with "someone, somewhere in the world."
- Safety between people who never meet. I built reporting, blocking, bans, an 18+ check at sign-in and moderation that fails closed. If oh? can't screen something, oh? doesn't deliver it.
- Testing a two-person loop by myself. The gift's receiving side can't happen on one device, so I recorded two-device videos for App Review and turned on sandbox gifting on the Worker during review.
- "One a day" means your day. A send at 11pm in Mumbai and a claim at 9am in Toronto both have to count right, so the matcher keys every slot to the user's local date.
What I learned
- One rule shaped the whole product. "Send to receive" gives people a reason to open the app each morning, and the waiting makes the arrival feel good.
- App Review reads your store copy as its own submission. One word choice cost me a review cycle.
- Buy-once pricing fits a small, gentle app. People told me they'd pay for something that doesn't bill them every month, and the gift option let them pay it forward too.
- WidgetKit rewards restraint. The widget reads from cache and stays light, and the app does the heavy work.
Built With
- apns
- app-intents
- cloudflare-kv
- cloudflare-workers
- cloudkit
- core-motion
- durable-objects
- foundation-models
- ios
- lottie
- mapkit
- musickit
- natural-language
- openai-moderation
- posthog
- revenuecat
- sign-in-with-apple
- sprite-kit
- swift
- swiftdata
- swiftui
- typescript
- vision
- widgetkit
Log in or sign up for Devpost to join the conversation.