n8n - Extending task managers to remind

/images/n8n todoist reminders example

Let’s get nagging

Todoist is a great all-round task manager and offers reminders and notifications to keep me productive. I want a bit more granular control over what reminders and how often they are sent.

To extend existing functionality, I have created a couple of n8n workflows to pick out important tasks with a due date and then harass me through push notifications until they are done. I have also created a workflow to create recurring tasks on certain days, which would also get picked up by your reminder workflows.

n8n offers nodes for Todoist, cron jobs and various push services. A good example is if I wanted to receive notifications every hour for tasks due today.

  1. A cron node runs every hour (0 */1 * * *)
  2. Get my todoist tasks and use a filter to return tasks due today or have a high-priority
  3. Send your notifications to a push service or store your tasks somewhere for later use

The result is that either my phone or smartwatch will buzz once an hour to remind me to do something. A little nag helps me get stuff done in case it’s slipped my mind.

Setting habits

/images/SScreenshot_20220828_223045.png As mentioned earlier, recurring tasks could take things a step further.

For example, if I wanted to keep on top of certain household chores or run errands, I can use cron nodes to create Todoist tasks that were due to be completed on certain days or times. The reminder workflow example below will then pick these tasks and then nag me.

Show me the code

In the example I have below, two things are going on here. I like to keep Pushbullet clean because a lot is coming through, so I have a bit of cleanup.

We also have Todoist pulling the current tasks and then pushing them through Pushbullet. These two parts of the workflow are separated but are not dependent on each other

You can copy and paste this JSON into your n8n instance and it should pull everything into whatever workspace you have open.

All you will need to do is get the Todoist API token and set up any other notification service you want to use.

