Mobile Push Notification Preference Spec

Mobile Push Notification Preference Spec
Spec Coding Editorial Team · Spec-first engineering notes

Push preferences look like a settings screen and turn out to be a distributed system. I have watched teams ship a single "notifications" toggle, then spend the next two years untangling why a security alert got silenced along with a marketing ping. This is the spec I wish those teams had written on day one.

Published on 2026-03-09 · 6 min read · Updated May 6, 2026 · Author: Spec Coding Editorial Team · Review policy: Editorial Policy

Review Note

Reviewed May 6, 2026. This focused reference is now promoted as a search-indexable companion to the API Contracts Hub. It includes concrete review artifacts, failure modes, and next-step links for readers applying the topic in practice.

Category Model Beats Channel Model, and It Is Not Close

The first real decision is how you group notifications. Teams keep modeling preferences by channel — sound, badge, lock screen — because that is what the OS settings app surfaces. Do not do that. Channel is a rendering concern. Category is a consent concern, and consent is the thing your product actually needs to reason about.

Write the category taxonomy as a closed enum. I use four buckets: transactional (order shipped, payment received), security (new device sign-in, 2FA code), social (likes, comments, mentions), and marketing (announcements, promos, re-engagement). If a proposed notification does not fit, the spec forces the question: is this really a new category, or are you sneaking marketing into transactional so more users see it?

Picture a photo-sharing app. Someone likes my post — social. Someone signs in from a new country — security. A Black Friday promo — marketing. The order confirmation for a print I bought — transactional. Same user, same device, four categories, four independent opt-in states. Default state differs per category: security defaults on and is very hard to turn off, transactional defaults on, social defaults on but is one tap to mute per sub-type, and marketing must default off wherever GDPR applies with opt-in explicit, logged, and revocable. One global push toggle makes all of that impossible.

The OS Permission Dance Must Be Written Down

On iOS, I want provisional authorization (UNAuthorizationOptionProvisional) for anything that can deliver quietly to Notification Center without interrupting the user — it lets us demonstrate value before the hard prompt. On Android 13 and above, POST_NOTIFICATIONS is a runtime permission, and the spec must say when we ask.

My rule: never prompt on first launch. Prompt at the first moment a notification would create obvious value — after a user's first photo, or right after enabling two-factor auth. Log a permission_prompt_shown event and never prompt twice. If the user denies, route them to an in-app explanation with a deep link to OS settings, not a repeated native prompt.

Per-Category State Is a Tiny State Machine

Per category, per user, per device, the preference has three states: default, user-on, and user-off. The distinction between default-on and user-on controls what happens when you later change the default. If a category was user-on, a server-side default flip does not touch it. If it was default-on, the new default applies. Without this, every default change silently overwrites real user choices.

Store the transition timestamp and source (in_app_settings, os_settings_sync, unsubscribe_link, admin_override). You will want that audit trail the first time legal asks whether a user actually consented to marketing on 2025-11-03.

Quiet Hours: Store the Timezone, Not the Offset

"9pm to 8am" is meaningless without a timezone, and a timezone is not a UTC offset. If I set quiet hours in New York and fly to Tokyo, do I want 9pm Tokyo or 9pm New York? My answer: quiet hours follow the device's current IANA zone. Store start and end as local wall-clock times plus an IANA zone id refreshed on every app foreground.

The spec must also say what "quiet" means. My default: suppress marketing and social, delay-deliver transactional until the window ends, bypass entirely for security. Every spec needs an explicit rule per category, not a generic "respect quiet hours" line.

Device-Level vs Account-Level: Most Restrictive Wins

A user has an account-level preference and each device has its own OS permission. The conflict rule is "most restrictive wins, and the user can always see why a notification did not arrive." If the account opts out of social but the iPhone permission is on, social is still suppressed. If the account opts in to marketing but Android POST_NOTIFICATIONS is denied, the settings screen shows "blocked at the OS level" rather than a misleading green toggle.

