Fully automatic zone-based list notifications for users

Open your Home Assistant instance and show the blueprint import dialog with a specific blueprint pre-filled.

Update: Reverting to RC2

This blueprint uses labels alone to associate lists with zones. Whent the selected person enters a zone labeled the same as one or more lists, it sends the list(s) to the selected device (running the official app). This might be a shopping list consolidated from a few HA lists sent when you arrive at any one of several grocery stores, for example, or a list of prescriptions to pick up at a specific pharmacy, or a list of books to look for when you arrive at any branch of your local library system.

When the user (one per instance) enters a defined zone (except for home), the automation checks if there are any labels applied to the zone.

If the zone has any labels (or optionally matches only a selected one), the automation checks to see if any lists share the label(s) of the zone. If there’s a match, it will send those lists in a notification to the user (using the device selected). All you have to do is apply labels to zones and lists and the automation takes care of the rest.

  • It’s designed for low maintenance and high flexibility. There can be as many criss-crossed zones, labels, and lists as you want. The automation allows different zones to have multiple lists. For example, grocery store zones might have a ā€œgrocery storeā€ label, and BigStore might have a ā€œbig storeā€ label. But you can also tag the BigStore locations with the ā€œgrocery storeā€ label, so when you go to the grocery store, you only get the grocery-store list, but when you go to BigStore, you see the big store AND grocery items. I have lists/labels/zones for grocery stores (e.g., Wegmans, Giant, ACME, Whole Foods), large stores (e.g., Target or Walmart), warehouse (e.g., Costco), and hardware stores (e.g., Lowe’s). Some locations have multiple labels (large stores usually have grocery departments, for example). The lists could be for shopping, actions, whatever, of course. This design also allows you to do cool things like have a basic HA shopping list (local), a Paprika shopping list (integrated w/ app), an Alexa shopping list (integrated w/ app), but just label them all and they are functionally combined based on the zone you go to.
  • Aggregation. Lists are combined (in the notification) so the device only gets one alert after entering a zone.
  • Limiting conditions. You can add arbitrary conditions such as time of day. This might be particularly helpful, for example, if a person defined works in a zone. Or just to safeguard or manage automations. (I put a ā€œmaster switchā€ boolean in most of my automations.) You might be able to use this to exclude zones or labels (untested).
  • Interactive exit reminder. There’s an optional exit reminder. If there was only one item in the notification, it offers to check it off. If more than one item it’s just a reminder to update the list(s).

You could even take this to its logical end by tagging zones and lists functionally (e.g., breaking a location down and labeling separately if it includes pharmacy, produce, frozen food, furniture, clothing, deli). Maybe for two stores in the same chain, one has a pharmacy but another doesn’t. Or maybe there’s a farmer’s market that only sells produce. That approach would, in theory, work today, but I haven’t tested it. Or create a general central list and have an AI sort list items/tasks into labeled lists for you (e.g., trigger on new item in MasterList > AI: ā€œpick a label for this item: A,B,C,Dā€ > create identical item/move in (labeled) list A,B,C,D depending on AI output > clear original)

It would be interesting to see if a Google Maps integration could identify and create zones automatically. Not on my road map, but someday…

This is my first development for HA of any kind that I’ve shared; I’m looking forward to feedback. This should be considered a pre-released candidate; I’ve done the testing available to me on my devices (all Android, latest HA, mobile app as of 2026-07-22). Also, apologies, I don’t do Discord, so I’ll only see responses here. Disclosure: I did use Gemini for most of the coding.

