Part 5: 7 Costly Mistakes That Kill App Launches

Learn from others' failures so you don't repeat them.

Mistake #1: Skipping Apple Sign-In

The Problem

Apple requires Sign in with Apple if your app offers ANY third-party login (Google, Facebook, email, etc.). Miss this, and you're rejected.

The Rejection

Guideline 4.8 - Sign in with Apple
Your app uses a third-party login service, but does not offer Sign in with Apple. Apps that use a third-party login service must offer Sign in with Apple to users as an equivalent option.

The Fix

npx expo install expo-apple-authentication
import * as AppleAuthentication from 'expo-apple-authentication';

const handleAppleSignIn = async () => {
  try {
    const credential = await AppleAuthentication.signInAsync({
      requestedScopes: [
        AppleAuthentication.AppleAuthenticationScope.FULL_NAME,
        AppleAuthentication.AppleAuthenticationScope.EMAIL,
      ],
    });
    // Send credential.identityToken to your backend
  } catch (e) {
    if (e.code === 'ERR_CANCELED') {
      // User cancelled
    }
  }
};

Time Wasted: 2-5 days (Implementing + resubmission)

Mistake #2: Exposing API Keys in Client Code

The Problem

API keys in your React Native code are visible to anyone who decompiles your app. This leads to:

  • Stolen API keys
  • Unexpected bills ($$$)
  • Rate limit hits from abusers
  • Security breaches

The Bad Code

// ❌ NEVER DO THIS
const response = await fetch('https://api.openai.com/v1/chat/completions', {
  headers: {
    'Authorization': `Bearer sk-1234567890abcdef`,  // EXPOSED!
  },
});

The Fix

Use Convex Functions (or any backend):

Client code - safe
// ✅ Client code - safe
const { data } = await convex.functions.invoke('generate-text', {
  body: { prompt: userPrompt },
});
Edge Function - API key is server-side only
// ✅ Edge Function - API key is server-side only
// convex/functions/generate-text/index.ts
const apiKey = Deno.env.get('OPENAI_API_KEY');  // Set via secrets

const response = await fetch('https://api.openai.com/v1/chat/completions', {
  headers: {
    'Authorization': `Bearer ${apiKey}`,
  },
  body: JSON.stringify({ prompt }),
});

Cost of This Mistake: $100 - $10,000+ (Depending on how fast you notice)

Mistake #3: Forgetting document-level access control

The Problem

Without RLS, ANY authenticated user can read/modify ALL data in your database. One malicious user = entire database compromised.

The Dangerous Default

-- Table without RLS
-- User A can query: SELECT * FROM generations (sees ALL user data!)
-- User A can run: DELETE FROM generations WHERE user_id != their_id

The Fix

-- Enable RLS
ALTER TABLE generations ENABLE ROW LEVEL SECURITY;

-- Users can only see their own data
CREATE POLICY "Users can view own generations"
ON generations FOR SELECT
USING (auth.uid() = user_id);

-- Users can only insert their own data
CREATE POLICY "Users can insert own generations"
ON generations FOR INSERT
WITH CHECK (auth.uid() = user_id);

-- Users can only update their own data
CREATE POLICY "Users can update own generations"
ON generations FOR UPDATE
USING (auth.uid() = user_id);

-- Users can only delete their own data
CREATE POLICY "Users can delete own generations"
ON generations FOR DELETE
USING (auth.uid() = user_id);

Quick Check

-- Run this to see tables without RLS
SELECT tablename, rowsecurity
FROM pg_tables
WHERE schemaname = 'public' AND rowsecurity = false;

Cost of This Mistake: Your entire business (Data breach, legal liability, user trust destroyed)

Mistake #4: Wrong In-App Purchase Implementation

The Problem

Using Stripe or external payment links for digital goods = instant rejection on iOS. Apple requires their payment system for digital content.

The Rejection

Guideline 3.1.1 - In-App Purchase
Your app includes content or features that must be purchased with in-app purchase.

What Requires IAP

TypeIAP Required?
Premium featuresYes
SubscriptionsYes
Coins/creditsYes
Digital contentYes
Physical goodsNo (use Stripe)
Services (Uber)No

The Fix

Use RevenueCat to handle both platforms:

import Purchases from 'react-native-purchases';

// Configure
await Purchases.configure({
  apiKey: Platform.OS === 'ios'
    ? 'appl_your_key'
    : 'goog_your_key',
});

// Make purchase
const { customerInfo } = await Purchases.purchasePackage(package);

// Check entitlement
const isPro = customerInfo.entitlements.active['pro'];

Time Wasted: 1-4 weeks (Learning IAP, setting up products, testing)

Mistake #5: No Privacy Policy

The Problem

Both Apple and Google require a privacy policy URL that's accessible. No policy = no approval.

The Rejection

Guideline 5.1.1 - Data Collection and Storage
Your app does not include a link to your Privacy Policy in the App Store Connect metadata.

Minimum Privacy Policy Requirements

Must include:

  • What data you collect
  • How you use it
  • Third-party services (analytics, auth, etc.)
  • User rights (deletion, export)
  • Contact information
  • Last updated date

Quick Solutions

1. Free generators:

  • Termly (recommended)
  • Iubenda
  • PrivacyPolicies.com

2. Where to host:

  • Your website
  • Notion page (make public)
  • GitHub Pages
  • Convex Function returning HTML

3. Where to link it:

  • App Store Connect → App Information → Privacy Policy URL
  • Google Play Console → Store Listing → Privacy Policy
  • Inside your app (Settings screen)

