<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Akin Ajobo</title>
    <description>The latest articles on DEV Community by Akin Ajobo (@mistakili).</description>
    <link>https://dev.to/mistakili</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4007662%2Fbbdd2e8c-3b1b-49d4-81e9-65cbc1e1503e.jpg</url>
      <title>DEV Community: Akin Ajobo</title>
      <link>https://dev.to/mistakili</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mistakili"/>
    <language>en</language>
    <item>
      <title>The live realtor model passed. The goodbye failed. published: true</title>
      <dc:creator>Akin Ajobo</dc:creator>
      <pubDate>Tue, 06 Oct 2026 10:03:01 +0000</pubDate>
      <link>https://dev.to/mistakili/the-live-realtor-model-passed-the-goodbye-failedpublished-true-3jjp</link>
      <guid>https://dev.to/mistakili/the-live-realtor-model-passed-the-goodbye-failedpublished-true-3jjp</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/kaggle-2026-09-23"&gt;Kaggle Benchmarking Challenge&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Benchmarked
&lt;/h2&gt;

&lt;p&gt;I run a voice CRM for a realtor. The agent is supposed to take a lead over the phone: hear a fact, save it, ask for the one missing detail, and stay on the line until the realtor says that is all.&lt;/p&gt;

&lt;p&gt;In production it kept missing that script. It went silent after saving a field. A phone number made it skip the email and jump toward the close. It asked again for a name or a budget it had just been given. It stored "call him Friday" and never said Friday. It saved and hung up before asking whether that was everything.&lt;/p&gt;

&lt;p&gt;I turned those five failures into five Kaggle tasks. The tools return only the fields they saved. They do not hand the model its next sentence. A short spoken reply is in the prompt, and it is not what the score measures. A task scores 1 only when every check passes.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;speak-after-update.&lt;/strong&gt; After a new lead, ask one missing question out loud.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;email-after-phone.&lt;/strong&gt; A phone number must not skip the email question.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;no-repeat-known.&lt;/strong&gt; Do not ask again for a field the realtor already gave.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;confirm-before-end.&lt;/strong&gt; Ask if that is all before saving, and end the call only after yes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;speak-the-reminder.&lt;/strong&gt; Say the follow-up day. Storing it is not enough.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Models Tested
&lt;/h2&gt;

&lt;p&gt;The model on the live phone line is Gemma 4 26B (&lt;code&gt;gemma-4-26b-a4b-it&lt;/code&gt;). That was the one I most wanted to see fail in the same way the calls fail.&lt;/p&gt;

&lt;p&gt;I also ran Gemini 3.7 Flash, which Kaggle ran when each task was created, plus Gemini 3 Flash, Claude Sonnet 5, and GPT-5.4 mini. Same prompt, same tools, same leads: Ada in Akobo, Tunde's land in Ibadan, Ngozi's duplex in Lekki, and a Friday reminder for Chidi.&lt;/p&gt;

&lt;h2&gt;
  
  
  Findings
&lt;/h2&gt;

