Sergey Kopanev: you sleep — agents ship

Go Back
AMORE: Acquisition, Monetization, Onboarding, Retention, Engagement · Part 6

One Offer, Ten Channels, One Story


A user gets a push notification at noon: “50% off ends at midnight.”

They open the app at 4 PM. An in-app banner says: “2 hours left to claim your 50%.”

They tap the banner. The paywall loads and says: “Offer expired.”

The user does not buy. They do not email support to ask for the discount. They just close the app and feel lied to.

Marketing teams often treat channels like isolated silos. The lifecycle team writes the push notifications. The product team configures the in-app banners. The monetization team builds the paywalls.

The user experiences all of them as one single entity. When the app speaks out of three different mouths and contradicts itself, the dissonance creates immediate distrust. The user tunes out.

The Default State is Disconnected

Most retention stacks wire copy directly into the delivery mechanism.

Braze holds the push notification text. RevenueCat holds the paywall packages. A custom config file holds the in-app banner strings.

This works for static, evergreen messaging. It breaks the moment you introduce urgency. If an offer has a rolling 24-hour window, the push needs to know exactly when that window ends for that specific user. If you hardcode “ends tonight” in the push template, but the user’s actual 24-hour clock expires at 3 PM, you have created a lie.

The Source-of-Truth Offer

The fix is architectural. Coordination between teams will not solve this.

The offer must be a stateful, centralized object. Channels must be treated as dumb renderers.

In AMORE, the offer object holds the entire narrative line:

  • price_modifier: 50%
  • rationale: “Summer Sale” vs “Winback”
  • expires_at: Timestamp
  • status: Active, Expired, Redeemed

When a push notification fires, the template in the lifecycle tool does not contain the hardcoded strings. It fetches the variables from the backend offer state.

Dumb Channels, Smart Core

A channel’s only job is to present the current state of the offer.

If an offer expires in 3 days, the email template renders: “Ends Sunday.” When the push fires on Saturday, the payload resolves to: “Ends tomorrow.” When the user opens the app, the banner reads the local time against the expires_at timestamp and renders: “Expires in 4 hours.”

If the deadline shifts or the offer is revoked, the underlying object updates. The channels instantly reflect the change without requiring a marketer to update three different platforms.

The Fallback to Silence

What happens if the offer expires between the push notification being delivered to the device and the user tapping it?

The deep link opens the app. The app checks the offer ID. The status is Expired.

The naive approach renders a broken paywall or an empty screen. The opportunistic approach ignores the expired context and just shows full price.

The correct approach is fallback logic. As discussed in the Value-Comparison Guard article, the engine evaluates if there is a valid backup offer. If there is, it smoothly pivots the narrative: “That offer expired, but here is a [Rank 4] offer for you.”

If no fallback exists, the app gracefully handles the dead end: a simple toast notification stating the offer has expired, and drops the user into the normal, unlocked experience. No broken screens. No aggressive full-price bait-and-switches.

The Trade-Off

Cost: You cannot use out-of-the-box lifecycle templates easily. You have to build middleware that intercepts Braze/Iterable webhooks and hydrates them with live offer state before delivery. You have to strictly type your offer objects and enforce that no channel goes rogue and hardcodes its own urgency.

Benefit: The app feels intelligent. The urgency feels real because it is mathematically consistent across every touchpoint.

Takeaway

One offer across ten channels must be one narrative line. Do not hardcode strings in your delivery tools. Treat channels as dumb view layers over a stateful offer object.


Next: Pressure matches state, not calendar