Critical Alerts Need Gatekeepers, Not PM Discretion

iOS critical alerts bypass Do Not Disturb. Android's time-sensitive and full-screen-intent categories do similar things. The spec must name who can flag a notification as critical, and the answer should almost never be the PM who owns the feature. I write it as: only Security can request critical-alert delivery, and only a named list (suspicious_login, account_takeover_blocked, 2fa_code) is eligible. Anything else gets denied at the compose layer, not discovered in review.

Delivery Receipts Lie, and the Spec Should Admit It

APNs and FCM tell you a message was accepted by their servers. They will not reliably tell you it hit the lock screen. Battery-saver mode, OEM notification throttling on Android skins, dwell-time limits — all silently drop or delay messages. State what "delivered" actually measures (accepted by the push provider) and define a separate user_observed event that fires on tap or badge clear. Do not let dashboards promise guarantees the platform cannot back up.

GDPR, TCPA, and the SMS Fallback Trap

Marketing pushes to EU users need a lawful basis, and "they installed the app" is not it. Require explicit opt-in with a timestamped consent record before any marketing push to a device whose last known region is EEA or UK. Transactional and security ride on legitimate interest and contractual necessity — but the spec must say so explicitly so legal can review.

The sneaky one is SMS fallback. When push fails, teams reach for SMS. That crosses into TCPA territory, and push consent does not cover SMS. The spec either forbids SMS fallback for marketing entirely, or requires a separate, documented SMS opt-in with its own audit trail.

Acceptance Criteria

- Given a user in Germany with no prior marketing opt-in
  When the marketing service attempts to send a promo push
  Then the send is rejected at the compose layer with reason "no_gdpr_consent"
  And no delivery attempt is made to APNs or FCM

- Given a user with quiet hours 21:00 to 08:00 in America/New_York
  When a social notification is generated at 23:30 New York time
  Then the notification is suppressed and logged as "quiet_hours_dropped"
  And a security notification in the same window delivers with critical-alert flag

- Given a user who toggled marketing to user-off on device A
  When the server changes the marketing category default from off to on
  Then device A remains user-off and no delivery occurs
  And the preference audit log retains the original user-off transition

- Given an Android 13 device where POST_NOTIFICATIONS is denied
  When the in-app settings screen renders
  Then all category toggles display a "blocked at OS level" state
  And tapping any toggle deep-links to the system app settings

The Unsubscribe Flow Push Apps Keep Forgetting

CAN-SPAM does not apply to push, but a user who cannot easily mute a category will mute your whole app at the OS level, and you will never get them back. The spec requires a one-tap unsubscribe link inside every marketing and social push — tap it, land on a screen that confirms the category was muted, offer a single "undo" and a link to full preferences. No login wall. No "sad to see you go" friction. That one flow has done more for retention on products I have worked on than any re-engagement campaign.

Preference Drift Audit

Push preferences drift because three systems can disagree: the app preference store, the notification provider, and the operating system. The spec should include a weekly audit job, not just a settings screen.

Preference drift audit

Input:
- user preference snapshot
- provider subscription state
- iOS or Android permission state
- last delivery receipt

Mismatch examples:
- app says marketing off, provider subscription still active
- app says enabled, OS permission denied
- critical alert enabled without entitlement
- quiet hours missing timezone

Action:
- repair provider state when app preference is authoritative
- show blocked-at-OS badge when OS denies delivery
- suppress delivery when precedence is ambiguous
- log audit repair with category, device id, and old/new state

This is not just hygiene. It prevents the most frustrating push bug: a user turns something off, the UI agrees, and the backend still sends it two days later.

Keywords: push notification preferences · notification categories · quiet hours timezone · iOS provisional authorization · Android POST_NOTIFICATIONS · GDPR push consent · critical alerts spec · delivery receipts

Editorial Note