This post reflects personal learning and experimentation. It does not represent commercial offerings or services.
The sixth rejection email landed at 11:47 PM on a Tuesday.
Confidence was high after fixing the fifth issue. Really high. The launch announcement was already being drafted mentally.
Then the notification: "Your submission has been rejected."
At that moment, after a year of work and six rejections, genuine questions arose about whether this was worth continuing. The pattern felt endless - fix something, wait 48 hours, get rejected for something new.
Looking back now, gratitude exists for every single one. Each rejection taught something previously unknown - not just technically, but about what users actually expect from apps they trust with their data.
Rejection 1: Account Deletion Missing
Missing required feature
Apps with accounts must let users delete their accounts. The code had sign-up and sign-in, but no delete endpoint.
// Missing: await supabase.auth.admin .deleteUser(userId); await clearAllLocalStorage();
Users expect data control
The question was "who would want to delete their account?" Wrong question. The right question: "do users have control over their data?"
- Control is a fundamental expectation
- No exit = trapped feeling
- Trust requires reversibility
Rejection 2: OAuth Race Condition
Listener registered too late
Auth listener in addPostFrameCallback created a race condition. OAuth redirect sometimes returned before listener was ready.
// Before: race condition
addPostFrameCallback((_) {
supabase.auth.onAuthStateChange...
});
// After: immediate
void initState() {
supabase.auth.onAuthStateChange...
}
"Sometimes works" is worse than "never works"
Sign-in worked 95% of the time. But the 5% failure felt random and broken. Users can't trust intermittent features.
- Apple testers hit the edge case
- Intermittent bugs feel unpredictable
- Users blame themselves, then leave
Rejection 3: Privacy Manifest Incomplete
API usage needs declared reasons
Starting 2025, apps need a Privacy Manifest. Every sensitive API (user defaults, file timestamps, disk space) needs a declared reason.
// PrivacyInfo.xcprivacy NSPrivacyAccessedAPITypes: - UserDefaults โ "CA92.1" - FileTimestamp โ "C617.1" - DiskSpace โ "E174.1"
Transparency is expected
Users increasingly care about privacy. The manifest isn't just for Apple - it's documentation of what data you access and why.
- Privacy-conscious users check labels
- Transparency builds trust
- "Why do you need this?" is fair
Rejections 4-5: Login Options & Guest Mode
Multiple auth paths needed
Sign in with Apple + Google wasn't enough. Needed email option AND working guest mode with clear upgrade path.
- Email/password auth flow
- Guest mode with feature access
- Clear "sign in to unlock" messaging
Users expect choice
Some users don't want social login. Some want to try before committing. A degraded guest experience feels punitive.
- Choice respects user preferences
- Try-before-buy reduces friction
- Degraded = punishment feeling
Rejection 6: Deep Link Handling
App-level link handling
OAuth redirects use deep links. Listening only in one screen meant links were lost if user was elsewhere.
// Before: screen-level
LoginScreen โ listen for redirect
// After: app-level
App โ listen for redirect
โ route to correct handler
Auth should "just work"
Users don't think about which screen they're on. They expect: tap sign-in, authenticate, return to app signed in. Every time.
- Context shouldn't break auth
- Users navigate unpredictably
- Edge cases feel like bugs
The Pattern
Apple's Rules = User Expectations
Every rejection had the same underlying cause: the thinking was focused on what the app does, not what users expect.
- Account deletion โ Users expect control over their data
- OAuth reliability โ Users expect sign-in to just work
- Privacy transparency โ Users expect to know how data is used
- Login options โ Users expect choice
- Guest mode โ Users expect to try before committing
Apple's review isn't arbitrary. It's forcing developers to think about user expectations, not just feature completion.
Pre-Submission Checklist
Technical verification
- Account deletion works on real device
- OAuth tested outside simulator
- Privacy Manifest complete
- Deep links work from any screen
- Guest mode is genuinely useful
User expectation check
- Can users control their data?
- Does auth feel reliable?
- Is privacy usage clear?
- Do users have login choices?
- Can users try before committing?
Key Lessons
Test on real devices, real scenarios
Simulators miss edge cases. Race conditions appear on device. Deep links behave differently. The 5% failure case is what Apple finds.
Compliance = codified user expectations
Apple's requirements aren't arbitrary bureaucracy. They're what users actually expect from apps that handle their data. Meet them because users deserve it.
The Moment It Finally Worked
After fixing the sixth issue - the deep link handling - submission happened again. Then came the wait.
App Store review typically takes 24-48 hours. This time felt longer. Every notification prompted a check. Another rejection? A question from the review team?
On a Tuesday morning, App Store Connect was checked.
"Ready for Sale."
Three words. After six rejections, dozens of fixes, and a year of work.
The interesting thing? Relief overshadowed excitement. Not "victory" but "survival." Six rejections had beaten the celebration out. What remained was just... gratitude that it was finally done.
And remarkably, gratitude for the rejections themselves. Each one forced deeper thinking about user expectations. The app that finally shipped was genuinely better than the initially submitted version.
Compliance isn't about satisfying Apple. It's about meeting user expectations that Apple has codified into rules. Every rejection made the app better - and improved thinking about what users actually need.
* * *
Related: Building GitaWisdom - the full origin story. And before submission, AI agents now review the code for compliance issues.