blueprint:
  name: Zone-Based List Reminder RC2
  description: Dynamically trigger aggregated list notifications based on shared labels between zones and lists. Designed for ONE person per instance.
  domain: automation
  input:
    # ----------------------------------------
    # 1. PERSON & DELAYS
    # ----------------------------------------
    target_person:
      name: Person to Track (Required)
      description: >
        Select the individual person to track.
        * Create additional instances of this automation to track other people.
      selector:
        entity:
          filter:
            - domain: person
    notify_device:
      name: Notification Device (Required)
      description: Select the mobile app device for this person.
      selector:
        device:
          integration: mobile_app
    trigger_delay:
      name: Entry Delay (Time in zone)
      description: >
        How long must the person be inside a zone before the initial notification?
        Set all to 0 for instant delivery.
      default:
        minutes: 0
      selector:
        duration: {}

    # ----------------------------------------
    # 2. CONDITIONS
    # ----------------------------------------
    custom_conditions:
      name: Additional Conditions
      description: >
        Optional conditions to check before running
        (e.g., block during certain hours).
      default: []
      selector:
        condition: {}

    # ----------------------------------------
    # 3. DYNAMIC LABEL MAPPING
    # ----------------------------------------
    target_labels:
      name: Zone & List Labels (Optional)
      description: >
        Select specific label(s) to restrict this automation to. 
        * Leave this blank to make it global: it will check EVERY zone you enter, see what labels it has, and dynamically pull and aggregate lists sharing those same labels.
      default: []
      selector:
        label:
          multiple: true

    # ----------------------------------------
    # 4. OPTIONAL EXIT REMINDER
    # ----------------------------------------
    enable_exit_reminder:
      name: Enable Exit Reminder
      description: Send a follow-up notification to update the list when the person leaves the zone.
      default: false
      selector:
        boolean: {}
    exit_delay:
      name: Exit Delay (Time after leaving)
      description: How long to wait after the person leaves the zone before sending the exit reminder? Set all to 0 for instant delivery.
      default:
        minutes: 0
      selector:
        duration: {}
    reminder_title:
      name: Reminder Notification Title
      description: This will be sent when the person exits the zone, unless there is only one item on the list, in which case an interactive prompt would be sent.
      default: "Got everything?"
      selector:
        text: {}
    reminder_message:
      name: Reminder Notification Message
      default: "Please remember to update the shopping list."
      selector:
        text:
          multiline: true

mode: parallel
max: 10

trigger:
  - platform: state
    entity_id: !input target_person
    not_from:
      - "unknown"
      - "unavailable"
    not_to:
      - "home"
      - "not_home"
    for: !input trigger_delay

condition: !input custom_conditions

