Back to blog
FILE 0xB4·BRAND-AWARE LOCATION REMINDERS WITHOUT A CUSTOM GEOCODER

Brand-aware location reminders without a custom geocoder

May 23, 2026 · aws, location-services, geocoding, side-project

I wanted to add "remind me when I'm at any Walmart" to a location-reminder feature, alongside the existing "remind me when I get home" pin-based reminders. The trick was making both work from one location-ping handler without writing a geocoder.

What was happening

The existing implementation stored each reminder as a pin: a specific lat/lon plus a 150m trigger radius. "When I get home" or "when I'm at work" resolved nicely once the user had a notes entry for home or work. But "any Walmart" doesn't have a single pin; it has many.

What I found

AWS Location's geocoder returns categories for places. When you look up Walmart, results come back tagged with categories like store, shop, grocer, pharmacy, fuel, etc. The presence of those categories is the signal that the input is a brand name, not a specific address.

So the routing decision falls out of the geocode response itself: nickname-resolved place (home, work) → pin reminder; category-tagged store → brand reminder.

The fix

One new nullable column on the reminders table:

ALTER TABLE location_reminders ADD COLUMN brand VARCHAR(120) NULL;
-- NULL  = specific pin (use lat/lon/radius)
-- set   = chain name (use nearby-search on every ping)

On reminder creation, the conversational handler geocodes the phrase and inspects categories:

BRAND_CATEGORIES = {'store', 'shop', 'restaurant',
                    'grocer', 'pharmacy', 'fuel'}

result = location.search_place_index_for_text(text=phrase)[0]
if BRAND_CATEGORIES & set(result['Categories']):
    insert_reminder(brand=result['Name'])  # "Walmart"
else:
    insert_reminder(lat=result['Lat'], lon=result['Lon'])

On every location ping the user emits, the handler runs both modes:

def on_location_ping(user, lat, lon):
    # Pin reminders: cheap distance check
    for r in pin_reminders(user):
        if haversine(lat, lon, r.lat, r.lon) <= r.radius_m:
            fire(r)

    # Brand reminders: ~1 SearchPlaceIndexForText per active brand per ping
    for r in brand_reminders(user):
        hits = location.search_place_index_for_text(
            text=r.brand,
            bias_position=(lon, lat),
            max_results=5,
        )
        for h in hits['Results']:
            if haversine(lat, lon, h['Lat'], h['Lon']) <= 150:
                fire(r)
                break

Sanity-checked at three locations: a Walmart in town fires at distance ~0, a Walmart 75 miles away fires at distance ~0, a spot in New Orleans with no Walmart within 150m correctly doesn't. No app rebuild was needed — the change is server-side plus one column.

What I'd do differently

The cost model is the thing to watch. Brand reminders are O(1) geocode call per active brand reminder per location ping. If a user emits a fix every 60 seconds and has 5 active brand reminders, that's 7,200 geocode calls per user per day. For now the user count is small enough that this doesn't matter, but at scale this needs a cache (chain → nearby store list) keyed by geohash with a TTL on the order of a day. Stores don't move that often.

The classification trick — "let the geocoder's category tags tell you whether this is a brand or a pin" — generalizes. Anywhere you'd otherwise be writing rules to disambiguate user input, see if the upstream API already returns a hint you can use.