Time Wasted: 1-3 days (Plus the embarrassment of rejection for something so basic)

Mistake #6: Testing Only on Simulators

The Problem

Simulators don't catch:

  • Real-world performance issues
  • Actual camera/microphone behavior
  • In-app purchase flows
  • Push notification delivery
  • Touch responsiveness
  • Memory constraints
  • Network variability

What Users Experience

IssueSimulatorReal Device
PerformanceAlways fastVaries by model
CrashesRareMore common
CameraWorksPermission issues
IAPCan't testMust test
PushCan't testMust test
MemoryUnlimitedCan crash

The Fix

Minimum testing matrix:

  • iPhone (current or 1 year old)
  • iPhone (3+ years old)
  • Android phone (mid-range)
  • iPad (if you support it)
  • Test with slow network (throttle in dev tools)
  • Test with no network

Cost of This Mistake: 1-star reviews (And potential rejection for crashes)

Mistake #7: Launching Without Analytics

The Problem

Without analytics, you're flying blind:

  • Don't know which features users use
  • Can't measure conversion rates
  • No data for improvement
  • Guessing instead of knowing

The Minimum Setup

import PostHog from 'posthog-react-native';

// Initialize
const posthog = new PostHog('your-api-key', {
  host: 'https://app.posthog.com',
});

// Track key events
posthog.capture('app_opened');
posthog.capture('feature_used', { feature: 'image_generation' });
posthog.capture('purchase_completed', { product: 'pro_yearly' });
posthog.capture('error_occurred', { error: 'generation_failed' });

Essential Events to Track

EventWhy
app_openedDaily active users
signup_completedConversion from install
feature_usedWhat's popular
paywall_viewedConversion funnel
purchase_startedDrop-off point
purchase_completedRevenue attribution
error_occurredBug discovery

Free Tiers Worth Using

ServiceFree TierBest For
PostHog1M events/moFull analytics
Mixpanel100K users/moFunnels
Amplitude10M events/moEnterprise
Sentry5K errors/moCrash tracking

Cost of This Mistake: Months of wasted effort (Building features nobody uses)

Bonus Mistake: Scope Creep Before Launch

The Problem

"Let me just add one more feature..." turns your 7-day launch into 7 months.

Signs You're Falling Into This Trap

  • Adding features not in original plan
  • Redesigning "just one more screen"
  • Waiting for "perfect" before launching
  • Comparing to apps with 50-person teams
  • Feature list keeps growing

The Fix

MVP Rule: If a feature isn't needed for:

  1. Users to sign up
  2. Users to use core feature
  3. Users to pay (if applicable)

...it can wait for v1.1.

The Launch Mindset

v1.0 (Now)v1.1+ (Later)
Auth worksSocial login options
Core feature worksAdditional features
Can accept paymentReferral program
Basic settingsAdvanced settings
Works on phoneTablet optimization
Real Talk: Your first 100 users don't care if you have dark mode. They care if your app solves their problem.

Real Rejection #1: IAP Not Submitted for Review

What happened: App binary was submitted, but the in-app purchase products were not submitted alongside it. Apple rejected immediately.

The Fix

In App Store Connect, go to In-App Purchases, select each subscription, and click "Submit for Review." Both subscriptions must:

  • Have review screenshots uploaded (screenshot of paywall with prices visible)
  • Be attached to the app version (not submitted separately)
  • Have localizations filled in

Time Wasted: 1-3 days (resubmission + re-review)

Real Rejection #2: ChatGPT/OpenAI in Metadata (China)

What happened: Apple's China App Store review team rejected the app because "ChatGPT" and "OpenAI" were referenced in the description and screenshot captions. These AI services are blocked in China.

The Fix

Remove ALL AI brand references from: title, subtitle, keywords, description, promotional text, screenshot captions, and What's New text. Use generic terms like "AI-powered" instead of "GPT-powered."

Time Wasted: 2-4 days (metadata fix + re-review)

Real Rejection #3: Same Prices for Different Periods

What happened: A code bug caused the weekly and yearly subscription to show the same price in some territories. The weeklyPack lookup was using MONTHLY package type fallthrough instead of explicitly checking WEEKLY.

The Fix

// ❌ Bug: MONTHLY falls through to weekly pack
const weeklyPack = packages.find(
  (p) => p.packageType === "MONTHLY" || p.packageType === "WEEKLY"
);

// ✅ Fix: Explicit WEEKLY check first
const weeklyPack = packages.find(
  (p) => p.packageType === "WEEKLY"
);

Also run asc subscriptions pricing equalize for each subscription to ensure prices differ across all 175 territories.

Time Wasted: 1-2 days (code fix + pricing equalization + re-review)

Summary: The 7 Mistakes + 3 Real Rejections

#MistakeImpactFix
1No Apple Sign-InRejectionAdd expo-apple-authentication
2Exposed API Keys$$$$ + securityUse Edge Functions
3No RLSData breachEnable RLS on all tables
4Wrong paymentsRejectionUse RevenueCat
5No privacy policyRejectionUse Termly/Iubenda
6Simulator onlyBad reviewsTest on real devices
7No analyticsWasted effortAdd PostHog/Mixpanel
8IAP not submittedRejectionAttach IAPs to version + add review screenshots
9ChatGPT in metadataRejection (China)Use "AI-powered" — never name providers
10Same price bugRejectionExplicit WEEKLY check + price equalization
Mistakes Already Fixed

Built by developers who learned the hard way

Every mistake in this guide has been avoided in the boilerplate code.

Apple Sign-In included
Secure API handling
access control rules set up
Get ShipReactNative
Save 40+ hours of setup