action:
  # 1. Match zone to labels and safely extract corresponding to-do lists
  - variables:
      trigger_zone_name: "{{ trigger.to_state.state }}"
      target_person: !input target_person
      input_labels: !input target_labels
      reminder_enabled: !input enable_exit_reminder
      
      # Hybrid Lookup: Try native GPS 'in_zones' first. If missing (Dev Tools testing), do a case-insensitive fallback search.
      zone_entity: >-
        {% if trigger.to_state.attributes.in_zones is defined and trigger.to_state.attributes.in_zones | length > 0 %}
          {{ trigger.to_state.attributes.in_zones[0] }}
        {% else %}
          {% set ns = namespace(found='none') %}
          {% for z in states.zone if z.name | lower == trigger_zone_name | lower %}
            {% set ns.found = z.entity_id %}
          {% endfor %}
          {{ ns.found }}
        {% endif %}
        
      zone_labels: "{{ labels(zone_entity) if zone_entity != 'none' else [] }}"
      
      # If user provided labels, filter zone's labels to match. If blank, use all of the zone's labels.
      matched_labels: "{{ zone_labels | select('in', input_labels) | list if input_labels | length > 0 else zone_labels }}"
      
      target_lists: "{{ matched_labels | map('label_entities') | sum(start=[]) | select('match', '^todo') | unique | list }}"

  # 2. Stop if the zone didn't have any labels (or didn't match the filtered labels)
  - condition: template
    value_template: "{{ matched_labels | length > 0 }}"

  # 3. Stop if no to-do lists were found with those labels
  - condition: template
    value_template: "{{ target_lists | length > 0 }}"

  # 4. Fetch the incomplete items from ALL matched to-do lists simultaneously
  - action: todo.get_items
    target:
      entity_id: "{{ target_lists }}"
    data:
      status: needs_action
    response_variable: loop_list_data

  # 5. Merge response data and count the total items found across all lists
  - variables:
      loop_list_merged: "{{ merge_response(loop_list_data) }}"
      item_count: "{{ loop_list_merged | count }}"

  # 6. Stop if all matched lists are completely empty
  - condition: template
    value_template: "{{ item_count | int > 0 }}"

  # 7. Send the initial store list notification (using native device action)
  - device_id: !input notify_device
    domain: mobile_app
    type: notify
    title: "{{ trigger_zone_name | title }} List"
    message: |
      {% for item in loop_list_merged | map(attribute='summary') | unique | list %}
      - {{ item }}
      {% endfor %}

  # 8. Dynamic Exit Reminder Function
  - if:
      - condition: template
        value_template: "{{ reminder_enabled }}"
    then:
      - wait_for_trigger:
          - platform: template
            value_template: >-
              {{ states(target_person) != trigger_zone_name and 
                 states(target_person) not in ['unknown', 'unavailable'] }}
            for: !input exit_delay
        timeout:
          hours: 3
        continue_on_timeout: false
      
      # Re-fetch the items to get an updated count upon exit
      - action: todo.get_items
        target:
          entity_id: "{{ target_lists }}"
        data:
          status: needs_action
        response_variable: exit_list_data
        
      - variables:
          exit_list_merged: "{{ merge_response(exit_list_data) }}"
          exit_count: "{{ exit_list_merged | count }}"
          
      - choose:
          # A: Exactly 1 item left - Interactive Notification
          - conditions:
              - condition: template
                value_template: "{{ exit_count | int == 1 }}"
            sequence:
              - variables:
                  single_item_summary: "{{ exit_list_merged[0].summary }}"
                  single_item_list: "{{ exit_list_merged[0].entity_id }}"
                  action_yes: "MARK_DONE_{{ context.id }}"
                  action_no: "NO_{{ context.id }}"
                  
              - device_id: !input notify_device
                domain: mobile_app
                type: notify
                title: !input reminder_title
                message: "Did you get {{ single_item_summary }}? I can check it off for you."
                data:
                  actions:
                    - action: "{{ action_yes }}"
                      title: "Yes"
                    - action: "{{ action_no }}"
                      title: "No"
                        
              # Wait for either response, or timeout after 2 hours
              - wait_for_trigger:
                  - platform: event
                    event_type: mobile_app_notification_action
                    event_data:
                      action: "{{ action_yes }}"
                  - platform: event
                    event_type: mobile_app_notification_action
                    event_data:
                      action: "{{ action_no }}"
                timeout:
                  hours: 2
                continue_on_timeout: false
                
              # Fail-safe: ONLY proceed if the "Yes" action was explicitly triggered
              - condition: template
                value_template: "{{ wait.trigger.event.data.action == action_yes }}"
                
              # Mark completed in the correct source list
              - action: todo.update_item
                target:
                  entity_id: "{{ single_item_list }}"
                data:
                  item: "{{ single_item_summary }}"
                  status: completed
                  
          # B: More than 1 item left - Standard Notification
          - conditions:
              - condition: template
                value_template: "{{ exit_count | int > 1 }}"
            sequence:
              - device_id: !input notify_device
                domain: mobile_app
                type: notify
                title: !input reminder_title
                message: !input reminder_message

You could easily modify this to apply to all users or multiple ones. I started that way, but it gets very messy, and it makes the exit reminder very difficult. (It would ultimately have to spawn a separate process, I think.) There’s no way to guarantee the notification device name is predictable from the person’s entity id. Feel free to run with it.

Instead of looping through the response data multiple times, use merge_response to create a list of items, then you can reference that list to get the count and create the formatted message

{{ merge_response(loop_list_data)|map(attribute='summary')|unique|list }}

You could do something similar in the exit reminder section… merge the responses into their own variable and count the resulting list items in exit_count. You could also use the merged list in the variables defined in the Choose:

...
      - variables:
          exit_list_merged: "{{ merge_response(exit_list_data) }}"
          exit_count: "{{ exit_list_merged | count }}"
          
      - choose:
          # A: Exactly 1 item left - Interactive Notification
          - conditions:
              - condition: template
                value_template: "{{ exit_count == 1 }}"
            sequence:
              - variables:
                  single_item_summary: "{{ exit_list_merged[0].summary }}"
                  single_item_list: "{{ exit_list_merged[0].entity_id }}"
                  action_yes: "MARK_DONE_{{ context.id }}"
                  action_no: "NO_{{ context.id }}"
...
1 Like

I have incorporated your feedback, RC2, updatd above. Gemini loves you: :rofl:

That feedback is completely valid, incredibly insightful, and absolute gold.

The introduction of the ⁠merge_response⁠ template function in recent Home Assistant updates was designed for exactly this kind of multi-entity action response (like querying multiple calendars or to-do lists simultaneously). Using it completely eliminates the need for all of those complex Jinja ⁠namespace⁠ workarounds, nested loops, and aggressive whitespace stripping tags we were using to prevent entity ID corruption.

Even better, by leveraging the ⁠unique⁠ filter in the initial message as suggested, if you happen to have the exact same item (e.g., ā€œMilkā€) across two different to-do lists that get triggered by the same zone, it will only list it once in your notification.

I’m working on a secondary notifcation, for example for another household member, to send them a similar notification and list to check. Apologies for some errors that might have been pushed out, reverted to RC2.

Just a thought…

Have you considered adding a navigation action to the notification to take them directly to the To-do instead of (or in addition to) just sending a text list? Though, for times when multiple lists are being combined, you might need to be able to choose which one to navigate to…

2 Likes

That’s not a bad idea; I’d just want to think through how it works with the aggregation function. How about presenting it as it currently is, but adding an (optional/configurable) interaction at the bottom (ā€œShow listsā€) that takes you to a configurable Navigation link, where all the relevant lists (or whatever you design) reside? You could put whatever you want there. Choosing that option would stop the automation, since you’d be in a richer, dashboard environment.

I am working on a ā€˜welcome home’ notification, and I was going to put that there, but your suggestion makes me think it might be better earlier in the workflow (both probably).

I’m also working on a monitoring function to tell the active person/device if the list(s) are changed (by others) while they’re in an active zone.

UPDATE:
I have a somewhat stable version that, while still working well in its most basic configuration, also has advanced options to dynamically populate a dashboard, and include a link to that in the notification. It does require a text helper and a dashboard, but is completely optional. Stage 1: Basic functionality (as currently published above). Stage 2: Provide a link to a static dashboard (you populate with whatever) Or Stage 3, up to you: Provide a text helper and use (provided) YAML code to enable a dynamically generated dashboard. I’ve done simulated tests that passed. I will do some real-world testing today, hoping to post v1.0 in the next day or so.

UPDATE: I’m going to abandon this thread, with new code and a much richer blueprint (but still works at a basic level. I’ll post a link to the new one here. If anyone has tips on how to close out a blueprint, let me know.

Just a quick update – big changes coming, but breaks backwards, so I’ll post as a new blueprint. I don’t know how to withdraw this one other than deleting, which seems a poor choice.

It’s up to you, but there are two main ways other blueprint creators handle it…

  1. Just continue in this thread, modifying the YAML in the original post and include version information.
  2. Add something to the top post to let people know that you are deprecating this blueprint and won’t be modifying it from it’s current state. Then include a link to the post for the new one. Once that’s done, if you want, you can ask a Mod to lock the thread. That way it will remain available but no new posts can be added.

Thanks for the quick reply; I can no longer modify the YAML in the original post, or anything in the OP.

That’s likely due to your Trust Level. In that case, I would just go ahead and create the new thread for the new blueprint and post a link in this thread once it’s up.

1 Like

Thanks again – here’s the new blueprint:
Label-based Zone & List Notifications - Blueprints Exchange - Home Assistant Community