Queue notifications with a TODO list

Here is a simple but effective way to temporarily store notifications until a certain set of conditions is fulfilled and you actually want them to fire. It’s just a few extra lines to an already existing script, but very useful; hopefully with voice making headways, we will soon have a better way to handle something like this!
If, for example, you want automation notifications to fire only after you are back home, or after you wake up, then this is for you.

Features:

  • Any and multiple conditions to queue or not.
  • Queueing skip; if a notification never needs to be queued, in which case it will be ignored if queueing conditions are met.
  • Check and retrieve queued notifications (called by whatever means you desire) and send back to the notify script.
  • Queued notification timeout, using todo’s due_datetime to consider a notification as “stale” after a defined period. The notification is not removed from the todo list on timeout, but it is skipped when the queue check script is called.
  • Do not duplicate notification in the queue (but timeout is overridden if duplicate found).

Note: This is a process that requires customization to your needs, hence only the base yaml is given below, for you to adapt (conditions, notification type, etc). If you want a full example of a working notify script (TTS) and queueing system, presence and sleep based, check out this github folder.

Prerequisites:

  • Various automations that trigger notifications; any kind of notification that contains a message will do, TTS, persistent, etc. Those notification actions will be replaced by Service calls to the notify script below (unless you already have a notify script, in which case, you simply update it with the below logic).
  • A TODO list called queued_notifications (or something else, but then don’t forget to adjust the scripts below where appropriate). Here I used the local_todo integration, but any should work the same, as long as they support the services of todo.

Sidenote: It’s easy enough to add multilanguage support in case your home requires it, simply add a script field language, and language-specific todo lists, then switch between them depending on the language (if language equals EN then use todo list EN etc); works for both scripts the same.

The good stuff

alias: Notify
mode: queued
max: 20
icon: mdi:speaker-message
fields:
  message:
    selector:
      text: null
    name: Message
    required: true
    default: Hey there!
  do_not_queue:
    selector:
      boolean: null
    name: Do Not Queue
    description: >-
      Do not queue notification, even if conditions are met (ignore it instead).
    required: false
  queued_timeout_min:
    selector:
      number:
        min: 0
        max: 525600
        step: 1
        mode: box
    name: Queued Timeout Min
    description: >-
      Queued notification timeout in minutes (if ever queued). If specified,
      after that number of minutes, the notification will be skipped when the
      queue is retrieved.
sequence:
  - if:
      # This is where the script decides if it should fire the notification immediately
      # or queue it (you can check if away, if sleeping, etc)
      # For example:
      - condition: state
        entity_id: person.you
        state: not_home
    then:
      - if:
          - condition: template
            value_template: "{{ do_not_queue is not defined or not do_not_queue }}"
        then:
          - service: todo.get_items
            target:
              entity_id: todo.queued_notifications
            data:
              status: needs_action
            response_variable: result
          - if:
              - condition: template
                value_template: >-
                  {{ not message in result['todo.queued_notifications']['items'] | map(attribute='summary') }}
            then:
              - service: todo.add_item
                data:
                  item: "{{ message }}"
                  due_datetime: >-
                    {{ now() + timedelta(minutes=(queued_timeout_min if (queued_timeout_min is defined and queued_timeout_min > 0) else 525600)) }}
                target:
                  entity_id: todo.queued_notifications
            else:
              - service: todo.update_item
                data:
                  item: "{{ message }}"
                  due_datetime: >-
                    {{ now() + timedelta(minutes=(queued_timeout_min if (queued_timeout_min is defined and queued_timeout_min > 0) else 525600)) }}
                target:
                  entity_id: todo.queued_notifications
    else:
      # This is where your notification normally fires (immediate)
      # For example:
      - service: notify.persistent_notification
        data:
          message: "{{ message }}"

Below is the script to check for the presence of queued notifications, and if any (not timed out), feed them back (all at once, but numbered) to the notify script above (entity ID assumed to be script.notify), then delete them from the queue.

You can easily call this script wherever appropriate (when automation detects you’re back home, or you woke up, etc).

Note: As of 2023.12.3, the Numeric state condition doesn’t show the title properly in UI if zero is used as number (bug), but the condition still works as expected.

alias: Check Queued Notifications
sequence:
  - if:
      - condition: numeric_state
        entity_id: todo.queued_notifications
        above: 0
    then:
      - service: todo.get_items
        target:
          entity_id: todo.queued_notifications
        data:
          status: needs_action
        response_variable: result
      - variables:
          remaining: |-
            {%- set ns = namespace(count=1) %}
            {%- for i in result["todo.queued_notifications"]["items"] %}
              {%- if now() <= as_datetime(i.due) %}
                {{ ns.count }}. {{ i.summary }}.
                {%- set ns.count = ns.count + 1 %}
              {%- endif %}
            {%- endfor %}
      - if:
          - condition: template
            value_template: "{{ remaining != '' }}"
        then:
          - service: script.notify
            data:
              message: You have queued notifications. {{ remaining }}
              do_not_queue: true
          - service: todo.remove_item
            data:
              item: >-
                {{ result["todo.queued_notifications"]["items"] | map(attribute='summary') | list }}
            target:
              entity_id: todo.queued_notifications
mode: single
icon: mdi:speaker-message

Calling the scripts - Some examples

Simple notification that will automatically queue if your conditions for it are met.

service: script.notify
data:
  message: The weather is now announcing some snow.

Same as above, but with a queue timeout after 30 minutes.
In the example, we assume the notification is attached to a temperature sensor’s value, hence would no longer be considered viable after a short time.
If you were to call the queue check script over 30 minutes after the initial time of notification, that one would be skipped.

service: script.notify
data:
  message: Temperature outside is nice. You should open up the house.
  queued_timeout_min: 30

Never queue this notification. If conditions for queueing are met, the notification is then simply ignored.

service: script.notify
data:
  message: Time to go to bed!
  do_not_queue: true

Check for queued notifications

service: script.check_queued_notifications
data: {}

Feel free to correct any mistake I might have made writing this down.

3 Likes

Wow, these scripts are awesome. I just implemented this for a bunch of my automations.

One question, do you know if its possible to have the ‘script.check_queued_notifications’ script play all queued notifications, regardless if someone is home are not?

I have it set to trigger this script when my front door opens, and sometimes the door opens before the mobile app has realized I’m home yet. Currently, if this happens, it just erases all the to-dos and no notificaitons are played.

1 Like

Thanks :wink:

Depends on your implementation of the automation and what you have available as trigger I guess.
You could:

  • add a fixed delay (front door open, wait x minutes, check queued)
  • change the trigger to use your mobile’s device_tracker changing to home
  • change the trigger to a motion sensor inside, triggering after the front door was opened

You can also use the service homeassistant.update_entity to force updating an entity, it’s not 100%, but it seems to help in some cases.

If you want to keep the same trigger, then you would have to add a “bypass” parameter for the Notify script which is then used in the condition, and set to true by the Check queued notification script.
Check the VoiceAssist folder here: GitHub - Nerivec/SmartHomeEnhanced: Tips, tricks, scripts to enhance your smart home. there are more detailed versions of the scripts, a lot more parameters, a lot more customization!