{
  "name": "Todoist overdue reminders - example",
  "nodes": [
    {
      "parameters": {},
      "name": "Start",
      "type": "n8n-nodes-base.start",
      "typeVersion": 1,
      "position": [
        -480,
        520
      ],
      "id": "5e8e913f-7cc5-4b2e-b05a-4b73db4dd429"
    },
    {
      "parameters": {
        "title": "Overdue task",
        "body": "= task is overdue!"
      },
      "name": "Pushbullet",
      "type": "n8n-nodes-base.pushbullet",
      "typeVersion": 1,
      "position": [
        920,
        200
      ],
      "id": "e1e6b697-d2f4-45e3-971a-70545b91b961",
      "credentials": {
        "pushbulletOAuth2Api": {
          "id": "11",
          "name": "Pushbullet account"
        }
      }
    },
    {
      "parameters": {
        "operation": "getAll",
        "filters": {
          "active": false
        }
      },
      "name": "Get all active",
      "type": "n8n-nodes-base.pushbullet",
      "typeVersion": 1,
      "position": [
        -20,
        120
      ],
      "alwaysOutputData": true,
      "id": "0ce9ffe5-8b9f-4662-859a-b93ce60a394c",
      "credentials": {
        "pushbulletOAuth2Api": {
          "id": "11",
          "name": "Pushbullet account"
        }
      },
      "continueOnFail": true
    },
    {
      "parameters": {
        "operation": "delete",
        "pushId": "="
      },
      "name": "Cleanup",
      "type": "n8n-nodes-base.pushbullet",
      "typeVersion": 1,
      "position": [
        220,
        120
      ],
      "id": "463c3712-ae65-419c-945a-c1f1d4ea6ef8",
      "credentials": {
        "pushbulletOAuth2Api": {
          "id": "11",
          "name": "Pushbullet account"
        }
      },
      "continueOnFail": true
    },
    {
      "parameters": {
        "operation": "getAll",
        "returnAll": true,
        "filters": {
          "filter": "today | p1 | p2 | p3"
        }
      },
      "id": "2be29af8-36e1-4895-8158-6f703b8f0ca2",
      "name": "High priority",
      "type": "n8n-nodes-base.todoist",
      "typeVersion": 1,
      "position": [
        -20,
        620
      ],
      "alwaysOutputData": false,
      "retryOnFail": false,
      "executeOnce": false,
      "credentials": {
        "todoistApi": {
          "id": "10",
          "name": "Todoist account"
        }
      }
    },
    {
      "parameters": {
        "operation": "getAll",
        "filters": {
          "filter": "@beforework | @afterwork | @weekly"
        }
      },
      "name": "before/after work or weekly",
      "type": "n8n-nodes-base.todoist",
      "typeVersion": 1,
      "position": [
        -20,
        400
      ],
      "id": "39f22e4c-3137-4717-ab79-8a8f5df313f0",
      "credentials": {
        "todoistApi": {
          "id": "10",
          "name": "Todoist account"
        }
      }
    },
    {
      "parameters": {
        "operation": "set",
        "key": "n8n_todoist_overdue",
        "value": "=",
        "keyType": "string"
      },
      "id": "1c044339-d989-473d-a0a1-05f335625ee2",
      "name": "Redis",
      "type": "n8n-nodes-base.redis",
      "typeVersion": 1,
      "position": [
        680,
        620
      ],
      "credentials": {
        "redis": {
          "id": "21",
          "name": "Redis account"
        }
      }
    },
    {
      "parameters": {
        "functionCode": "// Code here will run only once, no matter how many input items there are.\n// More info and help:https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.function/\n// Tip: You can use luxon for dates and $jmespath for querying JSON structures\n\n// Loop over inputs and add a new field called 'myNewField' to the JSON of each one\nvar output = []\nvar output2 = {};\nfor (item of items) {\n  console.log(item.json);\n  taskText = \"P\"+item.json.priority+\" - \"+item.json.content;\n  output.push(taskText);\n}\n\n// You can write logs to the browser console\nconsole.log('Done!');\n\nreturn {\"items\":output.sort()};"
      },
      "id": "afb7476d-ec3a-4281-baa5-df7420710074",
      "name": "format data",
      "type": "n8n-nodes-base.function",
      "typeVersion": 1,
      "position": [
        520,
        620
      ],
      "alwaysOutputData": true,
      "continueOnFail": true
    },
    {
      "parameters": {
        "triggerTimes": {
          "item": [
            {
              "mode": "custom",
              "cronExpression": "*/30 6-9 * * *"
            },
            {
              "mode": "custom",
              "cronExpression": "*/30 16-18 * * *"
            }
          ]
        }
      },
      "name": "Before/After work",
      "type": "n8n-nodes-base.cron",
      "typeVersion": 1,
      "position": [
        -280,
        320
      ],
      "id": "59348d92-4011-4d90-974e-b2e0a903c430"
    },
    {
      "parameters": {
        "triggerTimes": {
          "item": [
            {
              "mode": "custom",
              "cronExpression": "*/20 06-21 * * *"
            }
          ]
        }
      },
      "id": "14063742-12ed-4f92-ae5a-2e62acb9ee6c",
      "name": "06:00-21:00",
      "type": "n8n-nodes-base.cron",
      "typeVersion": 1,
      "position": [
        -280,
        740
      ]
    },
    {
      "parameters": {
        "triggerTimes": {
          "item": [
            {
              "hour": 3
            }
          ]
        }
      },
      "name": "03:00",
      "type": "n8n-nodes-base.cron",
      "typeVersion": 1,
      "position": [
        -280,
        120
      ],
      "id": "3482a783-6203-4392-b937-5ff83e43a58e"
    },
    {
      "parameters": {
        "operation": "sort",
        "sortFieldsUi": {
          "sortField": [
            {
              "fieldName": "due",
              "order": "descending"
            },
            {
              "fieldName": "priority",
              "order": "descending"
            }
          ]
        },
        "options": {}
      },
      "id": "7c7fc7fb-e25b-424e-a98c-7aaaa607e8e9",
      "name": "Item Lists",
      "type": "n8n-nodes-base.itemLists",
      "typeVersion": 1,
      "position": [
        340,
        620
      ]
    },
    {
      "parameters": {
        "conditions": {
          "number": [
            {
              "value1": "=",
              "operation": "equal",
              "value2": 4
            }
          ],
          "boolean": [
            {
              "value1": "=",
              "value2": true
            }
          ]
        },
        "combineOperation": "any"
      },
      "id": "45ccb712-5310-41fb-860e-e48ba46ce942",
      "name": "IF",
      "type": "n8n-nodes-base.if",
      "typeVersion": 1,
      "position": [
        440,
        320
      ]
    },
    {
      "parameters": {
        "operation": "set",
        "key": "n8n_todoist_overdue",
        "value": "=",
        "keyType": "string"
      },
      "id": "64426dd9-0d2f-4cfa-b339-728e2c409d7c",
      "name": "Redis Cloud",
      "type": "n8n-nodes-base.redis",
      "typeVersion": 1,
      "position": [
        680,
        800
      ],
      "credentials": {
        "redis": {
          "id": "24",
          "name": "Redis account 2"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "// Loop over input items and add a new field\n// called 'myNewField' to the JSON of each one\nfunction isOverdue(dte){\n  var today = new Date(new Date().toDateString());\n  \n  var overdue = false;\n  \n  if(dte){\n    var due = new Date(dte.date);\n    overdue = due < today;\n    console.log({od:overdue, dt:dte, du:due, to:today});\n  }\n\n  return overdue;\n}\n\nfor (const item of $input.all()) {\n  item.json.overDue = isOverdue(item.json.due);\n}\n\nreturn $input.all();\n"
      },
      "id": "ce029df5-e6e4-45bf-99b0-b9f376ba490e",
      "name": "Code",
      "type": "n8n-nodes-base.code",
      "typeVersion": 1,
      "position": [
        280,
        460
      ]
    },
    {
      "parameters": {
        "options": {}
      },
      "id": "a7c2f815-f21d-48f8-b617-f7097b6c5a76",
      "name": "Split In Batches",
      "type": "n8n-nodes-base.splitInBatches",
      "typeVersion": 2,
      "position": [
        660,
        300
      ]
    }
  ],
  "pinData": {},
  "connections": {
    "Start": {
      "main": [
        [
          {
            "node": "before/after work or weekly",
            "type": "main",
            "index": 0
          },
          {
            "node": "High priority",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get all active": {
      "main": [
        [
          {
            "node": "Cleanup",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "High priority": {
      "main": [
        [
          {
            "node": "Item Lists",
            "type": "main",
            "index": 0
          },
          {
            "node": "Code",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "before/after work or weekly": {
      "main": [
        [
          {
            "node": "Code",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "format data": {
      "main": [
        [
          {
            "node": "Redis",
            "type": "main",
            "index": 0
          },
          {
            "node": "Redis Cloud",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Before/After work": {
      "main": [
        [
          {
            "node": "before/after work or weekly",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "06:00-21:00": {
      "main": [
        [
          {
            "node": "High priority",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "03:00": {
      "main": [
        [
          {
            "node": "Get all active",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Item Lists": {
      "main": [
        [
          {
            "node": "format data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "IF": {
      "main": [
        [
          {
            "node": "Split In Batches",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code": {
      "main": [
        [
          {
            "node": "IF",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Split In Batches": {
      "main": [
        [
          {
            "node": "Pushbullet",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": true,
  "settings": {},
  "versionId": "3e2b5672-6a3b-414d-9c3e-38f964e7e275",
  "id": "11",
  "meta": {
    "instanceId": "e928dff12afb3577b43fa49117a504cf0c0c54e6a5be0a8cb930d495b64d8a43"
  },
  "tags": [
    {
      "createdAt": "2022-03-09T00:15:21.227Z",
      "updatedAt": "2022-03-09T00:15:21.227Z",
      "id": "1",
      "name": "Todoist"
    }
  ]
}

Because every use case is different, there is some additional setup such as getting your access tokens.

To take things one step further, you don’t have to just have one Todoist node, you could have several with their filters. For example, you could have one that sends notifications in the morning for tasks due today. You could then have certain reminders throughout the day for tasks of a certain priority or label.

A note on affiliations

I’m not affiliated with any of the companies mentioned above. I have made my own choice of using these third-party services.