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": "Expiry + Data Reminder Cron",
"nodes": [
{
"parameters": {
"rule": {
"interval": [
{
"field": "cronExpression",
"expression": "0 * * * *"
}
]
}
},
"id": "wf7-trigger",
"name": "Cron: Every Hour",
"type": "n8n-nodes-base.cron",
"typeVersion": 1,
"position": [
0,
0
]
},
{
"parameters": {
"operation": "select",
"table": "orders",
"columns": "o.order_id, o.tx_ref, o.plan_code, o.channel_origin,\n o.expires_at,\n o.data_total_gb, o.data_remaining_gb,\n pa.platform, pa.platform_user_id, pa.display_name",
"conditions": "o.status = 'fulfilled' AND o.expires_at IS NOT NULL AND o.expires_at BETWEEN NOW() AND NOW() + INTERVAL '7 days'",
"options": {
"join": "INNER JOIN platform_accounts pa ON pa.id = o.platform_account_id"
}
},
"id": "wf7-expiring",
"name": "Find Expiring Orders",
"type": "n8n-nodes-base.postgres",
"typeVersion": 1,
"position": [
200,
-200
]
},
{
"parameters": {
"operation": "select",
"table": "orders",
"columns": "o.order_id, o.tx_ref, o.plan_code, o.channel_origin,\n o.data_total_gb, o.data_remaining_gb,\n pa.platform, pa.platform_user_id, pa.display_name",
"conditions": "o.status = 'fulfilled' AND o.data_total_gb IS NOT NULL AND o.data_remaining_gb IS NOT NULL AND (o.data_total_gb - o.data_remaining_gb) / o.data_total_gb >= 0.8",
"options": {
"join": "INNER JOIN platform_accounts pa ON pa.id = o.platform_account_id"
}
},
"id": "wf7-high-usage",
"name": "Find High Data Usage",
"type": "n8n-nodes-base.postgres",
"typeVersion": 1,
"position": [
200,
200
]
},
{
"parameters": {
"functionCode": "// Merge expiring and high-usage into deduplicated list\nconst inputs = $input.all();\nconst seen = new Set();\nconst combined = [];\n\nfor (const item of inputs) {\n const order = item.json;\n if (!seen.has(order.order_id)) {\n seen.add(order.order_id);\n order.reminder_type = [];\n\n // Expiry warning\n if (order.expires_at) {\n order.reminder_type.push('expiry');\n const daysLeft = Math.ceil((new Date(order.expires_at) - new Date()) / (24*60*60*1000));\n order.message = `\u26a0\ufe0f Your ${order.plan_code || order.channel_origin} proxy expires in ${daysLeft} day${daysLeft === 1 ? '' : 's'}. Reply RENEW to keep access.`;\n }\n\n // Data usage warning\n if (order.data_total_gb && order.data_remaining_gb) {\n const used = parseFloat(order.data_total_gb) - parseFloat(order.data_remaining_gb);\n const pct = Math.round((used / parseFloat(order.data_total_gb)) * 100);\n order.data_pct = pct;\n if (!order.message) {\n order.message = `\ud83d\udcca You've used ${pct}% of your ${order.plan_code || order.channel_origin} data (${used.toFixed(1)}/${order.data_total_gb}GB). Reply UPGRADE for more.`;\n }\n }\n\n combined.push({ json: order });\n }\n}\nreturn combined;"
},
"id": "wf7-merge",
"name": "Merge + Deduplicate",
"type": "n8n-nodes-base.function",
"typeVersion": 1,
"position": [
400,
0
]
},
{
"parameters": {
"message": "{{ $json.message }}\n\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\nOrder: {{ $json.tx_ref || $json.order_id }}\nProduct: {{ $json.plan_code || $json.channel_origin }}\n{{#if $json.expires_at }}Expires: {{ $json.expires_at }}\n{{/if}}{{#if $json.data_pct }}Data: {{ $json.data_pct }}% used\n{{/if}}"
},
"id": "wf7-send",
"name": "Send Reminder",
"type": "n8n-nodes-base.whatsApp",
"typeVersion": 1,
"position": [
600,
0
]
}
],
"connections": {
"Cron: Every Hour": {
"main": [
[
{
"node": "Find Expiring Orders",
"type": "main",
"index": 0
}
],
[
{
"node": "Find High Data Usage",
"type": "main",
"index": 0
}
]
]
},
"Find Expiring Orders": {
"main": [
[
{
"node": "Merge + Deduplicate",
"type": "main",
"index": 0
}
]
]
},
"Find High Data Usage": {
"main": [
[
{
"node": "Merge + Deduplicate",
"type": "main",
"index": 0
}
]
]
},
"Merge + Deduplicate": {
"main": [
[
{
"node": "Send Reminder",
"type": "main",
"index": 0
}
]
]
}
},
"settings": {
"executionOrder": "v1",
"saveManualExecutions": false,
"timezone": "Africa/Lagos"
},
"staticData": null,
"tags": [
"reminder",
"cron",
"customer"
]
}
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
Expiry + Data Reminder Cron. Uses postgres, whatsApp. Scheduled trigger; 5 nodes.
Source: https://github.com/sonnyagent30-beep/bunche/blob/e991131928c348097e3fb411d5caa3b0466900f8/.n8n/workflows/expiry-reminder-cron.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.
Daily Summary. Uses postgres, whatsApp. Scheduled trigger; 12 nodes.
Daily Summary. Uses postgres, whatsApp. Scheduled trigger; 12 nodes.
EduPrime - Daily Management Report. Uses postgres, whatsApp, slack, emailSend. Scheduled trigger; 8 nodes.
EduPrime - Daily Fee Reminders. Uses postgres, whatsApp. Scheduled trigger; 8 nodes.
Trial Reset Daily Midnight. Uses postgres, whatsApp. Scheduled trigger; 6 nodes.