&lt;p&gt;Gemma passed all five. So did Gemini 3.7 Flash. Gemini 3 Flash and Claude passed four. GPT-5.4 mini passed two.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Task&lt;/th&gt;
&lt;th&gt;Gemma 4 26B&lt;/th&gt;
&lt;th&gt;Gemini 3.7 Flash&lt;/th&gt;
&lt;th&gt;Gemini 3 Flash&lt;/th&gt;
&lt;th&gt;Claude Sonnet 5&lt;/th&gt;
&lt;th&gt;GPT-5.4 mini&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;speak-after-update&lt;/td&gt;
&lt;td&gt;Pass&lt;/td&gt;
&lt;td&gt;Pass&lt;/td&gt;
&lt;td&gt;Pass&lt;/td&gt;
&lt;td&gt;Pass&lt;/td&gt;
&lt;td&gt;Pass&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;email-after-phone&lt;/td&gt;
&lt;td&gt;Pass&lt;/td&gt;
&lt;td&gt;Pass&lt;/td&gt;
&lt;td&gt;Pass&lt;/td&gt;
&lt;td&gt;Pass&lt;/td&gt;
&lt;td&gt;Fail&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;no-repeat-known&lt;/td&gt;
&lt;td&gt;Pass&lt;/td&gt;
&lt;td&gt;Pass&lt;/td&gt;
&lt;td&gt;Pass&lt;/td&gt;
&lt;td&gt;Pass&lt;/td&gt;
&lt;td&gt;Fail&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;confirm-before-end&lt;/td&gt;
&lt;td&gt;Pass&lt;/td&gt;
&lt;td&gt;Pass&lt;/td&gt;
&lt;td&gt;Fail&lt;/td&gt;
&lt;td&gt;Fail&lt;/td&gt;
&lt;td&gt;Fail&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;speak-the-reminder&lt;/td&gt;
&lt;td&gt;Pass&lt;/td&gt;
&lt;td&gt;Pass&lt;/td&gt;
&lt;td&gt;Pass&lt;/td&gt;
&lt;td&gt;Pass&lt;/td&gt;
&lt;td&gt;Pass&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;On the opening lead, Gemma saved Ada Okafor, the three-bedroom flat, and Akobo, then said, "Got it. Do you have her phone number?" Given the phone number, it asked, "Thanks. Do you have an email for her?" Told she had no email, it moved on: "No problem. What is her budget?" For the reminder it said, "I'll remind you to call Chidi this Friday." Before ending Ngozi's call it asked, "Is that all?" and closed with, "The lead is saved. Have a great day!"&lt;/p&gt;

&lt;p&gt;That is the opposite of what the live calls do. The intake policy is in this model. The production misses are in the voice loop around it: silence after a tool call, a phone number that skips a step, a reminder that is stored and never spoken.&lt;/p&gt;

&lt;p&gt;GPT-5.4 mini is the model that actually skipped the script. After Ada's phone number it asked, "What's her budget?" Told she had no email, it stored the email as "No email" and jumped to, "Is that all for Ada Okafor?" For Tunde it first asked, "What's his email?" Then, when the realtor said, "What else should I tell you?" it saved the lead, ended the call, and said "Saved." Email and a follow-up were still missing.&lt;/p&gt;

&lt;p&gt;Claude and Gemini 3 Flash failed only the last sentence of the closing task. Both asked whether that was all, both saved, and both ended the call. Claude said, "Saved Ngozi's lead successfully!" and then, after the hang-up, "Thanks for calling, have a great day!" Gemini said the details were saved, ended the call, and finished with "Omnipresent Voice AI CRM lead capture complete." The check looks at the final sentence. A correct save followed by a polite goodbye scores zero. GPT did say "Saved." and still failed, because it never called &lt;code&gt;end_call&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Two measurements are worth doing next. One is the spoken audio path, because a perfect text transcript did not reproduce the live failures. The other is a closing check that accepts "saved" anywhere in the closing turn, so a second goodbye sentence is not an automatic miss.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Benchmark
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.kaggle.com/benchmarks/mistakilihive/realtor-intake" rel="noopener noreferrer"&gt;Realtor intake on Kaggle&lt;/a&gt;&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>kagglechallenge</category>
      <category>ai</category>
      <category>machinelearning</category>
    </item>
    <item>
      <title>OVAM AI: A Voice Assistant for My Wife A Realtor</title>
      <dc:creator>Akin Ajobo</dc:creator>
      <pubDate>Mon, 05 Oct 2026 01:04:15 +0000</pubDate>
      <link>https://dev.to/mistakili/ovam-ai-a-voice-assistant-for-realtors-10gb</link>
      <guid>https://dev.to/mistakili/ovam-ai-a-voice-assistant-for-realtors-10gb</guid>
      <description>&lt;p&gt;What I Built&lt;br&gt;
My wife runs a real-estate business, and one of the annoying parts of her workflow happens after she talks to a prospect.&lt;br&gt;
She might finish a conversation knowing the person's name, phone number, preferred location, budget, property type, timeline, objections, and payment plans — but then she has to remember everything and manually turn that conversation into a CRM record.&lt;br&gt;
So I built OVAM AI, a voice-first real-estate assistant for her.&lt;br&gt;
Instead of filling out a form, she can simply tell it what happened:&lt;br&gt;
“I just spoke to Sarah. She wants a residential plot around Akobo, Ibadan. Her budget is about 10 million naira and she wants to buy within two months. Her phone number is 08012345678.”&lt;/p&gt;

&lt;p&gt;OVAM AI turns that into a structured lead.&lt;br&gt;
She can then correct or add information naturally:&lt;br&gt;
“Actually, her name is Kemi, not Sarah.”&lt;/p&gt;

&lt;p&gt;or:&lt;br&gt;
“She can pay 3 million down and the rest over 12 months.”&lt;/p&gt;

&lt;p&gt;The assistant updates the existing lead instead of creating a mess of duplicate information.&lt;br&gt;
It also doesn't save automatically.&lt;br&gt;
Once it has enough information, it summarizes what it understood and asks for confirmation. “No” leaves the lead unsaved. “Yes, save it” creates the record.&lt;br&gt;
The goal is simple:&lt;br&gt;
Tell me what happened. I'll handle the CRM.&lt;/p&gt;

&lt;p&gt;Demo&lt;br&gt;
Live demo:&lt;br&gt;
&lt;a href="https://ovam-ai-fawn.vercel.app" rel="noopener noreferrer"&gt;https://ovam-ai-fawn.vercel.app&lt;/a&gt;&lt;br&gt;
Tap the microphone and talk to OVAM AI like you just finished speaking with a real-estate prospect.&lt;br&gt;
You can also use the typed-note mode if you don't want to use your microphone.&lt;/p&gt;

&lt;p&gt;Code&lt;br&gt;
GitHub:&lt;br&gt;
&lt;a href="https://github.com/Mistakili/OVAM-ai" rel="noopener noreferrer"&gt;https://github.com/Mistakili/OVAM-ai&lt;/a&gt;&lt;br&gt;
The repository contains the React frontend, API routes, Gemma integration, AssemblyAI voice integration, CRM tools, and setup instructions.&lt;/p&gt;

&lt;p&gt;How I Built It&lt;br&gt;
The core intelligence behind OVAM AI is Gemma.&lt;br&gt;
I use Gemma to turn natural language into structured CRM information rather than forcing the realtor through a traditional form.&lt;br&gt;
The application is built with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Gemma for lead understanding and structured CRM reasoning&lt;/li&gt;
&lt;li&gt;AssemblyAI Voice Agent API for real-time speech, transcription, turn detection, and voice responses&lt;/li&gt;
&lt;li&gt;React + Vite for the frontend&lt;/li&gt;
&lt;li&gt;Vercel serverless functions for the backend/API layer&lt;/li&gt;
&lt;li&gt;Tool calling with update_lead and save_lead to modify the CRM state&lt;/li&gt;
&lt;li&gt;Browser storage for the current lead records
The important part is the interaction between the model and the application.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Gemma isn't just generating a conversational response. It can update structured lead state through tools. The system is designed to understand facts in whatever order the realtor provides them, preserve information that is still valid when something is corrected, avoid inventing missing information, and ask only for useful missing details.&lt;/p&gt;

&lt;p&gt;Why Does Open Innovation Matter?&lt;br&gt;
For this project, I didn't want the AI model to be a black box that my entire product depended on.&lt;br&gt;
Gemma gives me an open-weight model that I can build around, experiment with, and potentially deploy differently as the product evolves.&lt;br&gt;
That matters because OVAM AI is intended for a real business, not just a demo.&lt;br&gt;
Today, I can use Gemma through an API. Tomorrow, I can experiment with different Gemma variants, deployment strategies, structured outputs, or private inference without having to redesign the entire product around a proprietary model.&lt;br&gt;
More importantly, open innovation makes experimentation accessible.&lt;br&gt;
I'm an independent developer building this for a small real-estate business. I don't have a giant infrastructure team or an enormous AI budget.&lt;br&gt;
Being able to take an open-weight model and turn it into a useful product for someone close to me is exactly the kind of thing I think open innovation should enable.&lt;/p&gt;

&lt;p&gt;Prize Categories&lt;br&gt;
Best Use of Gemma&lt;br&gt;
OVAM AI is built around Gemma as its lead-understanding layer.&lt;br&gt;
The model takes messy, natural human conversation and turns it into structured CRM state that the application can act on through tools.&lt;br&gt;
That is the core AI behavior of the product.&lt;/p&gt;

&lt;p&gt;Why I Built This&lt;br&gt;
I didn't build another CRM because the world needs another CRM.&lt;br&gt;
I built this because my wife shouldn't have to finish a conversation with a potential buyer and immediately become a data-entry clerk.&lt;br&gt;
She should be able to say:&lt;br&gt;
“Here's what happened.”&lt;br&gt;
And let the software take care of the rest.&lt;br&gt;
Talk → Understand → Structure → Confirm → Save.&lt;br&gt;
That's OVAM AI.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>weekendchallenge</category>
      <category>hf26challenge</category>
    </item>
    <item>
      <title>The Production Bug No Capacitor Push Notification Tutorial Prepared Me For</title>
      <dc:creator>Akin Ajobo</dc:creator>
      <pubDate>Mon, 29 Jun 2026 08:39:58 +0000</pubDate>
      <link>https://dev.to/mistakili/the-production-bug-no-capacitor-push-notification-tutorial-prepared-me-for-1386</link>
      <guid>https://dev.to/mistakili/the-production-bug-no-capacitor-push-notification-tutorial-prepared-me-for-1386</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Everything worked perfectly... until I uploaded my app to TestFlight.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Every push notification tutorial I read covered the same things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Register for push notifications.&lt;/li&gt;
&lt;li&gt;Request permission.&lt;/li&gt;
&lt;li&gt;Get the device token.&lt;/li&gt;
&lt;li&gt;Send your first notification.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And to be fair, all of those tutorials worked.&lt;/p&gt;

&lt;p&gt;Until they didn't.&lt;/p&gt;

&lt;p&gt;The moment I uploaded my Capacitor app to TestFlight, the entire push notification flow quietly fell apart.&lt;/p&gt;

&lt;p&gt;No obvious errors.&lt;/p&gt;

&lt;p&gt;No compiler issues.&lt;/p&gt;

&lt;p&gt;No helpful stack traces.&lt;/p&gt;

&lt;p&gt;Just... silence.&lt;/p&gt;

&lt;p&gt;Over the next two days, I went down one rabbit hole after another before discovering that the problem wasn't a single bug—it was a collection of production-only assumptions that none of the tutorials mentioned.&lt;/p&gt;

&lt;p&gt;This article is the postmortem I wish I'd found before I started.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Setup
&lt;/h2&gt;

&lt;p&gt;The stack was pretty standard.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Capacitor&lt;/li&gt;
&lt;li&gt;TypeScript&lt;/li&gt;
&lt;li&gt;Node.js backend&lt;/li&gt;
&lt;li&gt;APNs&lt;/li&gt;
&lt;li&gt;iOS&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Everything behaved exactly as expected during development.&lt;/p&gt;

&lt;p&gt;Push permissions appeared.&lt;/p&gt;

&lt;p&gt;The device registered.&lt;/p&gt;

&lt;p&gt;Tokens looked correct.&lt;/p&gt;

&lt;p&gt;Notifications arrived instantly.&lt;/p&gt;

&lt;p&gt;I genuinely thought this part of the app was finished.&lt;/p&gt;

&lt;p&gt;Then I created a TestFlight build.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Moment Everything Broke
&lt;/h2&gt;

&lt;p&gt;I installed the TestFlight build.&lt;/p&gt;

&lt;p&gt;Opened the app.&lt;/p&gt;

&lt;p&gt;Tapped my "Send Test Notification" button.&lt;/p&gt;

&lt;p&gt;Nothing happened.&lt;/p&gt;

&lt;p&gt;No notification.&lt;/p&gt;

&lt;p&gt;No crash.&lt;/p&gt;

&lt;p&gt;No useful error message.&lt;/p&gt;

&lt;p&gt;At that point I made the same assumption I think most developers make:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"I must have misconfigured APNs."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That assumption cost me hours.&lt;/p&gt;




&lt;h2&gt;
  
  
  My Debugging Timeline
&lt;/h2&gt;

&lt;p&gt;Here's roughly how the next two days went.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;09:30&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Verified notification permissions.&lt;/p&gt;

&lt;p&gt;✅ Correct.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;10:00&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Verified APNs key.&lt;/p&gt;

&lt;p&gt;✅ Correct.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;11:15&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Regenerated provisioning profiles.&lt;/p&gt;

&lt;p&gt;No change.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;12:40&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Deleted and recreated the app on my phone.&lt;/p&gt;

&lt;p&gt;Still nothing.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;14:00&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Started blaming Firebase.&lt;/p&gt;

&lt;p&gt;Wrong direction.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;15:30&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Started questioning Capacitor.&lt;/p&gt;

&lt;p&gt;Also wrong.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;17:00&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Realized something important.&lt;/p&gt;

&lt;p&gt;TestFlight isn't your development environment.&lt;/p&gt;

&lt;p&gt;It's production.&lt;/p&gt;

&lt;p&gt;That changed everything.&lt;/p&gt;




&lt;h2&gt;
  
  
  Production Behaves Differently
&lt;/h2&gt;

&lt;p&gt;This was the biggest lesson.&lt;/p&gt;

&lt;p&gt;When you install from TestFlight, Apple expects production infrastructure.&lt;/p&gt;

&lt;p&gt;Not development.&lt;/p&gt;

&lt;p&gt;Not sandbox.&lt;/p&gt;

&lt;p&gt;Production.&lt;/p&gt;

&lt;p&gt;That sounds obvious now.&lt;/p&gt;

&lt;p&gt;It wasn't obvious while debugging.&lt;/p&gt;

&lt;p&gt;Once I started treating the build as a production deployment instead of "a beta version," the entire debugging strategy changed.&lt;/p&gt;




&lt;h1&gt;
  
  
  Then Another Problem Appeared
&lt;/h1&gt;

&lt;p&gt;Even after fixing the APNs environment...&lt;/p&gt;

&lt;p&gt;My backend occasionally crashed.&lt;/p&gt;

&lt;p&gt;The error looked something like this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Error: read ECONNRESET

Error: write EPIPE
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At first I thought Apple was rejecting my requests.&lt;/p&gt;

&lt;p&gt;It wasn't.&lt;/p&gt;

&lt;p&gt;Apple was simply closing idle HTTP/2 sessions.&lt;/p&gt;

&lt;p&gt;That's normal.&lt;/p&gt;

&lt;p&gt;My server wasn't treating it as normal.&lt;/p&gt;




&lt;h2&gt;
  
  
  Production Software Needs Production Recovery
&lt;/h2&gt;

&lt;p&gt;Once I stopped treating HTTP/2 disconnects as exceptional failures, the fix became straightforward.&lt;/p&gt;

&lt;p&gt;Instead of allowing dropped connections to crash the process, the backend now:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Detects disconnects&lt;/li&gt;
&lt;li&gt;Tears down the session cleanly&lt;/li&gt;
&lt;li&gt;Reconnects automatically&lt;/li&gt;
&lt;li&gt;Retries safely where appropriate&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those few changes completely changed the reliability of the notification service.&lt;/p&gt;




&lt;h1&gt;
  
  
  The Three Assumptions That Cost Me The Most Time
&lt;/h1&gt;

&lt;h2&gt;
  
  
  1. "If it works locally, production will work."
&lt;/h2&gt;

&lt;p&gt;Wrong.&lt;/p&gt;

&lt;p&gt;Development environments hide a surprising amount of complexity.&lt;/p&gt;

&lt;p&gt;Production exposes all of it.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. "It has to be my certificates."
&lt;/h2&gt;

&lt;p&gt;Certificates were fine.&lt;/p&gt;

&lt;p&gt;Provisioning profiles were fine.&lt;/p&gt;

&lt;p&gt;The issue was the production environment itself.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. "ECONNRESET means something is broken."
&lt;/h2&gt;

&lt;p&gt;Not necessarily.&lt;/p&gt;

&lt;p&gt;HTTP/2 sessions close.&lt;/p&gt;

&lt;p&gt;Networks reconnect.&lt;/p&gt;

&lt;p&gt;Production software should expect that.&lt;/p&gt;




&lt;h1&gt;
  
  
  What I Wish Someone Had Told Me
&lt;/h1&gt;

&lt;p&gt;Before shipping a Capacitor app with push notifications:&lt;/p&gt;

&lt;p&gt;✅ Test on a physical device early.&lt;/p&gt;

&lt;p&gt;✅ Treat TestFlight as production.&lt;/p&gt;

&lt;p&gt;✅ Don't hardcode APNs environments.&lt;/p&gt;

