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 →
{
"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"
}
]
}
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 →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
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
This workflow is an improvement of this workflow by Greg Brzezinka.
N8N-Workflow-Github-Manager. Uses github, httpRequest, n8n. Scheduled trigger; 38 nodes.
This workflow uses KlickTipp community nodes, available for self-hosted n8n instances only.
This workflow acts as an automated engagement bot. It sends a Direct Message (DM) with a link or resource to any follower who replies to your post with a specific target keyword.