I bought a MacBook specifically for this job, and I had never used macOS before.

The work was a Fiverr contract on an existing iOS app called Speech Route. The client wanted a subscription added and the app published to the App Store. I had built and shipped things before, but never on a Mac, never with Apple subscriptions, and never through App Store Connect — which, when I opened it, was completely empty. No app record, no products, nothing.

It took at least eight rejections to get through review.

What I expected was that the hard part would be the code. It wasn't. Most of what held me up wasn't in the app at all.

The paywall loaded, and it was empty

The first rejection was the one that confused me most. The paywall opened, and no subscriptions appeared in it. Just the screen, with nothing to buy.

My instinct was that I'd implemented StoreKit wrong, so that's where I looked. The fetch was fine. The product identifiers matched. The code was doing what I'd asked it to.

The products weren't there to fetch.

This is Guideline 2.1, App Completeness. An app whose in-app purchases don't load looks unfinished to a reviewer, because it is. StoreKit does not throw when the paperwork is incomplete. It returns an empty list, which looks exactly like a failed request.

Part of the setup sat on the client's side — the app information in App Store Connect wasn't fully filled in. I wrote the App Store Connect checklist that actually unblocked it in Why StoreKit Returned No Products. The short version: the Paid Applications Agreement, tax and banking, complete product metadata, matching product IDs, and a sandbox sign-in all have to be done before the binary can see anything.

I spent hours debugging a request that was working correctly the whole time. Now, when a paywall comes back empty, App Store Connect is the first place I look, not Xcode.

Then the paywall screen itself got rejected

Once subscriptions actually appeared, the next rejection was about the paywall's interface. The problem was the buttons — the close and back controls. A user opening the paywall couldn't reliably get out of it.

That one felt unfair when I read it, and it isn't. If the only way past a purchase screen is to buy something, the app is trapping people. This is Guideline 3.1.2, Subscriptions. I wrote up the full screen requirements — dismissal, price, duration, and functional links — in Apple Rejected My Paywall Because You Couldn't Close It.

Another rejection came down to the privacy policy. The client had sent me an HTML file.

Guideline 5.1.1 wants a privacy policy URL — something publicly reachable that a reviewer can open in a browser. A file sitting on my machine, or attached to an email, is not that. The same URL also has to work as a link on the paywall. I unpacked both places in Apple Rejected My Privacy Policy Because It Was a File.

Certificates were the worst of the Mac setup

The hardest part of being new to macOS wasn't the Mac. It was signing.

Certificates, identifiers, provisioning profiles, and the app record are four separate things that all have to agree with each other, and at the start I genuinely didn't understand what any of them were or how they related. Builds failed for reasons that had nothing to do with the code I'd written, and the errors assumed I already knew the vocabulary.

I got through it with the editor in front of me and a lot of searching, one error at a time. The model I wish I'd had on day one is in Certificates, Identifiers, and Provisioning Profiles, Explained Late.

What eight rejections actually taught me

If I split those rejections into categories, almost none of them were about the app's behaviour. They were about an incomplete App Store Connect account, a paywall that didn't meet interface requirements, a privacy policy that wasn't hosted, and metadata the client hadn't provided.

That's the part I got wrong going in. I thought publishing was the last step after the code was finished. It's a separate piece of work with its own requirements, and on a first release it can easily take longer than the feature you were hired to build.

The ending was good: the client came back afterwards for UI changes, I shipped those too, and he tipped me on top of the contract. But the reason I still think about this project isn't the outcome. It's that I learned the App Store rejects you for things that have nothing to do with your code, and I now budget for that before I quote a timeline.

When I submitted my own app, Hangar, it went through on the first try. That wasn't luck. It was this list, run before I pressed submit.