&lt;p&gt;✅ Log every push response.&lt;/p&gt;

&lt;p&gt;✅ Build reconnect logic into your APNs client.&lt;/p&gt;

&lt;p&gt;✅ Expect HTTP/2 sessions to terminate.&lt;/p&gt;

&lt;p&gt;✅ Document every native iOS change.&lt;/p&gt;

&lt;p&gt;These sound simple.&lt;/p&gt;

&lt;p&gt;They aren't obvious when you're learning from getting-started tutorials.&lt;/p&gt;




&lt;h1&gt;
  
  
  Why I Built an Open-Source Reference
&lt;/h1&gt;

&lt;p&gt;While debugging this, I noticed something.&lt;/p&gt;

&lt;p&gt;Most tutorials stop once the first notification appears.&lt;/p&gt;

&lt;p&gt;That's the easy part.&lt;/p&gt;

&lt;p&gt;Almost nobody talks about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;TestFlight failures&lt;/li&gt;
&lt;li&gt;Production APNs routing&lt;/li&gt;
&lt;li&gt;Backend reconnect logic&lt;/li&gt;
&lt;li&gt;Native configuration drift&lt;/li&gt;
&lt;li&gt;Production troubleshooting&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those are exactly the problems that consume entire weekends.&lt;/p&gt;

&lt;p&gt;So instead of letting those lessons disappear into Git commits, I started building an open-source project called &lt;strong&gt;Production Hybrid Reference&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The goal isn't another starter template.&lt;/p&gt;

&lt;p&gt;It's a collection of production-ready recipes, troubleshooting guides, and engineering postmortems based on real applications—not hypothetical examples.&lt;/p&gt;

&lt;p&gt;The first module focuses entirely on production push notifications and includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A runnable Node.js APNs server&lt;/li&gt;
&lt;li&gt;A Capacitor mobile example&lt;/li&gt;
&lt;li&gt;Troubleshooting guides&lt;/li&gt;
&lt;li&gt;A production postmortem&lt;/li&gt;
&lt;li&gt;Practical engineering principles&lt;/li&gt;
&lt;li&gt;Copy-paste reference code&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If it saves even one developer from spending two days chasing the wrong problem, it'll have been worth publishing.&lt;/p&gt;




&lt;h1&gt;
  
  
  What's Next
&lt;/h1&gt;

&lt;p&gt;Push notifications are just Module Zero.&lt;/p&gt;

&lt;p&gt;The next production battle scars I'll be documenting include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;RevenueCat synchronization&lt;/li&gt;
&lt;li&gt;AppDelegate configuration drift&lt;/li&gt;
&lt;li&gt;Background tasks&lt;/li&gt;
&lt;li&gt;Production logging&lt;/li&gt;
&lt;li&gt;Apple review preparation&lt;/li&gt;
&lt;li&gt;Hybrid app architecture&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each module follows the same philosophy.&lt;/p&gt;

&lt;p&gt;Real bug.&lt;/p&gt;

&lt;p&gt;Real investigation.&lt;/p&gt;

&lt;p&gt;Real fix.&lt;/p&gt;




&lt;h1&gt;
  
  
  Final Thoughts
&lt;/h1&gt;

&lt;p&gt;The biggest lesson wasn't technical.&lt;/p&gt;

&lt;p&gt;It was realizing that there's a huge gap between &lt;strong&gt;"it works"&lt;/strong&gt; and &lt;strong&gt;"it survives production."&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Tutorials are great at helping you build features.&lt;/p&gt;

&lt;p&gt;Production teaches you how software actually behaves.&lt;/p&gt;

&lt;p&gt;If you've run into a production issue with Capacitor that took hours—or days—to solve, I'd genuinely love to hear about it.&lt;/p&gt;

&lt;p&gt;Leave a comment below with your biggest production battle scar.&lt;/p&gt;

&lt;p&gt;If enough people are fighting the same problem, it'll become the next module in the Production Hybrid Reference.&lt;/p&gt;

&lt;p&gt;Let's save each other a few weekends.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;About this series:&lt;br&gt;
This article is the first in a series documenting real production issues I encountered while shipping a commercial Capacitor application. Every solution is based on an actual debugging session—not a contrived tutorial example.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
    </item>
  </channel>
</rss>
