Created
July 1, 2026 18:29
-
-
Save iamvaar-dev/0b704a24213725bf83d2f2399d699696 to your computer and use it in GitHub Desktop.
Automated Pet Vacination Reminder
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| { | |
| "nodes": [ | |
| { | |
| "parameters": { | |
| "assignments": { | |
| "assignments": [ | |
| { | |
| "id": "9f5e9804-1607-4a24-98e2-cf2bdcd2b0c4", | |
| "name": "3 days from now", | |
| "value": "={{ DateTime.now().plus({ days: 3 }).toISODate() }}", | |
| "type": "string" | |
| } | |
| ] | |
| }, | |
| "options": {} | |
| }, | |
| "type": "n8n-nodes-base.set", | |
| "typeVersion": 3.4, | |
| "position": [ | |
| 624, | |
| 80 | |
| ], | |
| "id": "ed3f1259-d56b-4fa6-b826-8eeb4af3fda4", | |
| "name": "Edit Fields" | |
| }, | |
| { | |
| "parameters": { | |
| "rule": { | |
| "interval": [ | |
| { | |
| "triggerAtHour": 11 | |
| } | |
| ] | |
| } | |
| }, | |
| "type": "n8n-nodes-base.scheduleTrigger", | |
| "typeVersion": 1.3, | |
| "position": [ | |
| 400, | |
| 80 | |
| ], | |
| "id": "bbd2a7c4-29ae-4ab5-b2da-058dbcc43109", | |
| "name": "Schedule Trigger" | |
| }, | |
| { | |
| "parameters": { | |
| "content": "# Vaccination Stage Logic\n\n| `current_stage` | Meaning | Next due date rule |\n|-------------------|-----------------------------------|----------------------------------------------|\n| `none` | No shots yet | `pet_dob + 49 days` (7 weeks) |\n| `puppy_1` | 1st puppy shot done | `last_vaccination_date + 28 days` |\n| `puppy_2` | 2nd puppy shot done | `last_vaccination_date + 28 days` |\n| `puppy_3` | 3rd puppy shot done | `last_vaccination_date + 1 year` |\n| `adult_1yr` | 1-year booster done | `last_vaccination_date + 1 year` |\n| `adult_booster` | Any later annual booster done | `last_vaccination_date + 1 year` (loops forever) |", | |
| "height": 368, | |
| "width": 752 | |
| }, | |
| "type": "n8n-nodes-base.stickyNote", | |
| "typeVersion": 1, | |
| "position": [ | |
| 880, | |
| -352 | |
| ], | |
| "id": "b63fa563-c45c-4616-ace4-0298fa3f0028", | |
| "name": "Sticky Note" | |
| }, | |
| { | |
| "parameters": { | |
| "conditions": { | |
| "options": { | |
| "caseSensitive": true, | |
| "leftValue": "", | |
| "typeValidation": "strict", | |
| "version": 3 | |
| }, | |
| "conditions": [ | |
| { | |
| "id": "177a50df-b6cf-41d2-b961-1661a63ecdee", | |
| "leftValue": "={{ $json.dnd }}", | |
| "rightValue": false, | |
| "operator": { | |
| "type": "boolean", | |
| "operation": "false", | |
| "singleValue": true | |
| } | |
| } | |
| ], | |
| "combinator": "and" | |
| }, | |
| "options": {} | |
| }, | |
| "type": "n8n-nodes-base.if", | |
| "typeVersion": 2.3, | |
| "position": [ | |
| 1728, | |
| 64 | |
| ], | |
| "id": "565e4272-32b1-43be-a6fa-8e4c19698525", | |
| "name": "If1" | |
| }, | |
| { | |
| "parameters": { | |
| "operation": "getAll", | |
| "tableId": "pet_vaccinations", | |
| "returnAll": true | |
| }, | |
| "type": "n8n-nodes-base.supabase", | |
| "typeVersion": 1, | |
| "position": [ | |
| 832, | |
| 80 | |
| ], | |
| "id": "074c0dea-7ab6-4c32-8983-8b8abfbbe432", | |
| "name": "Get Pet Vacination Records", | |
| "credentials": { | |
| "supabaseApi": { | |
| "id": "APOX6ME1jBnq4U50", | |
| "name": "VENKIBVB" | |
| } | |
| } | |
| }, | |
| { | |
| "parameters": { | |
| "jsCode": "// Get today's date and normalize to midnight for accurate day math\nconst today = new Date();\ntoday.setHours(0, 0, 0, 0);\n\n// Map the raw stage to a template-friendly format\n// This fits perfectly into the phrase: \"needs [stage] vaccine by...\"\nconst stageNames = {\n 'none': 'their 1st puppy',\n 'puppy_1': 'their 2nd puppy',\n 'puppy_2': 'their 3rd puppy',\n 'puppy_3': 'their 1-year adult',\n 'adult_1yr': 'their annual adult',\n 'adult_booster': 'their annual adult'\n};\n\n// Loop over all incoming items from the previous node\nfor (const item of $input.all()) {\n const data = item.json;\n const stage = data.current_stage;\n \n // Assign the readable text to a new property to use in your template\n data.readable_stage = stageNames[stage] || 'their upcoming';\n \n let nextDueDate = null;\n\n // 1. First shot: 7 weeks after birth\n if (stage === 'none' && data.pet_dob) {\n let dob = new Date(data.pet_dob);\n dob.setDate(dob.getDate() + 49); // 7 weeks * 7 days\n nextDueDate = dob;\n } \n // 2. Puppy boosters: 4 weeks after the last shot\n else if (['puppy_1', 'puppy_2'].includes(stage) && data.last_vaccination_date) {\n let lastDate = new Date(data.last_vaccination_date);\n lastDate.setDate(lastDate.getDate() + 28); // 4 weeks * 7 days\n nextDueDate = lastDate;\n } \n // 3. Annual boosters: 1 year after the last shot\n else if (['puppy_3', 'adult_1yr', 'adult_booster'].includes(stage) && data.last_vaccination_date) {\n let lastDate = new Date(data.last_vaccination_date);\n lastDate.setFullYear(lastDate.getFullYear() + 1); // Add 1 year\n nextDueDate = lastDate;\n }\n\n // Set default contact flags and template\n data.should_contact = false;\n data.contact_reason = null;\n data.message_template = null;\n\n if (nextDueDate) {\n // Format the calculated date as YYYY-MM-DD for database consistency\n const yyyy = nextDueDate.getFullYear();\n const mm = String(nextDueDate.getMonth() + 1).padStart(2, '0');\n const dd = String(nextDueDate.getDate()).padStart(2, '0');\n \n data.next_due_date = `${yyyy}-${mm}-${dd}`;\n\n // Normalize nextDueDate to midnight to compare cleanly with 'today'\n const normalizedDue = new Date(yyyy, nextDueDate.getMonth(), nextDueDate.getDate());\n normalizedDue.setHours(0, 0, 0, 0);\n\n // Calculate difference in days between due date and today\n const msPerDay = 1000 * 60 * 60 * 24;\n const diffDays = Math.round((normalizedDue.getTime() - today.getTime()) / msPerDay);\n\n // LOGIC 1: Due in 3 days or less (and not yet passed)\n if (diffDays >= 0 && diffDays <= 3) {\n data.should_contact = true;\n data.contact_reason = diffDays === 0 ? 'due_today' : `due_in_${diffDays}_days`;\n data.message_template = 'whatsapp_single_template'; \n } \n // LOGIC 2: Vaccination date has passed\n else if (diffDays < 0) {\n const daysOverdue = Math.abs(diffDays);\n\n // Contact exactly on the 1-month, 2-month, and 3-month marks\n if (daysOverdue === 30 || daysOverdue === 60 || daysOverdue === 90) {\n data.should_contact = true;\n data.contact_reason = `overdue_${daysOverdue}_days`;\n data.message_template = 'whatsapp_single_template';\n } else if (daysOverdue > 90) {\n data.contact_reason = 'lost_to_follow_up';\n } else {\n data.contact_reason = 'overdue_waiting';\n }\n }\n } else {\n data.next_due_date = null; \n }\n}\n\nreturn $input.all();" | |
| }, | |
| "type": "n8n-nodes-base.code", | |
| "typeVersion": 2, | |
| "position": [ | |
| 1040, | |
| 80 | |
| ], | |
| "id": "faaa0b12-720b-46ff-b54d-f929100d8956", | |
| "name": "Filter Vaccine Eligible Pets" | |
| }, | |
| { | |
| "parameters": { | |
| "conditions": { | |
| "options": { | |
| "caseSensitive": true, | |
| "leftValue": "", | |
| "typeValidation": "strict", | |
| "version": 3 | |
| }, | |
| "conditions": [ | |
| { | |
| "id": "b3e43924-0089-4ec9-86a6-e1f125ac84c5", | |
| "leftValue": "={{ $json.should_contact }}", | |
| "rightValue": false, | |
| "operator": { | |
| "type": "boolean", | |
| "operation": "true", | |
| "singleValue": true | |
| } | |
| } | |
| ], | |
| "combinator": "and" | |
| }, | |
| "options": {} | |
| }, | |
| "type": "n8n-nodes-base.if", | |
| "typeVersion": 2.3, | |
| "position": [ | |
| 1248, | |
| 80 | |
| ], | |
| "id": "5231f27f-3a81-4a27-8516-223a14aa5d8d", | |
| "name": "validate the eligibility" | |
| }, | |
| { | |
| "parameters": { | |
| "operation": "getAll", | |
| "tableId": "pet_parents", | |
| "filters": { | |
| "conditions": [ | |
| { | |
| "keyName": "phone", | |
| "condition": "eq", | |
| "keyValue": "={{ $json.phone }}" | |
| } | |
| ] | |
| } | |
| }, | |
| "type": "n8n-nodes-base.supabase", | |
| "typeVersion": 1, | |
| "position": [ | |
| 1520, | |
| 64 | |
| ], | |
| "id": "9bed07d8-1b10-44de-9420-857f49432db1", | |
| "name": "Check pet parent is on DND mode", | |
| "credentials": { | |
| "supabaseApi": { | |
| "id": "APOX6ME1jBnq4U50", | |
| "name": "VENKIBVB" | |
| } | |
| } | |
| }, | |
| { | |
| "parameters": { | |
| "operation": "send", | |
| "phoneNumberId": "1083481144853090", | |
| "recipientPhoneNumber": "={{ $json.phone }}", | |
| "textBody": "=Hi {{ $json.parent_name }}! 🐾 A quick health update for {{ $('Get Pet Vacination Records').item.json.pet_name }} from Blankarray pet clinic.\n\n{{ $('Get Pet Vacination Records').item.json.pet_name }} needs {{ $('Filter Vaccine Eligible Pets').item.json.readable_stage }} vaccine by {{ $('Get Pet Vacination Records').item.json.next_due_date }}.", | |
| "additionalFields": {} | |
| }, | |
| "type": "n8n-nodes-base.whatsApp", | |
| "typeVersion": 1.1, | |
| "position": [ | |
| 1952, | |
| 48 | |
| ], | |
| "id": "421c4e2d-15fa-4f65-83b9-b4bc516f4fa1", | |
| "name": "Send message", | |
| "webhookId": "6f918903-32e8-405d-a968-7f0886a4b699", | |
| "credentials": { | |
| "whatsAppApi": { | |
| "id": "KsIEVWr4quKHUlO7", | |
| "name": "WhatsApp account" | |
| } | |
| } | |
| }, | |
| { | |
| "parameters": { | |
| "updates": [ | |
| "messages" | |
| ], | |
| "options": {} | |
| }, | |
| "type": "n8n-nodes-base.whatsAppTrigger", | |
| "typeVersion": 1, | |
| "position": [ | |
| 400, | |
| 336 | |
| ], | |
| "id": "c9e1373c-0530-4977-9685-d5d8df78b60e", | |
| "name": "WhatsApp Trigger", | |
| "webhookId": "8fba190b-ee4e-42ec-8289-f76c74d85b6b", | |
| "credentials": { | |
| "whatsAppTriggerApi": { | |
| "id": "iXRwfmH8WHEurWCh", | |
| "name": "WhatsApp OAuth account" | |
| } | |
| } | |
| }, | |
| { | |
| "parameters": { | |
| "conditions": { | |
| "options": { | |
| "caseSensitive": false, | |
| "leftValue": "", | |
| "typeValidation": "strict", | |
| "version": 3 | |
| }, | |
| "conditions": [ | |
| { | |
| "id": "650444df-43d5-49f6-b9a3-fb0f636382d9", | |
| "leftValue": "={{ $json.messages[0].text.body }}", | |
| "rightValue": "", | |
| "operator": { | |
| "type": "string", | |
| "operation": "notEmpty", | |
| "singleValue": true | |
| } | |
| }, | |
| { | |
| "id": "8e635e6f-7d76-4487-9a25-288b32f2cbef", | |
| "leftValue": "={{ $json.messages[0].type }}", | |
| "rightValue": "unsupported", | |
| "operator": { | |
| "type": "string", | |
| "operation": "notEquals" | |
| } | |
| }, | |
| { | |
| "id": "4f87efcf-58d6-41ee-be9b-c9d520d37822", | |
| "leftValue": "={{ $json.messages[0].text.body }}", | |
| "rightValue": "STOP", | |
| "operator": { | |
| "type": "string", | |
| "operation": "equals" | |
| } | |
| } | |
| ], | |
| "combinator": "and" | |
| }, | |
| "options": { | |
| "ignoreCase": true | |
| } | |
| }, | |
| "type": "n8n-nodes-base.if", | |
| "typeVersion": 2.3, | |
| "position": [ | |
| 592, | |
| 336 | |
| ], | |
| "id": "b848d80e-6b22-4322-aad5-9e7194ed718f", | |
| "name": "If2" | |
| }, | |
| { | |
| "parameters": { | |
| "operation": "update", | |
| "tableId": "pet_parents", | |
| "filters": { | |
| "conditions": [ | |
| { | |
| "keyName": "phone", | |
| "condition": "eq", | |
| "keyValue": "={{ $('WhatsApp Trigger').item.json.contacts[0].wa_id }}" | |
| } | |
| ] | |
| }, | |
| "fieldsUi": { | |
| "fieldValues": [ | |
| { | |
| "fieldId": "dnd", | |
| "fieldValue": "TRUE" | |
| } | |
| ] | |
| } | |
| }, | |
| "type": "n8n-nodes-base.supabase", | |
| "typeVersion": 1, | |
| "position": [ | |
| 928, | |
| 320 | |
| ], | |
| "id": "9a4403c6-e889-4596-b5d2-7b070eb5d9ac", | |
| "name": "Update a row", | |
| "credentials": { | |
| "supabaseApi": { | |
| "id": "APOX6ME1jBnq4U50", | |
| "name": "VENKIBVB" | |
| } | |
| } | |
| }, | |
| { | |
| "parameters": { | |
| "operation": "send", | |
| "phoneNumberId": "1083481144853090", | |
| "recipientPhoneNumber": "={{ $('WhatsApp Trigger').item.json.contacts[0].wa_id }}", | |
| "textBody": "We succesfully put you on DND mode. So no more marketing messages will be sent. Reply back \"START\" to get your pet vaccine reminders", | |
| "additionalFields": {} | |
| }, | |
| "type": "n8n-nodes-base.whatsApp", | |
| "typeVersion": 1.1, | |
| "position": [ | |
| 1216, | |
| 320 | |
| ], | |
| "id": "016244d4-33ab-4932-9553-f566269418ee", | |
| "name": "Send message1", | |
| "webhookId": "669fef63-1f6e-4046-9f88-9f2e06d6de24", | |
| "credentials": { | |
| "whatsAppApi": { | |
| "id": "KsIEVWr4quKHUlO7", | |
| "name": "WhatsApp account" | |
| } | |
| } | |
| }, | |
| { | |
| "parameters": { | |
| "conditions": { | |
| "options": { | |
| "caseSensitive": false, | |
| "leftValue": "", | |
| "typeValidation": "strict", | |
| "version": 3 | |
| }, | |
| "conditions": [ | |
| { | |
| "id": "88773ca3-a81b-4a99-8696-cc9f9394fe26", | |
| "leftValue": "={{ $json.messages[0].text.body }}", | |
| "rightValue": "start", | |
| "operator": { | |
| "type": "string", | |
| "operation": "equals", | |
| "name": "filter.operator.equals" | |
| } | |
| } | |
| ], | |
| "combinator": "and" | |
| }, | |
| "options": { | |
| "ignoreCase": true | |
| } | |
| }, | |
| "type": "n8n-nodes-base.if", | |
| "typeVersion": 2.3, | |
| "position": [ | |
| 816, | |
| 512 | |
| ], | |
| "id": "60aaa43d-cedd-4199-9159-2768a984d6a6", | |
| "name": "If" | |
| }, | |
| { | |
| "parameters": { | |
| "operation": "update", | |
| "tableId": "pet_parents", | |
| "filters": { | |
| "conditions": [ | |
| { | |
| "keyName": "phone", | |
| "condition": "eq", | |
| "keyValue": "={{ $('WhatsApp Trigger').item.json.contacts[0].wa_id }}" | |
| } | |
| ] | |
| }, | |
| "fieldsUi": { | |
| "fieldValues": [ | |
| { | |
| "fieldId": "dnd", | |
| "fieldValue": "FALSE" | |
| } | |
| ] | |
| } | |
| }, | |
| "type": "n8n-nodes-base.supabase", | |
| "typeVersion": 1, | |
| "position": [ | |
| 1056, | |
| 496 | |
| ], | |
| "id": "0686ec5d-13f3-4aef-82c7-42c9f908e657", | |
| "name": "Update a row1", | |
| "credentials": { | |
| "supabaseApi": { | |
| "id": "APOX6ME1jBnq4U50", | |
| "name": "VENKIBVB" | |
| } | |
| } | |
| }, | |
| { | |
| "parameters": { | |
| "operation": "send", | |
| "phoneNumberId": "1083481144853090", | |
| "recipientPhoneNumber": "={{ $('WhatsApp Trigger').item.json.contacts[0].wa_id }}", | |
| "textBody": "We succesfully put Lifted DND mode. So you will recieve your pet's vaccination reminders from now. Reply back \"STOP\" if this is a mistake", | |
| "additionalFields": {} | |
| }, | |
| "type": "n8n-nodes-base.whatsApp", | |
| "typeVersion": 1.1, | |
| "position": [ | |
| 1312, | |
| 496 | |
| ], | |
| "id": "84641f7d-a6fd-4877-b7e4-475512e82889", | |
| "name": "Send message2", | |
| "webhookId": "669fef63-1f6e-4046-9f88-9f2e06d6de24", | |
| "credentials": { | |
| "whatsAppApi": { | |
| "id": "KsIEVWr4quKHUlO7", | |
| "name": "WhatsApp account" | |
| } | |
| } | |
| } | |
| ], | |
| "connections": { | |
| "Edit Fields": { | |
| "main": [ | |
| [ | |
| { | |
| "node": "Get Pet Vacination Records", | |
| "type": "main", | |
| "index": 0 | |
| } | |
| ] | |
| ] | |
| }, | |
| "Schedule Trigger": { | |
| "main": [ | |
| [ | |
| { | |
| "node": "Edit Fields", | |
| "type": "main", | |
| "index": 0 | |
| } | |
| ] | |
| ] | |
| }, | |
| "If1": { | |
| "main": [ | |
| [ | |
| { | |
| "node": "Send message", | |
| "type": "main", | |
| "index": 0 | |
| } | |
| ] | |
| ] | |
| }, | |
| "Get Pet Vacination Records": { | |
| "main": [ | |
| [ | |
| { | |
| "node": "Filter Vaccine Eligible Pets", | |
| "type": "main", | |
| "index": 0 | |
| } | |
| ] | |
| ] | |
| }, | |
| "Filter Vaccine Eligible Pets": { | |
| "main": [ | |
| [ | |
| { | |
| "node": "validate the eligibility", | |
| "type": "main", | |
| "index": 0 | |
| } | |
| ] | |
| ] | |
| }, | |
| "validate the eligibility": { | |
| "main": [ | |
| [ | |
| { | |
| "node": "Check pet parent is on DND mode", | |
| "type": "main", | |
| "index": 0 | |
| } | |
| ] | |
| ] | |
| }, | |
| "Check pet parent is on DND mode": { | |
| "main": [ | |
| [ | |
| { | |
| "node": "If1", | |
| "type": "main", | |
| "index": 0 | |
| } | |
| ] | |
| ] | |
| }, | |
| "WhatsApp Trigger": { | |
| "main": [ | |
| [ | |
| { | |
| "node": "If2", | |
| "type": "main", | |
| "index": 0 | |
| } | |
| ] | |
| ] | |
| }, | |
| "If2": { | |
| "main": [ | |
| [ | |
| { | |
| "node": "Update a row", | |
| "type": "main", | |
| "index": 0 | |
| } | |
| ], | |
| [ | |
| { | |
| "node": "If", | |
| "type": "main", | |
| "index": 0 | |
| } | |
| ] | |
| ] | |
| }, | |
| "Update a row": { | |
| "main": [ | |
| [ | |
| { | |
| "node": "Send message1", | |
| "type": "main", | |
| "index": 0 | |
| } | |
| ] | |
| ] | |
| }, | |
| "If": { | |
| "main": [ | |
| [ | |
| { | |
| "node": "Update a row1", | |
| "type": "main", | |
| "index": 0 | |
| } | |
| ] | |
| ] | |
| }, | |
| "Update a row1": { | |
| "main": [ | |
| [ | |
| { | |
| "node": "Send message2", | |
| "type": "main", | |
| "index": 0 | |
| } | |
| ] | |
| ] | |
| } | |
| }, | |
| "pinData": {}, | |
| "meta": { | |
| "templateCredsSetupCompleted": true, | |
| "instanceId": "93f396852104089b8670e7494b0f3668b420464668ae4a8c1d6b4b5799f8e3ef" | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment