To-Do - Due Notifications

There may be an obvious answer I’m missing but I’ve been unable to really find a clear way of how to do this.

I am looking to send notifications to my device based on the due date of a to-do list task. Essentially, when the task is due that day (or ideally even that hour), send a notification to my companion app. How would I do this please?

2 Likes

I echo this request! Can anyone assist with this? I already have it setup when an item is added I get a notification but would be great if I could get one when it’s due(maybe even add an option to snooze it?!?!)

Thanks

Currently working on this as literally nobody else has thought of this apparently.

If I get something working, I’ll post it here! At least someone here can be helpful :wink:.

That’s fantastic news. Let me know how I can help!

Here’s what I have so far, feel free to try it yourself. Currently doesn’t work as it always evaluates to False - don’t know why. 0 should equal 0 but apparently not here. I’ve tested the outputs from delta.days and it definitely returns 0, so yeah.

description: To-do Notifications
trigger:
  - platform: time_pattern
    hours: "*"
    minutes: "*"
condition: []
action:
  - service: todo.get_items
    metadata: {}
    data:
      status: needs_action
    response_variable: todovar
    target:
      entity_id: todo.[PUT YOUR LIST NAME HERE]
  - if:
      - condition: template
        value_template: |
          {%- for item in todovar['todo.YOURLISTHERE']['items'] %}
            {% if 'due' in item%}
              {% set delta = as_local(strptime(item['due'], "%Y-%m-%d")) - today_at("00:00") %} 
              {% if delta.days == 0 %} True
              {% elif delta.days < 0 %} True
              {% elif delta.days > 0 %} False
              {% endif %}
            {% endif %}
          {%- endfor %}
    then:
      - service: persistent_notification.create
        data:
          notification_id: TASK DUE
          title: A task is due today!
          message: A task is due today, check your to-do!!
mode: single

YAML sure is great!

I got it working, I had to add {% break %} to each section. EDIT: I’ve added functionality to alert you to which task is due (not sure if you have multiple on the same day but hey). I’ve also made it so it notifies my phone, but you can modify it to suit your needs (persistent notification is there just disabled, copying message content to it should be enough). Full code:

alias: YOUR's To-Do Notification
description: To-do Notifications
trigger:
  - platform: time
    at: "08:00:00"
  - platform: time
    at: "12:00:00"
  - platform: time
    at: "15:00:00"
condition: []
action:
  - service: todo.get_items
    metadata: {}
    data:
      status: needs_action
    response_variable: todovar
    target:
      entity_id: todo.YOUR_to_do
  - if:
      - condition: template
        value_template: |-
          {%- for item in todovar['todo.YOUR_to_do']['items'] %}
            {% if 'due' in item%}
              {% set delta = as_local(strptime(item['due'], "%Y-%m-%d")) - today_at("00:00") %} 
              {% if delta.days == 0 %}
                True
                {% break %}
              {% elif delta.days < 0 %}
                True
                {% break %}
              {% endif %}
            {% endif %}
          {% endfor -%}
    then:
      - service: persistent_notification.create
        data:
          notification_id: TASK DUE
          title: A task is due today!
          message: A task is due today, check your to-do!!
        enabled: false
      - service: notify.mobile_app_YOURfone
        data:
          title: A task is due!
          message: |
            {%- for item in todovar['todo.YOUR_to_do']['items'] %}
              {% if 'due' in item%}
                {% set delta = as_local(strptime(item['due'], "%Y-%m-%d")) - today_at("00:00") %}
                {% if delta.days < 0 %} Task OVERDUE by {{ (delta.days | abs) }} day(s)  {{ '\n\n' -}} {{ item['summary'] }}
                {% elif delta.days == 0 %} Task due TODAY {{ '\n\n' -}} {{ item['summary'] }}
                {% endif %}
              {% endif %}
            {% endfor -%}
          data:
            ttl: 0
            priority: high
mode: single

Awesome! I’ll put it in my system tonight when I get some time and keep you posted. Really appreciate this!!!

I implemented this but having an issue with the mobile notification(which is what I want!). Logic for overdue works and if I enable the persistent notification it works fine. It’s when I try to have it notify my phone(with your message format) with doesn’t seem to send any notification. I can simplify your message with something simple like “Task Due” and that works fine but when I try to use your message format to get more details in the message(which again is what I was looking for) it just doesn’t send out a message.

I’m getting these errors when trying to execute it:

  • Home To-Do Notification: If at step 2: Error executing script. Error for call_service at pos 1: Error rendering data template: ValueError: Template error: strptime got invalid input ‘2024-03-06T14:07:00-05:00’ when rendering template ‘{%- for item in todovar[‘todo.home_list’][‘items’] %} {% if ‘due’ in item%} {% set delta = as_local(strptime(item[‘due’], “%Y-%m-%d”)) - today_at(“00:00”) %} {% if delta.days < 0 %} Task OVERDUE by {{ (delta.days | abs) }} day(s) {{ ‘\n\n’ -}} {{ item[‘summary’] }} {% elif delta.days == 0 %} Task due TODAY {{ ‘\n\n’ -}} {{ item[‘summary’] }} {% endif %} {% endif %} {% endfor -%}’ but no default was specified
  • Home To-Do Notification: Error executing script. Error for if at pos 2: Error rendering data template: ValueError: Template error: strptime got invalid input ‘2024-03-06T14:07:00-05:00’ when rendering template ‘{%- for item in todovar[‘todo.home_list’][‘items’] %} {% if ‘due’ in item%} {% set delta = as_local(strptime(item[‘due’], “%Y-%m-%d”)) - today_at(“00:00”) %} {% if delta.days < 0 %} Task OVERDUE by {{ (delta.days | abs) }} day(s) {{ ‘\n\n’ -}} {{ item[‘summary’] }} {% elif delta.days == 0 %} Task due TODAY {{ ‘\n\n’ -}} {{ item[‘summary’] }} {% endif %} {% endif %} {% endfor -%}’ but no default was specified

Try removing the due time. May need to update the strptime to include HH MM like this %Y-%m-%dT%H:%M:%S%z otherwise, I didn’t account for that. I can certainly try to, but it’s more than I bargained for given how annoying and limited this code format is to work with…

Fixed!

alias: YOUR's To-Do Notification
description: To-do Notifications
trigger:
  - platform: time
    at: "08:00:00"
  - platform: time
    at: "12:00:00"
  - platform: time
    at: "15:00:00"
condition: []
action:
  - service: todo.get_items
    metadata: {}
    data:
      status: needs_action
    response_variable: todovar
    target:
      entity_id: todo.YOUR_to_do
  - if:
      - condition: template
        value_template: |-
          {%- for item in todovar['todo.YOUR_to_do']['items'] %}
            {% if 'due' in item%}
              {% set timeformat = "%Y-%m-%dT%H:%M:%S%z" if 'T' in item['due'] else  "%Y-%m-%d" %}
              {% set delta = as_local(strptime(item['due'], timeformat)) - today_at("00:00") %} 
              {% if delta.days == 0 %}
                True
                {% break %}
              {% elif delta.days < 0 %}
                True
                {% break %}
              {% endif %}
            {% endif %}
          {% endfor -%}
    then:
      - service: persistent_notification.create
        data:
          notification_id: TASK DUE
          title: A task is due today!
          message: A task is due today, check your to-do!!
        enabled: false
      - service: notify.mobile_app_YOURfone
        data:
          title: A task is due!
          message: |
            {%- for item in todovar['todo.YOUR_to_do']['items'] %}
              {% if 'due' in item%}
                {% set timeformat = "%Y-%m-%dT%H:%M:%S%z" if 'T' in item['due'] else  "%Y-%m-%d" %}
                {% set delta = as_local(strptime(item['due'], timeformat)) - today_at("00:00") %}
                {% if delta.days < 0 %} Task OVERDUE by {{ (delta.days | abs) }} day(s)  {{ '\n\n' -}} {{ item['summary'] }}
                {% elif delta.days == 0 %} Task due TODAY {{ '\n\n' -}} {{ item['summary'] }}
                {% endif %}
              {% endif %}
            {% endfor -%}
          data:
            ttl: 0
            priority: high
            tag: todo_notification
            actions:
              - action: URI
                title: Check your to-do list
                icon: mdi:note
                uri: /todo
mode: single

2 Likes

BOOM! I’m going to try and take logic from another to-do automation so it works on all to-do lists so I don’t have to create an automation for each list. Not sure if you are interested but will keep you posted if you are!

Sure post anything here if you make changes, might help other people looking for answers. I guess you could just create a variable for your lists to pass into the rest of the automation or even just stick all the lists in an area and target that - I’m only really concerned with my main list but can see why that functionality would be appealing.

I’m sort of at a loss on how to do this. Like I said I have another automation that sends me a notification anytime an item is added to any to-do list but it works by using different logic. I attached it below if that helps at all.

trigger:
  - platform: event
    event_type: call_service
    event_data:
      domain: todo
      service: add_item
    variables:
      list_name: >
        {{ state_attr((trigger.event.data.service_data.entity_id)[0],
        'friendly_name') }}
      item: "{{ trigger.event.data.service_data.item | title }}"
      user_name: >
        {% set uid = trigger.event.context.user_id %}

        {% set user = None %}

        {% set matching_users = states.person |
        selectattr('attributes.uid','==', uid) | list %}

        {% set user = matching_users|first if matching_users|length > 0 else
        "System" %}

        {{ user if user == "System" else
        state_attr(user.entity_id,"friendly_name") }}
    context: {}
condition: []

I think that works by monitoring the parent to-do service call, so that would be list agnostic which I don’t think would work here. The notification has to explicitly load the contents of the list into a variable in order to pull out the due dates, so that needs to be specified. You can get the contents of all the lists by creating an area and assigning to-do lists to it. This will give you a nested dictionary with all lists and their content within that area.

Try putting {{ todovar }} in the message output so you can see what it spits out, it’s basically a dictionary within dictionaries. You would basically just need to modify the for loop to account for the extra keys and values. Maybe something like for key, value in todovar.items():.

Sorry this completely fell off my radar. The more I thought about this I think I can survive with just one list and honestly didn’t quite understand your suggestion. If you have time to take a stab at it I would appreciate it but if not I am happy with what you already provided!!

Thank you very much!
I confirm: if there is a time in the task, an error is caused, but if there is only a date, everything is OK, this is not a problem - you can change it. I tweaked it for myself - everything works fine.

I don’t understand what i am doing wrong but it is not working. Automation stops at “if” condition. What can be wrong? Also I have never identified “todovar” variable. Should I create helper for that or not?

Hey, check out my Github for a working version if you’re having problems. The todovar is entirely contained within the YAML configuration. If you’re stopping at the “if” you probably aren’t using a break in the code, I had the same problem, but yeah check my HA repo.

I have updated it to account for times as well, it just needed a split variable for when the time is present. Repo above if you’re interested, but if you figured it out too, great!

I made similar automations, one for when the task is due to the present day and one for tasks that are overdue (due yesterday or before it). The summary of every due task is shown in the notification too. Note that both ignore the time and use only the due date, even if there is a due time.

Notification example on phone:

Notify tasks due today:

alias: Notify tasks due today
description: ""
trigger:
  - platform: time
    at: "19:45:00"
condition: []
action:
  - service: todo.get_items
    metadata: {}
    data:
      status: needs_action
    target:
      entity_id: todo.personal
    response_variable: tasks
  - variables:
      tasks_for_today: >
        {%- set today = now().strftime('%Y-%m-%d')-%}
        {%- set ns =  namespace(result = []) -%}

        {% set tasks_list = tasks['todo.personal']['items'] %}

        {%- if tasks_list is iterable -%}
          {%- for task in tasks_list -%}
            {%- if task['due'].split('T')[0] == today -%}
              {%- set ns.result = ns.result + [task['summary']] -%}
            {%- endif -%}
          {%- endfor -%}
        {%- endif -%}

        {%- if ns.result is iterable -%}
          {%- for item in ns.result %}  
        - {{ item }}
          {%- endfor -%}
        {%- endif -%}
  - condition: template
    value_template: "{{ tasks_for_today != '' }}"
  - service: notify.mobile_android
    metadata: {}
    data:
      title: Tasks due today
      message: "{{ tasks_for_today }}"
    enabled: true
mode: single

(change ‘todo.personal’ to match your to-do list, change ‘notify.mobile_android’ to match your phone, and modify or add trigger times)

Notify overdue tasks:

alias: Notify overdue tasks
description: ""
trigger:
  - platform: time
    at: "09:30:00"
  - platform: time
    at: "19:45:00"
condition: []
action:
  - service: todo.get_items
    metadata: {}
    data:
      status: needs_action
    target:
      entity_id: todo.personal
    response_variable: tasks
  - variables:
      tasks_overdue: |
        {%- set today = as_timestamp(now().strftime('%Y-%m-%d')) -%}
        {%- set ns = namespace(result = []) -%}

        {% set tasks_list = tasks['todo.personal']['items'] %}
        {%- if tasks_list is iterable -%}
          {%- for task in tasks_list -%}
            {%- if as_timestamp( task['due'].split('T')[0] ) < today -%}
              {%- set ns.result = ns.result + [task['summary']] -%}
            {%- endif -%}
          {%- endfor -%}
        {%- endif -%}

        {%- if ns.result is iterable -%}
          {%- for item in ns.result %}  
        - {{ item }}
          {%- endfor -%}
        {%- endif -%}
  - condition: template
    value_template: "{{ tasks_overdue != '' }}"
  - service: notify.mobile_android
    metadata: {}
    data:
      title: Overdue tasks
      message: "{{ tasks_overdue }}"
    enabled: true
mode: single

(also change ‘todo.personal’ to match your to-do list, change ‘notify.mobile_android’ to match your phone, and modify or add trigger times)

Hope this helps someone

3 Likes