Build log · Month 5 of 12
The App Store Gauntlet
the craft between "works on my simulator" and "a stranger installed it." Privacy manifests, account deletion, Sign in with Apple, RevenueCat wiring, and eating review rejections.
- Day 121
This month: the App Store. The gap between "runs in my simulator" and "a stranger downloaded it" is full of craft nobody tweets about — privacy manifests, account deletion, review rejections. So I'm going to tweet about it. Someone has to.
- Day 122
The privacy manifest (
PrivacyInfo.xcprivacy) is mandatory now. You declare every data type you collect and why, plus "required reason" APIs. I keep a shared manifest in the core and each app extends it. Declare it once, honestly, or fail submission. - Day 123
Rejection #1: CalDesk got bounced for requesting health-adjacent data without a clear enough usage string. The
NSUsageDescriptioncan't be lazy — "needs your data" gets rejected; "to log meals against your daily nutrition goals" passes. Say the actual why. - Day 124
Account deletion is non-negotiable: if users can sign up in-app, they must be able to delete the account in-app, not via support email. Built one shared flow in the core: confirm, revoke tokens, purge server data, sign out. Every app inherits it.
- Day 125
Account deletion has a nasty edge: what about purchases, synced data, an in-flight subscription? I delete identity + personal data, keep anonymized aggregate telemetry (never tied to them anyway), and explain exactly that on the confirm screen.
- Day 126
🧵 Thread: the App Store submission checklist I wish I'd had on day one.
- Privacy manifest declaring every data type + required-reason APIs. Wrong = rejection.
- In-app account deletion if you have accounts. Support-email-only won't pass.
- Sign in with Apple if you offer any third-party social login. Required, not optional.
- Real usage strings: state the actual purpose, not "needs access."
- No placeholder content, no broken links, no "coming soon" buttons. Reviewers tap everything.
- Day 127
Sign in with Apple rule that catches everyone: if you offer Google/social login, you MUST also offer Sign in with Apple. Not a preference — guideline 4.8 territory. I built it into shared auth so no app can ship social login without it. Compliance by architecture.
- Day 128
Sign in with Apple gotcha: Apple gives you the user's email/name only on the first authorization, once, ever. Miss it and you can't get it again without the user revoking and re-authing. I persist it on first callback immediately. There's no second chance.
- Day 129
RevenueCat keeps me sane across a fleet of paywalls. One dashboard, one SDK, handles receipt validation, subscription state, cross-platform entitlements. Rolling my own StoreKit receipt validation twelve times is how I'd lose the year. Buy the boring infra.
- Day 130
StoreKit bug: entitlement state is async and can lag on cold start. I flashed the paywall to a paying user for a split second before the receipt resolved. Fix: default to "loading," never "locked," until entitlement is known. Never accuse a paying customer.
- Day 131
Reviewers tap everything. A rejection came because a settings link went to a placeholder page. They grade the build in front of them, not your intent. So I do a full "tap every button, follow every link" pass before submission. No dead ends.
- Day 132
App Review notes are underrated. A clear note — test creds, how to trigger the paywall, why you need a permission — turns a likely rejection into an approval. Reviewers are humans with a queue. Make their job easy and they'll make yours easy.
- Day 133
Screenshots are marketing, not documentation. My first set was literal UI dumps and they were dead. Redid them with a one-line value prop per shot. Same app, very different tap-through. The store listing is the top of your funnel — don't treat it as an afterthought.
- Day 134
Rejection #3: a demo account that "didn't work" for the reviewer — because it required an email verification they couldn't complete. Fix: a pre-verified review account that skips the friction. If your reviewer can't get in, you're rejected regardless.
- Day 135
Guideline 2.1 (completeness) is where "minimum viable" dies. An app with three real screens and nine "coming soon" gets bounced. Ship fewer, finished screens. Better a small complete app than a big skeleton. Reviewers can smell a demo.
- Day 136
🧵 Thread: my per-app App Store submission runbook.
- Version + build number bump. Changelog written for humans, not "bug fixes."
- Screenshots refreshed, one value prop per shot.
- Privacy manifest diff reviewed. New data collection = new declaration.
- Reviewer note: test creds, how to trigger the paywall, why each permission.
- Tap every button, follow every link. No dead ends.
- It's a checklist you rerun: 20-minute submit vs. a lost afternoon.
- Day 137
TestFlight before the store, always. External testers catch the "works on my device" bugs — older phone, different locale, tiny screen. My simulator is a liar of omission; it never runs out of storage or loses signal mid-sync. Real devices, real conditions.
- Day 138
AdMob for the free apps (Cipher, Vertex): mediation set up, but the real work is where the ad goes. An interstitial mid-puzzle would nuke retention. Ads at natural stops — after a result, between sessions — not mid-flow. Monetize the pause, never the peak.
- Day 139
App Tracking Transparency: touch the IDFA and you must ask, most users say no, and that's fine. I designed the ad setup to work without tracking consent, because assuming a "yes" you won't get is building on sand. Consent-optional by default.
- Day 140
Halfway through the year next month, and here's the App Store truth: getting approved is a solvable checklist. Getting found in the store is the actual boss, and no privacy manifest helps with that. Approval is table stakes. Discovery is the war.
- Day 141
Localization is a distribution lever I underused. Just localizing the store listing (title, keywords, screenshots) for a few languages opens whole markets the algorithm otherwise hides you from. Translating the listing is cheaper than the app and moves installs.
- Day 142
Keyword craft in App Store Connect is brutal: 100 characters, comma-separated, no wasted spaces, don't repeat your app name (already indexed). I treat it like a tiny SEO problem. The store search bar is a distribution channel I ignored for months.
- Day 143
A rejection I deserved: shipped a build with a debug menu reachable by a long-press I forgot to gate. Reviewer found it. Embarrassing and fair. Now the release build strips debug affordances via a compile flag. If a reviewer can reach it, so can a user.
- Day 144
In-app purchase restore is required and everyone forgets it. New phone, reinstall, "where'd my subscription go" — you MUST have a "Restore Purchases" button. RevenueCat makes it one call, but you still have to add the button. Missing it = instant rejection.
- Day 145
Opinion: Apple's rules are annoying and mostly right. Account deletion, privacy manifests, restore purchases, Sign in with Apple — I resented each and each protects the user. The gauntlet makes the fleet better. I just wish it told you the rule before the rejection.
- Day 146
The economics no one mentions: $99/yr across a 12-app fleet is nothing per app. The expensive part is the time per submission — screenshots, review notes, rejection round-trips. So I systematized it. Amortize the process, not just the fee.
- Day 147
Shipped a fleet-wide compliance pass: every app with accounts now has in-app deletion, Sign in with Apple, restore purchases, and a privacy manifest — all inherited from the core. Fix the platform once, twelve apps comply. Architecture beats checklists.
- Day 148
Month 5 lesson: "done" has a legal and platform definition, not just a technical one. Code that works isn't shippable; code that works and passes review and respects data rights is. The gauntlet is where hobby projects become real products.
- Day 149
Next month: Huddle, self-hosted team chat on Matrix + LiveKit. After a month of platform rules, I get to build platform infrastructure — real-time messaging and screenshare. The hardest distributed-systems problems in the fleet. Excited and slightly scared.
- Day 150
[N] approvals, [N] rejections, [N] resubmissions this month, bracketed until I total them honestly. The store is no longer a mystery gate — it's a checklist I can run. That's a real unlock. Now back to deep engineering. Real-time systems, here we go.