Building a Mobile App With Push Notifications People Actually Open: Cost, Timeline and Failure Modes
Adding working push to an app you already have runs $15,000 to $60,000 over 3 to 6 weeks. A first release that includes push as a core loop runs $50,000 to $130,000 over 10 to 16 weeks. A full platform with segmentation, scheduling, multi-tenant sending and an ops console runs $150,000 to $350,000 over 6 to 12 months. The send itself is a two day job. Everything after "did it arrive and did anyone care" is where the money goes.
The send is easy. The operating system is the product risk.
Firebase Cloud Messaging accepts a token and a payload. Apple Push Notification service accepts a JWT and a device token. A Hello World notification lands on a test phone the same afternoon. That is what a two week quote covers. What it does not cover is that push is the only feature where the OS, not you, decides whether your work is seen, and where a bad month permanently lowers your ceiling.
A retail client ran a 40,000 device campaign at 9am. The dashboard said 40,000 accepted. Support said something different: several hundred users got the same notification four times inside ninety seconds. A worker fanned out sends, timed out at the HTTP layer after FCM had already accepted the batch, and the queue retried it. FCM did its job twice. Nothing in the code checked whether this notification for this user had already been dispatched.
The same campaign woke 6,000 devices belonging to users who had uninstalled months earlier, because nobody consumed the unregistration responses. Push fails by being ignored, and both platforms are watching. On iOS, users who swipe your notifications away without opening train the system to summarise or bury you. On Android, a channel a user mutes stays muted and your app cannot unmute it in code. There is no subject line that wins that audience back.
Token churn is database rot nobody quotes for
A device token is a lease, not an identity. It rotates on reinstall, on some OS upgrades, on restore from backup to a new phone, when a user clears app data on Android, and when FCM decides to rotate it. Across our deployments, roughly 20 to 35 percent of an active token table goes stale in a year on a normal consumer app, faster on apps with churn.
Model the token table as a many to many between users and devices, never a column on the user row. One user has an iPhone, an iPad and an old Android. One physical device may host two accounts if you allow account switching, which means logout must delete the token binding or the departing employee gets the new one's notifications. Register the token on every app foreground, not just first launch, and upsert on the token value.
Then consume the failure signals. FCM v1 returns UNREGISTERED and INVALID_ARGUMENT per message. APNs returns 410 Gone with a timestamp, and 400 BadDeviceToken. Each one is an instruction to delete the row. Log it and move on and your send volume inflates, your open rate denominator inflates, and every metric you steer by becomes fiction. APNs tokens are also environment scoped: a token minted against the sandbox gateway returns BadDeviceToken on production, which is why "it worked in TestFlight and broke on the App Store" is a launch day page. Store the environment alongside the token.
The permission prompt is a one shot asset
On iOS you get one system prompt per install. If the user taps Don't Allow, you cannot ask again in code. Your only move left is deep linking into iOS Settings, and almost nobody follows. Android 13 and above applies the same constraint through the POST_NOTIFICATIONS runtime permission, and two denials trigger the permanent don't ask again state.
In our delivery data, apps that fire the system prompt on first launch land opt in rates of 35 to 45 percent. Apps that show a custom pre permission screen explaining the specific value, and only call the system API for users who tap yes on the soft ask, land at 60 to 75 percent. The soft ask is retryable and the hard ask is not, so you spend the OS prompt only on users who already said yes once. That moves your reachable audience by roughly 30 points of your install base for about two days of engineering.
The second decision is Android notification channels. Since Android 8 every notification belongs to a channel the user controls independently. Dump order updates, marketing and chat into one default channel and the user who is sick of promos mutes all three, taking transactional delivery with them. Define channels up front and never merge them, because you cannot recategorise an existing channel after ship. You can only create a new one and lose everyone already opted into the old one.
Idempotency, fan out and the duplicate storm
The retail bug is structural. A push pipeline has at least three retrying layers: your job queue, your HTTP client, and the provider. Retries are correct behaviour, so duplicates are the default outcome unless you design against them.
Use a deterministic idempotency key per intended notification, typically campaign id plus user id plus a time bucket, written to a store with a unique constraint before the send and checked on every attempt. FCM supports collapse keys and APNs supports apns-collapse-id, which tell the provider to replace an undelivered notification rather than stack it, which is what a score update or a delivery ETA needs. Use both: the collapse id protects the user's lock screen, the idempotency key protects your pipeline.
Respect the rate reality. APNs over HTTP/2 wants one long lived multiplexed connection reused across sends, not a fresh TLS handshake per notification, and teams that get this wrong find out at 50,000 devices rather than 50. FCM's HTTP v1 API removed the old 1,000 device multicast, so a million device campaign is a million calls and the architecture must be a worker pool with backpressure, not a for loop. Handle 429 and 503 by honouring the Retry-After header.
Silent pushes are not a scheduler. content-available on iOS is a hint, budgeted against battery, Low Power Mode and how often the user opens your app, and Apple documents that undelivered ones are not queued beyond the most recent. If a feature depends on a silent push firing at a set time, that feature is broken and you find out when a customer's inventory sync is six hours stale.
Making them open it
A campaign sent at "9am" server time lands at 4am for a chunk of a US and Europe audience, and a 4am notification gets your channel muted rather than ignored. That means a per user timezone stored and refreshed on foreground because people travel, a scheduler that fans out per timezone bucket rather than firing once globally, and a quiet hours window enforced server side, because the client cannot be trusted to be awake.
Deep links have to survive cold start. The expensive way to waste a notification is to deliver it, win the tap, and dump the user on your home screen because routing only works when the app is warm. On iOS a tap on a cold app arrives through the launch options path, on Android through intent extras, and both need to reconstruct a back stack so the hardware back button does not exit your app. Test it with the app force quit, not backgrounded, because those are different code paths.
Frequency capping is the strongest long term lever we see on open rate: a hard cap of two to three non transactional notifications per user per week, enforced inside the sending service so no campaign owner can override it. Transactional and marketing need separate channels and separate caps, so a promo budget can never eat the delivery alert the user actually wanted.
Then instrument honestly. iOS gives you a delivery signal only through a Notification Service Extension that fires on receipt, or through aggregate reporting. Without that extension your open rate is opens over accepted, which flatters you by every stale token you never cleaned, and the extension is a line item.
What this costs and how long it takes
These are Digital Heroes bands from 2,000 plus delivered projects, not industry averages.
Push added to an existing app, both platforms, with token lifecycle, idempotent sending, channels, soft ask, deep linking and basic analytics: $15,000 to $60,000, typically 3 to 6 weeks. The spread is about what your app already has. No clean device model and no job queue means we build infrastructure before we build push.
Push as part of a first release, where the notification is the core loop: $50,000 to $130,000 over 10 to 16 weeks, including the scheduling service, timezone handling, preference centre and an internal console for whoever presses send.
A full platform with multi tenant sending, segmentation, campaign builder, A/B testing, per tenant rate isolation and an audit trail: $150,000 to $350,000 over 6 to 12 months.
What drives this capability's number up, in order of impact. Rich media with Notification Service and Content Extensions on iOS, because that is a second binary target with its own memory limit and signing. Per user timezone scheduling with quiet hours, which turns a cron into a distributed scheduler. Multi device and account switching. Regulated content, where a lock screen preview is a disclosure, so the payload carries an opaque id and the extension fetches the body only after unlock. And any requirement for guaranteed or auditable delivery, which push cannot provide and which forces an SMS or email fallback with its own dedupe logic.
When you should not build this
If you send fewer than roughly 100,000 notifications a month, your segmentation is "all users" or two or three simple lists, and push does not need to trigger custom in app state, do not build a sending platform. Use OneSignal, Firebase Cloud Messaging with Firebase In App Messaging, Braze or Customer.io depending on budget, and spend engineering only on the client integration, the soft ask and the deep links. You get the scheduler, timezone logic, frequency caps, delivery dashboard and compliance surface for less than one month of a developer's time.
That is the right call for a marketing led product, a content app, an ecommerce app, or any team where the person pressing send is not an engineer. For most companies asking this question, a managed sender plus two weeks of client work is the answer, and we say so on calls.
Build custom when one of these holds. Your notification content is derived from data you cannot export to a third party for legal or contractual reasons. Your sends are triggered by real time state in your own system and the round trip to a marketing tool adds latency you cannot afford. You are the platform and your customers send to their users, which makes the sender your product. Or your volume has pushed per message pricing past the fully loaded cost of running it yourself, which starts mattering in the millions of messages per month, not the thousands.
How to brief and vet a developer
Brief with numbers. Give them expected device count at 12 months, notification types split into transactional versus marketing, whether any single event fans out to more than 10,000 devices at once, whether users can have multiple devices or switch accounts, whether content is sensitive on a lock screen, and who presses send. Those six answers determine the architecture. Without them any quote is a guess.
"What do you do when APNs returns a 410?" If they do not say delete the token and mention the timestamp, they have never run a real token table.
"How do you ask for notification permission?" No soft ask before the system prompt means 30 points of your audience gone in the first session.
"How do you stop a duplicate send when the queue retries?" You want idempotency key with a unique constraint, and collapse id as a separate concept. "We use a reliable queue" is not an answer, because the queue's reliability is what causes duplicates.
"How do you schedule a 9am local notification across 14 timezones?" A single cron plus client side filtering means they have not shipped this.
"What is the difference between a notification your app builds and one iOS builds from the payload?" Anyone who has shipped rich push will name the Notification Service Extension, mutable-content, and the extension's short execution window and memory ceiling.
"Where do you store the token and what is its relationship to the user?" You want a devices table. A column on the users table is a stop signal.
"What is your delivered number measured against?" Someone who has shipped explains that accepted by APNs is not delivered, that true delivery on iOS needs an extension, and that most dashboards report accepted. Someone who has not will read you the dashboard number.
The evidence behind this guide
Independent findings on why this investment pays off. Every link goes to the primary source.
- The median annual wage for U.S. software developers was $133,080 in May 2024, and employment is projected to grow 15% from 2024 to 2034 - a core input to any in-house build-vs-buy TCO model. Source: U.S. Bureau of Labor Statistics (2024) →
- Standish's 2015 CHAOS research found roughly a third of software projects (about 36% by the Modern definition) fully succeed on time, on budget, and on scope, with top success drivers including executive support, user involvement, and clear requirements/business objectives. Source: Standish Group (CHAOS Report) (2015) →
- Acquiring a new customer is five to 25 times more expensive than retaining an existing one, and research by Frederick Reichheld of Bain & Company found that increasing customer retention rates by 5% increases profits by 25% to 95% - underscoring the ROI of support that keeps customers. Source: Harvard Business Review / Bain & Company (2014) →
- In Gartner's 2025 AI in Finance Survey of 183 CFOs and senior finance leaders (fielded May-June 2025), 59% reported using AI in their finance function, with accounts payable process automation adopted by 37% of respondents (the second-highest single use case, behind knowledge management at 49%). Source: Gartner (2025) →
Rohan advises mid-market and enterprise teams on ERP, CRM and custom software, and has led delivery on dozens of business-software builds.
Writes for Digital Heroes, shipping business software for 2,000+ brands across 55+ countries since 2017.