Nearby & "What Can I Use Now"
The everyday high-frequency hook — befday merchants near you, each annotated with what's actionable there (points, perks, stamps, birthday), ranked by what's most worth doing. The complement to the once-a-year birthday engine.
Status: Accepted (direction); implementation deferred
Date: June 2026
Decision: Build a nearby surface in apps/native that answers one question at the decision moment: “where can I go right now to use what I have?” It shows befday merchants near the user, each annotated with what’s actionable there — redeemable points, active perks/vouchers, stamp progress, and birthday perks. This is the everyday hook that complements the once-a-year birthday engine.
Relationship to the existing Nearby Alerts feature (already built)
apps/nativealready ships Nearby Alerts (docs): an entirely on-device geofencing flow that fires a local notification when the user walks near a bookmarked shop (nearest-20 regions, 12h cooldown, quiet hours, configurable radius — no server, no router beyondshops.getBookmarkedShops). That is a push/proximity-alert mechanic. This decision is a different thing: an interactive, server-ranked discovery surface (list + map) annotated with the user’s actionable state. They are complementary — Nearby Alerts pushes you toward bookmarks; this surface lets you browse/decide. See Reconciliation with Nearby Alerts before implementing.
TL;DR
A list + map of nearby befday merchants, each annotated with the user’s actionable state (points usable, active perks, stamp progress, birthday perk), sorted by what’s most worth doing, not just distance. It’s the high-frequency “where should we go?” hook that keeps the app alive between birthdays. Mostly reads existing data; location is optional. Complements — doesn’t replace — the on-device Nearby Alerts push.
Context
The birthday engine is the emotional spine but fires once a year per user. The points wallet, receipt wallet, and stamps give standing value, but none answer the everyday, in-the-moment question:
“I’m out / deciding where to go — where can I use my befday stuff right now?”
That moment happens many times a week. If befday is the tool people open to decide, it becomes a habit. This is the frequency layer that keeps the app from going dormant between birthdays.
The insight: befday knows three things a generic map doesn’t — your points balance, your perks, and your history at each merchant. A map shows where places are; befday shows what’s waiting for you there. That annotation is the product.
Decision
A nearby surface (list + map) of befday merchants, each annotated with the user’s actionable state at that merchant, sorted by “what’s most worth acting on.”
What it shows per merchant
| Annotation | Source |
|---|---|
| Points usable | Points currency wallet balance → max credit redeemable here |
| Active perks | voucher_codes claimed/available for this shop |
| Stamp progress | “2 more stamps → free coffee” |
| Birthday perk | Birthday engine grant active in window |
| Distance / open | Geo + opening hours |
Sorting — “actionability,” not just distance
The default sort is what’s most worth doing, not nearest. A merchant with a redeemable birthday perk or a nearly-complete stamp card ranks above a closer one with no actionable state. Rough priority:
1. Birthday perk active (time-sensitive, high emotion)
2. Perk/voucher expiring soon
3. Stamp card near completion (1–2 away)
4. Redeemable points balance
5. Plain nearby befday merchant (discovery)
This turns the list into a recommendation, not a directory.
Two views
- List (primary) — ranked cards, each showing the merchant + its top actionable annotation. Fast, cheap, works without precise location.
- Map (secondary) — pins for spatial “what’s around me,” same annotations on tap.
Location handling
- Location permission is optional — without it, fall back to a city/area picker or “merchants you’ve visited.” Never block the surface on GPS.
- Request location with a clear in-context reason (“see what’s near you”) — not a cold prompt at launch.
Why this is the right everyday hook
| Idea | Frequency | Uses befday’s unique data | In-the-moment utility |
|---|---|---|---|
| Nearby + my perks | High (every outing) | Yes (points/perks/history) | Yes — decision tool |
| Spend insights | Medium | Yes | No (reflective, not active) |
| Tiers/status | Passive | Yes | No |
| Birthday engine | Once/yr | Yes | Only in window |
Nearby is the only candidate that is both high-frequency and actively useful at a decision point. Spend insights and tiers are good supporting surfaces but don’t pull an open the way “where do we go right now” does.
Data Model Impact (sketch)
Mostly reads existing data — minimal new schema. The work is aggregation + geo, not new primitives.
shops extension (geo + discovery)
| Column | Type | Notes |
|---|---|---|
latitude |
decimal | nullable — for distance sort/map |
longitude |
decimal | nullable |
opening_hours |
jsonb | nullable — for “open now” badge |
is_discoverable |
boolean | merchant opt-in to appear in nearby |
If shops already carry address/geo from the seeded directory, this is just confirming/backfilling lat-lng + adding the discoverability flag.
No new transactional tables
The actionable annotations are derived at query time from existing data:
- Points usable →
befday_wallets.points_balance(points currency) - Active perks →
voucher_codeswhereuser_id = meand shop matches - Stamp progress →
stamp_cardsfor(store_customer, shop) - Birthday perk →
birthday_perk_grants(birthday engine) in window
A per-user “nearby summary” can be cached briefly (it changes slowly) to keep the surface fast.
API Impact (sketch)
| Procedure | Status | Notes |
|---|---|---|
consumer.nearby.list |
New | Ranked merchants + actionable annotations; takes optional lat/lng |
consumer.nearby.map |
New | Geo-bounded pins (or fold into list with a bbox param) |
merchant.profile.setLocation |
New | Merchant sets lat/lng, hours, discoverability |
consumer.nearby.list is the workhorse: it joins the user’s wallet/perks/stamps/birthday grants against discoverable shops, computes the actionability rank, and returns ready-to-render cards. Keep the heavy aggregation server-side; the client just renders.
Performance: this is a fan-out read across several tables per merchant. Build an index map of the user’s state once (wallet, perks by shop, stamps by shop, birthday grants), then annotate the merchant list in a single pass — avoid N queries per merchant.
Reconciliation with Nearby Alerts
The app already ships Nearby Alerts — an on-device geofencing flow. This decision is a different, complementary surface, not a replacement. This section keeps them from colliding.
What exists today (Nearby Alerts)
| Aspect | Nearby Alerts (built) |
|---|---|
| Job | Proximity push — notify when you walk near a bookmarked shop |
| Trigger | OS geofence + TaskManager, entirely on-device (no server push) |
| Data | shops.getBookmarkedShops → nearest-20 regions; on-device expo-sqlite |
| Output | A local notification that deep-links to the shop |
| Throttling | 12h cooldown, quiet hours, max-20 regions, last-notified map |
| Opt-in | Profile toggle (background location + notifications), fully optional |
What this decision adds (Nearby surface)
| Aspect | Nearby Surface (this decision) |
|---|---|
| Job | Interactive discovery — “where can I use what I have right now?” |
| Trigger | User opens the surface (pull), not a push |
| Data | Server join of wallet/perks/stamps/birthday vs discoverable shops |
| Output | A ranked list + map annotated with actionable state |
| Scope | All discoverable befday merchants, not just bookmarks |
The reconciliation rules
- Push vs pull — keep both, don’t merge. Nearby Alerts is the background push (“you’re near a bookmark”); this is the foreground browse (“show me everything actionable nearby”). They serve different moments and should stay distinct features.
- Different shop sets on purpose. Alerts watch bookmarked shops (intentional, capped at 20, on-device). This surface ranks all discoverable shops (server-side). Don’t unify the source — the constraint (nearest-20 on-device) exists for battery/OS limits and shouldn’t leak into the server surface.
- Shared geo data is the integration point. Both need merchant coordinates. The
shopsgeo +is_discoverablefields proposed here are the canonical location data; Nearby Alerts can keep reading bookmark locations, but merchant lat/lng should converge on one source rather than two. - Notification coordination (cold-start / fatigue). Once this surface adds server push (expiring perks, birthday, new nearby perks per Native Retention Stack), it must be frequency-capped against the on-device Nearby Alerts so the user isn’t double-notified about the same shop. Track this under the shared push-frequency cap.
- Naming. Two features both called “nearby” will confuse. Consider distinct names — e.g. keep “Nearby Alerts” for the geofence push and name this surface something action-oriented (“Use Now” / “Around You”) to avoid user and code ambiguity.
Net effect on this decision
- The geo/coordinate work partially overlaps existing bookmark-location handling — converge on one canonical
shopsgeo source. - This surface is genuinely net-new (server ranking, annotations, list/map UI); it doesn’t reuse the on-device geofencing code.
- Open questions added: the naming split, and the push-frequency coordination between the two nearby features.
Consequences
| Type | Consequence |
|---|---|
| Pro | The high-frequency complement to the birthday engine — keeps the app alive between birthdays. |
| Pro | Reuses befday’s unique data (points/perks/history) to be a recommendation, not a generic map. |
| Pro | Mostly reads — minimal new schema, no new transactional flows. |
| Pro | Natural home for discovery → drives users toward merchants they haven’t visited (acquisition for merchants). |
| Con | Needs merchant geo data + opt-in; sparse coverage early makes “nearby” feel empty (cold-start, like birthday). |
| Con | Location permission UX must be handled gracefully (optional, in-context) or it feels invasive. |
| Con | Actionability ranking is a fan-out aggregation — needs careful query design + caching to stay fast. |
| Con | Value scales with merchant density — strongest in areas with many befday merchants, weak where coverage is thin. |
Resolved Decisions
| Question | Decision |
|---|---|
| Everyday hook? | Yes — nearby “what can I use now” is the high-frequency surface |
| Sort order | Actionability (perks/stamps/points), not pure distance |
| Views | List (primary) + map (secondary) |
| Location | Optional — graceful fallback to area picker / visited merchants |
| New schema | Minimal — shop geo + discoverability; annotations derived at read |
| Discovery | Include unvisited merchants at the bottom for acquisition |
Open Questions
- Coverage cold-start: what’s the minimum merchant density per area before “nearby” is worth showing (vs hiding it)?
- Ranking weights: exact priority/tuning between birthday > expiring perk > stamp-near-done > points > discovery.
- Caching window: how long to cache the per-user nearby summary before it feels stale.
- Map provider: which maps SDK (cost, native feel) for the secondary map view?
- Privacy: is location ever sent to the server, or is distance computed client-side from merchant coordinates?
- Sponsored placement: do merchants ever pay to rank higher in discovery, and how to keep that from polluting the “actionable” trust?
- Naming vs Nearby Alerts: what do we call this surface so it isn’t confused with the existing on-device Nearby Alerts push? (e.g. “Use Now” / “Around You”.)
- Push coordination: how is this surface’s server push frequency-capped against Nearby Alerts so a user isn’t double-notified about the same shop?