AutomationFlowsWeb Scraping › Hourly Reminder Notifier via WhatsApp & Push

Hourly Reminder Notifier via WhatsApp & Push

Original n8n title: Reminder Notifier Wa+push (rest)

Reminder Notifier WA+Push (REST). Uses httpRequest. Scheduled trigger; 7 nodes.

Cron / scheduled trigger★★★★☆ complexity7 nodesHTTP Request
Web Scraping Trigger: Cron / scheduled Nodes: 7 Complexity: ★★★★☆ Added:

The workflow JSON

Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →

Download .json
{
  "name": "Reminder Notifier WA+Push (REST)",
  "nodes": [
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "0 * * * *"
            }
          ]
        }
      },
      "id": "schedule-trigger",
      "name": "Schedule (Setiap Jam)",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.1,
      "position": [
        240,
        300
      ],
      "notes": "Polling hourly. Untuk testing manual, klik 'Execute Workflow'."
    },
    {
      "parameters": {
        "method": "POST",
        "url": "={{ $env.SUPABASE_URL }}/rest/v1/rpc/exec_sql",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "apikey",
              "value": "={{ $env.SUPABASE_SERVICE_ROLE_KEY }}"
            },
            {
              "name": "Authorization",
              "value": "=Bearer {{ $env.SUPABASE_SERVICE_ROLE_KEY }}"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            },
            {
              "name": "Prefer",
              "value": "return=representation"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"query\": \"SELECT r.id, r.user_id, r.title, r.amount, r.category, r.notes, r.due_date, r.remind_days_before, r.recurrence, u.wa_number, u.nama FROM reminders r JOIN users u ON u.id = r.user_id WHERE r.status = 'pending' AND r.notify_via_wa = true AND (r.due_date - (r.remind_days_before * INTERVAL '1 day')) <= NOW() AND r.due_date >= (NOW() - INTERVAL '7 days') AND (r.reminded_at IS NULL OR r.reminded_at < NOW() - INTERVAL '23 hours') ORDER BY r.due_date ASC LIMIT 50\"\n}",
        "options": {}
      },
      "id": "fetch-due-reminders",
      "name": "Ambil Reminders Jatuh Tempo (Supabase)",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        460,
        300
      ],
      "notes": "Catatan: butuh function exec_sql di Supabase ATAU ganti ke node Postgres langsung dengan koneksi DB. Lihat README.\n\nQuery: ambil reminder yang 'pending', perlu notif WA, sudah masuk window remind_days_before, dan belum dinotif dalam 23 jam terakhir."
    },
    {
      "parameters": {
        "fieldToSplitOut": "={{ $json }}",
        "options": {}
      },
      "id": "split-reminders",
      "name": "Split per Reminder",
      "type": "n8n-nodes-base.splitOut",
      "typeVersion": 1,
      "position": [
        680,
        300
      ]
    },
    {
      "parameters": {
        "jsCode": "// Format pesan WhatsApp + push payload per reminder\nconst items = $input.all();\nconst results = [];\n\nfor (const item of items) {\n  const r = item.json;\n  if (!r || !r.wa_number) continue;\n\n  const today = new Date();\n  today.setHours(0, 0, 0, 0);\n  const due = new Date(r.due_date);\n  due.setHours(0, 0, 0, 0);\n  const diffDays = Math.ceil((due - today) / (1000 * 60 * 60 * 24));\n\n  let dueText, pushSubject;\n  if (diffDays < 0) {\n    dueText = `\u26a0\ufe0f Sudah lewat ${Math.abs(diffDays)} hari`;\n    pushSubject = `Sudah lewat ${Math.abs(diffDays)} hari`;\n  } else if (diffDays === 0) {\n    dueText = '\ud83d\udd25 Hari ini!';\n    pushSubject = 'Jatuh tempo hari ini';\n  } else if (diffDays === 1) {\n    dueText = '\ud83d\udccc Besok';\n    pushSubject = 'Jatuh tempo besok';\n  } else {\n    dueText = `\ud83d\udcc5 ${diffDays} hari lagi`;\n    pushSubject = `${diffDays} hari lagi`;\n  }\n\n  const dueDateFormatted = due.toLocaleDateString('id-ID', {\n    day: 'numeric', month: 'long', year: 'numeric'\n  });\n\n  const namaSapaan = r.nama ? r.nama.split(' ')[0] : 'Kak';\n\n  let amountText = '';\n  let amountInline = '';\n  if (r.amount && r.amount > 0) {\n    const fmt = `Rp ${Number(r.amount).toLocaleString('id-ID')}`;\n    amountText = `\\n\ud83d\udcb0 *Estimasi:* ${fmt}`;\n    amountInline = ` (${fmt})`;\n  }\n\n  let categoryText = '';\n  if (r.category) categoryText = `\\n\ud83d\udcc2 *Kategori:* ${r.category}`;\n\n  let notesText = '';\n  if (r.notes) notesText = `\\n\ud83d\udcdd *Catatan:* ${r.notes}`;\n\n  const waMessage = `Halo ${namaSapaan}! \ud83d\udc4b\\n\\n` +\n    `Ada reminder buat kamu:\\n\\n` +\n    `*${r.title}*\\n` +\n    `${dueText} (${dueDateFormatted})` +\n    amountText +\n    categoryText +\n    notesText +\n    `\\n\\nKalau sudah dibayar, tandai di dashboard ya \ud83d\ude0a\\ndashboard.nayyiraai.online/reminders`;\n\n  let waFormatted = String(r.wa_number).replace(/\\D/g, '');\n  if (waFormatted.startsWith('0')) waFormatted = '62' + waFormatted.slice(1);\n  if (waFormatted.startsWith('8')) waFormatted = '62' + waFormatted;\n  const chatId = `${waFormatted}@s.whatsapp.net`;\n\n  const pushTitle = `\ud83d\udd14 ${r.title}`;\n  const pushBody = `${pushSubject}${amountInline}`;\n\n  results.push({\n    json: {\n      reminderId: r.id,\n      userId: r.user_id,\n      chatId: chatId,\n      message: waMessage,\n      title: r.title,\n      waNumber: waFormatted,\n      pushTitle: pushTitle,\n      pushBody: pushBody,\n    }\n  });\n}\n\nreturn results;"
      },
      "id": "format-message",
      "name": "Format Pesan WA + Push",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        900,
        300
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "={{ $env.WAHA_URL }}/api/sendText",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "X-Api-Key",
              "value": "={{ $env.WAHA_API_KEY }}"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"session\": \"account1\",\n  \"chatId\": \"{{ $json.chatId }}\",\n  \"text\": {{ JSON.stringify($json.message) }}\n}",
        "options": {
          "timeout": 30000
        }
      },
      "id": "send-wa",
      "name": "Kirim WhatsApp (WAHA)",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1120,
        300
      ],
      "notes": "Pakai session 'account1' (sesuai tech spec). Ganti kalau session berbeda."
    },
    {
      "parameters": {
        "method": "PATCH",
        "url": "={{ $env.SUPABASE_URL }}/rest/v1/reminders?id=eq.{{ $('Format Pesan WA').item.json.reminderId }}",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "apikey",
              "value": "={{ $env.SUPABASE_SERVICE_ROLE_KEY }}"
            },
            {
              "name": "Authorization",
              "value": "=Bearer {{ $env.SUPABASE_SERVICE_ROLE_KEY }}"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            },
            {
              "name": "Prefer",
              "value": "return=minimal"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"reminded_at\": \"{{ $now.toISOString() }}\"\n}",
        "options": {}
      },
      "id": "update-reminded-at",
      "name": "Update reminded_at (Supabase)",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1340,
        300
      ],
      "notes": "Tandai bahwa reminder sudah dikirim notifikasi-nya, biar tidak spam dalam 23 jam ke depan."
    },
    {
      "parameters": {
        "method": "POST",
        "url": "={{ $env.DASHBOARD_URL }}/api/push/send",
        "authentication": "none",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "=Bearer {{ $env.INTERNAL_API_TOKEN }}"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"user_id\": \"{{ $json.userId }}\",\n  \"title\": {{ JSON.stringify($json.pushTitle) }},\n  \"body\": {{ JSON.stringify($json.pushBody) }},\n  \"url\": \"/reminders\",\n  \"tag\": \"reminder-{{ $json.reminderId }}\"\n}",
        "options": {
          "timeout": 15000,
          "response": {
            "response": {
              "neverError": true
            }
          }
        }
      },
      "id": "send-push",
      "name": "Kirim Push Notification",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1120,
        480
      ],
      "notes": "Kirim browser push. Auth: Bearer INTERNAL_API_TOKEN. neverError=true biar gagal push tidak menggagalkan workflow."
    }
  ],
  "connections": {
    "Schedule (Setiap Jam)": {
      "main": [
        [
          {
            "node": "Ambil Reminders Jatuh Tempo (Supabase)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Ambil Reminders Jatuh Tempo (Supabase)": {
      "main": [
        [
          {
            "node": "Split per Reminder",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Split per Reminder": {
      "main": [
        [
          {
            "node": "Format Pesan WA + Push",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format Pesan WA + Push": {
      "main": [
        [
          {
            "node": "Kirim WhatsApp (WAHA)",
            "type": "main",
            "index": 0
          },
          {
            "node": "Kirim Push Notification",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Kirim WhatsApp (WAHA)": {
      "main": [
        [
          {
            "node": "Update reminded_at (Supabase)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1",
    "saveDataSuccessExecution": "all",
    "saveExecutionProgress": false,
    "saveManualExecutions": true,
    "callerPolicy": "workflowsFromSameOwner"
  },
  "staticData": null,
  "versionId": "",
  "triggerCount": 0,
  "tags": [
    {
      "name": "nayyira"
    },
    {
      "name": "reminder"
    }
  ]
}
Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

About this workflow

Reminder Notifier WA+Push (REST). Uses httpRequest. Scheduled trigger; 7 nodes.

Source: https://github.com/Hydraa57/nayyira-dashboard/blob/5ca4e7e6ceeb0f80a54ccc51ecc54c873ffbdc90/n8n-workflows/reminder_notifier.json — original creator credit. Request a take-down →

More Web Scraping workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

Web Scraping

Birthday Automation - Production (Fixed). Uses stopAndError, httpRequest, emailSend, bannerbear. Scheduled trigger; 86 nodes.

Stop And Error, HTTP Request, Email Send +1
Web Scraping

This template runs two scheduled workflows to govern Microsoft Entra ID (Azure AD) guest accounts by detecting stale users via Microsoft Graph, staging deletions in SharePoint with a 72-hour window, n

Microsoft SharePoint, Microsoft Teams, Microsoft Entra +1
Web Scraping

Jira-Allure-Auto-Qa. Uses httpRequest, jira. Scheduled trigger; 68 nodes.

HTTP Request, Jira
Web Scraping

Spotify-Sync-Surrealdb-V1. Uses httpRequest, n8n-nodes-surrealdb, spotify. Scheduled trigger; 62 nodes.

HTTP Request, N8N Nodes Surrealdb, Spotify
Web Scraping

As n8n instances scale, teams often lose track of sub-workflows—who uses them, where they are referenced, and whether they can be safely updated. This leads to inefficiencies like unnecessary copies o

HTTP Request, n8n, N8N Trigger +1