Shipping · September 2026 · 8 min
What does it actually mean to ship?
A conversation about the point where working software has to become something another person can actually use.
So when did Tilawa start feeling real to you?
It was when somebody else had to install it without me sitting there. I had already made calls between two phones by then, and of course that felt good, but I knew too much about the app for my own tests to mean what I thought they meant. If a button needed a second tap, I already knew. If a teacher had to keep the app open for a call to come through, I knew that too, so I was unconsciously arranging everything for the test to pass.
Then you give the build to somebody else and they do very normal things you somehow did not account for. They leave the app, lock the phone, take longer on a screen or tap something in an order you were not expecting. You cannot explain each moment to them, and you should not have to. That was when I started seeing the difference between something I could demonstrate and something another person could use.
But the actual call was working, right? What was still left?
The call was working, but a student does not begin with the call. They have to get the app, sign in, understand what they are looking at, find an available teacher and know what is happening while they wait. The teacher has to receive the request even if the phone is locked, and both people need the session to end properly if the connection becomes unstable. After that there are minutes, payments, history, support and all the admin work behind it.
I think I used to look at those as separate things around the main feature. Once I was preparing Tilawa for other people, they stopped feeling separate. If the incoming call does not arrive, it does not matter that the audio code is good. If the user pays and the minutes do not appear, the payment screen being clean does not help. The person uses the whole chain, so the whole chain is the product.
You had quite a time getting the iOS build out. What happened there?
There were a few rounds. One time Xcode finished the upload and told me it had completed, but I went to App Store Connect and there were zero builds. I kept looking because from my side the upload had worked. Apple later sent an email saying a camera-purpose string was missing from Info.plist. One of the libraries referenced camera APIs, so the declaration was required even though Tilawa is audio-first.
After that I was dealing with privacy information, signing, the splash screen, the app icon, build numbers and warnings about missing symbols inside WebRTC and Objective-C frameworks. Some warnings did not stop the upload, while other issues meant the build would never appear, and you have to learn the difference. It was frustrating, but it made me understand that getting software onto somebody’s device is engineering work too. The code can be fine and the product can still be unavailable.
Was there anything that could have passed all of that and still failed?
Yes, the production configuration. With Flutter, I could archive a build from Xcode and get a valid binary without necessarily compiling the right environment values into it. So the app could install, show the correct logo and look completely fine, then authentication and the server-backed features would fail because it was not talking to the production Supabase project properly.
That one stayed with me because there is something reassuring about seeing a successful build, especially after you have fixed several errors. You want to believe the green result means the app is ready. It only means that particular process succeeded. I still have to ask what I actually built, which backend it points to and whether the exact archive I am sending has been tested.
How do you decide whether a build is ready now?
I try to go through it the way a user will, starting before the app opens. Did the tester get access? Can they install it? Are they signing into production? Can they find a teacher, start a call, receive one while the phone is locked and come back after the connection changes? If they buy minutes, what happens if they close the payment page or the provider sends the same update twice?
I also check whether I can help when something goes wrong. I need logs that tell me more than “it failed.” The admin side has to show enough state for me to investigate without guessing. There has to be a safe way to stop new activity if a feature is causing trouble. That part became important because once testers are involved, every problem is no longer happening conveniently in front of my development tools.
What is most of the work near the end actually like?
A lot of it is not interesting to look at. I was checking database policies, retry behaviour, privacy wording, expiration rules, release settings and what the user sees when there is no data yet. I wrote a manual test checklist that became much longer than I expected because every fix reminded me of another path that could break.
It is easy to underestimate this work because you cannot put it in a nice product screenshot. Still, that is where I was deciding what should happen if a webhook arrives twice, if a call fails before it really starts or if somebody leaves the page halfway through a payment. Those situations are ordinary in production even though they feel like edge cases while you are building.
If somebody is shipping their first product, what would you tell them?
I would tell them to test the boring route much earlier. Do not wait until launch week to install the app through the same channel your users will use. Build against production before the final archive. Give it to someone and resist the urge to explain what they should tap. Also think about the message you would receive when something fails, because “it is not working” may be all the information you get.
I still care about polish, and I spent real time going back and forth over the icon, splash logo and font. I just do not confuse that with the rest of finishing anymore. A product is ready when another person can get through the important parts and I can take responsibility for what happens when they cannot.