AutomationFlowsWeb Scraping › Rodopi Dent - SMS Reminder (напомняне)

Rodopi Dent - SMS Reminder (напомняне)

Rodopi Dent - SMS Reminder (Напомняне). Uses googleCalendar, httpRequest. Scheduled trigger; 9 nodes.

Cron / scheduled trigger★★★★☆ complexity9 nodesGoogle CalendarHTTP Request
Web Scraping Trigger: Cron / scheduled Nodes: 9 Complexity: ★★★★☆ Added:

This workflow follows the Google Calendar → HTTP Request recipe pattern — see all workflows that pair these two integrations.

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": "Rodopi Dent - SMS Reminder (\u041d\u0430\u043f\u043e\u043c\u043d\u044f\u043d\u0435)",
  "nodes": [
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "hours",
              "hoursInterval": 24,
              "triggerAtHour": 12
            }
          ]
        }
      },
      "id": "schedule-trigger-001",
      "name": "Daily at 12:00",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        -1200,
        300
      ]
    },
    {
      "parameters": {
        "jsCode": "// Calculate tomorrow's date in Sofia timezone\nconst now = new Date();\nconst sofia = new Intl.DateTimeFormat('en-CA', {\n  timeZone: 'Europe/Sofia',\n  year: 'numeric',\n  month: '2-digit',\n  day: '2-digit'\n});\n\n// Get tomorrow\nconst tomorrow = new Date(now);\ntomorrow.setDate(tomorrow.getDate() + 1);\n\nconst tomorrowStr = sofia.format(tomorrow); // YYYY-MM-DD format\n\n// Also format for SMS display (DD.MM.YYYY)\nconst displayDate = new Intl.DateTimeFormat('bg-BG', {\n  timeZone: 'Europe/Sofia',\n  day: '2-digit',\n  month: '2-digit',\n  year: 'numeric'\n}).format(tomorrow);\n\nreturn [{\n  json: {\n    tomorrowISO: tomorrowStr,\n    tomorrowDisplay: displayDate,\n    timeMin: tomorrowStr + 'T00:00:00+02:00',\n    timeMax: tomorrowStr + 'T23:59:59+02:00'\n  }\n}];"
      },
      "id": "calc-tomorrow-001",
      "name": "Calculate Tomorrow",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -980,
        300
      ]
    },
    {
      "parameters": {
        "operation": "getAll",
        "calendarId": "rodopi.dent@gmail.com",
        "returnAll": true,
        "options": {
          "timeMin": "={{ $json.timeMin }}",
          "timeMax": "={{ $json.timeMax }}",
          "singleEvents": true,
          "orderBy": "startTime"
        }
      },
      "id": "get-calendar-001",
      "name": "Get Tomorrow Events",
      "type": "n8n-nodes-base.googleCalendar",
      "typeVersion": 1.2,
      "position": [
        -760,
        300
      ],
      "credentials": {
        "googleCalendarOAuth2Api": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "// Get events and filter by reminder flag (\ud83d\udd14 \u041d\u0430\u043f\u043e\u043c\u043d\u044f\u043d\u0435: \u0414\u0410)\nconst items = $input.all();\nconst tomorrowDisplay = $('Calculate Tomorrow').first().json.tomorrowDisplay;\n\nconst eventsToRemind = [];\nconst debug = {\n  totalEvents: items.length,\n  skippedCancelled: 0,\n  skippedNoReminderTag: 0,\n  skippedReminderNo: 0,\n  skippedStatusDone: 0,\n  skippedNoPhone: 0,\n  skippedSamples: []\n};\n\nfunction logSkip(reason, event) {\n  if (debug.skippedSamples.length < 3) {\n    debug.skippedSamples.push({\n      reason,\n      summary: event.summary || '',\n      descriptionPreview: (event.description || '').slice(0, 300)\n    });\n  }\n}\n\nfor (const item of items) {\n  const event = item.json;\n  const desc = event.description || '';\n\n  if (event.status === 'cancelled') {\n    debug.skippedCancelled++;\n    continue;\n  }\n\n  // Look for \ud83d\udd14 \u041d\u0430\u043f\u043e\u043c\u043d\u044f\u043d\u0435 tag (use LAST match \u2014 admin edits append to description)\n  const allReminderMatches = [...desc.matchAll(/\ud83d\udd14\\s*\u041d\u0430\u043f\u043e\u043c\u043d\u044f\u043d\u0435:\\s*(\u0414\u0410|\u041d\u0415|YES|NO)/gi)];\n  if (allReminderMatches.length === 0) {\n    debug.skippedNoReminderTag++;\n    logSkip('no-reminder-tag', event);\n    continue;\n  }\n  const flagVal = allReminderMatches[allReminderMatches.length - 1][1].toUpperCase();\n  if (flagVal !== '\u0414\u0410' && flagVal !== 'YES') {\n    debug.skippedReminderNo++;\n    logSkip('reminder-NO', event);\n    continue;\n  }\n\n  if (/\u0421\u0442\u0430\u0442\u0443\u0441:\\s*\u0417\u0430\u0432\u044a\u0440\u0448\u0435\u043d/i.test(desc)) {\n    debug.skippedStatusDone++;\n    continue;\n  }\n\n  // Try labelled phone first, then any Bulgarian-format phone anywhere in description\n  let phone = '';\n  const labelled = desc.match(/(?:\ud83d\udcde\\s*\u0422\u0435\u043b:|\ud83d\udcf1|Phone:|\u0422\u0435\u043b\u0435\u0444\u043e\u043d:)\\s*([+]?[0-9\\s\\-()]{8,20})/i);\n  if (labelled) {\n    phone = labelled[1].replace(/[\\s\\-()]/g, '');\n  } else {\n    const generic = desc.match(/(\\+359\\d{9}|\\+?0?\\d{9,10})/);\n    if (generic) phone = generic[1].replace(/[\\s\\-()]/g, '');\n  }\n  if (!phone) {\n    debug.skippedNoPhone++;\n    logSkip('no-phone', event);\n    continue;\n  }\n\n  // Optional email\n  let email = '';\n  const emailMatch = desc.match(/(?:\u2709\ufe0f\\s*\u0418\u043c\u0435\u0439\u043b:|Email:|\u0418\u043c\u0435\u0439\u043b:)\\s*([^\\s\\n]+@[^\\s\\n]+)/i);\n  if (emailMatch) email = emailMatch[1].trim();\n\n  // Notification channels (default: sms)\n  let channels = ['sms'];\n  const channelsMatch = desc.match(/\ud83d\udcec\\s*\u0418\u0437\u0432\u0435\u0441\u0442\u0438\u044f:\\s*([^\\n]+)/i);\n  if (channelsMatch) {\n    const raw = channelsMatch[1].toLowerCase();\n    channels = [];\n    if (raw.includes('sms')) channels.push('sms');\n    if (raw.includes('\u0438\u043c\u0435\u0439\u043b') || raw.includes('email')) channels.push('email');\n    if (channels.length === 0) channels = ['sms'];\n  }\n\n  // Time in Sofia TZ\n  let time = '';\n  const startISO = event.start?.dateTime || event.start?.date;\n  if (startISO) {\n    const eventDate = new Date(startISO);\n    time = new Intl.DateTimeFormat('en-GB', {\n      timeZone: 'Europe/Sofia',\n      hour: '2-digit',\n      minute: '2-digit',\n      hour12: false\n    }).format(eventDate);\n  }\n\n  const patientName = (event.summary || '').replace(/[\u2705\ud83d\udfe2\u23f3\ud83d\udfe1\ud83d\udd14]/g, '').trim() || '\u041f\u0430\u0446\u0438\u0435\u043d\u0442';\n\n  eventsToRemind.push({\n    eventId: event.id,\n    patientName,\n    phone,\n    email,\n    channels,\n    date: tomorrowDisplay,\n    time,\n    summary: event.summary\n  });\n}\n\nif (eventsToRemind.length === 0) {\n  return [{ json: { noEvents: true, message: '\u041d\u044f\u043c\u0430 \u0447\u0430\u0441\u043e\u0432\u0435 \u0437\u0430 \u043d\u0430\u043f\u043e\u043c\u043d\u044f\u043d\u0435 \u0443\u0442\u0440\u0435', debug } }];\n}\n\nreturn eventsToRemind.map(e => ({ json: e }));"
      },
      "id": "filter-confirmed-001",
      "name": "Filter Events Needing Reminder",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -540,
        300
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "leftValue": "={{ $json.noEvents }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "notEquals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "check-events-001",
      "name": "Has Events?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.1,
      "position": [
        -320,
        300
      ]
    },
    {
      "parameters": {
        "jsCode": "// Prepare SMS for each patient\nconst event = $input.first().json;\n\n// Normalize Bulgarian phone number\nlet phone = event.phone.replace(/\\s+/g, '').replace(/[^0-9+]/g, '');\nif (phone.startsWith('0')) {\n  phone = '+359' + phone.substring(1);\n} else if (!phone.startsWith('+')) {\n  phone = '+359' + phone;\n}\n\n// Create reminder message\nconst message = `\u0420\u043e\u0434\u043e\u043f\u0438 \u0414\u0435\u043d\u0442: \u041d\u0430\u043f\u043e\u043c\u043d\u044f\u043d\u0435 - \u0438\u043c\u0430\u0442\u0435 \u0447\u0430\u0441 \u0443\u0442\u0440\u0435 (${event.date}) \u0432 ${event.time}. \u041e\u0447\u0430\u043a\u0432\u0430\u043c\u0435 \u0432\u0438!`;\n\nreturn [{\n  json: {\n    phone: phone,\n    message: message,\n    patientName: event.patientName,\n    eventId: event.eventId\n  }\n}];"
      },
      "id": "prepare-sms-001",
      "name": "Prepare Reminder SMS",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -100,
        200
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://fcm.googleapis.com/v1/projects/sms-sender-27028/messages:send",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "googleApi",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"message\": {\n    \"token\": \"cdLIn7RwRYuNi7hvpHeUxU:APA91bEiNvE_SCHwrHE-O1nArEOf6Phpltza4HH3vxoRKr8bHlfX9nK_q_4r0xlC9YYvfGxZHNPL5X6uQ44H6dSqbTcwFYQ68ZLYh2G8JtOfpHLoWy7E4aI\",\n    \"data\": {\n      \"phone\": \"{{ $json.phone }}\",\n      \"message\": \"{{ $json.message }}\"\n    }\n  }\n}",
        "options": {
          "allowUnauthorizedCerts": false
        }
      },
      "id": "send-fcm-001",
      "name": "Send FCM to Android",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        120,
        200
      ],
      "credentials": {
        "googleApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "content": "## \ud83d\udcf1 SMS \u041d\u0430\u043f\u043e\u043c\u043d\u044f\u043d\u0435 \u0437\u0430 \u0447\u0430\u0441\u043e\u0432\u0435\n\n**\u0418\u0437\u043f\u044a\u043b\u043d\u044f\u0432\u0430 \u0441\u0435:** \u0412\u0441\u0435\u043a\u0438 \u0434\u0435\u043d \u0432 12:00 (Europe/Sofia)\n\n**\u041b\u043e\u0433\u0438\u043a\u0430 (OPT-IN):**\n1. \u0412\u0437\u0438\u043c\u0430 \u0432\u0441\u0438\u0447\u043a\u0438 \u0441\u044a\u0431\u0438\u0442\u0438\u044f \u043e\u0442 Google Calendar \u0437\u0430 \u0423\u0422\u0420\u0415\u0428\u041d\u0418\u042f \u0434\u0435\u043d\n2. \u26a0\ufe0f \u0418\u0437\u043f\u0440\u0430\u0449\u0430 \u0421\u0410\u041c\u041e \u0430\u043a\u043e \u0432 \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0435\u0442\u043e \u0438\u043c\u0430 \u0442\u0430\u0433 `\ud83d\udd14 \u041d\u0430\u043f\u043e\u043c\u043d\u044f\u043d\u0435: \u0414\u0410`\n   - \u0411\u0435\u0437 \u0442\u0430\u0433 \u2192 \u043f\u0440\u043e\u043f\u0443\u0441\u043a\u0430 (\u0441\u0442\u0430\u0440\u0438/\u0430\u0434\u043c\u0438\u043d \u0447\u0430\u0441\u043e\u0432\u0435 \u0431\u0435\u0437 opt-in)\n   - `\ud83d\udd14 \u041d\u0430\u043f\u043e\u043c\u043d\u044f\u043d\u0435: \u041d\u0415` \u2192 \u043f\u0440\u043e\u043f\u0443\u0441\u043a\u0430\n3. \u0418\u0437\u0432\u043b\u0438\u0447\u0430 \u0442\u0435\u043b\u0435\u0444\u043e\u043d (`\ud83d\udcde \u0422\u0435\u043b:`) \u043e\u0442 \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0435\u0442\u043e\n4. \u0418\u0437\u043f\u0440\u0430\u0449\u0430 SMS \u0447\u0440\u0435\u0437 Android \u0442\u0435\u043b\u0435\u0444\u043e\u043d\u0430 (FCM)\n\n**\u041a\u043e\u0439 \u043f\u043e\u043b\u0443\u0447\u0430\u0432\u0430 \u0442\u0430\u0433 \u0414\u0410:**\n- \u041d\u041e\u0412 \u043f\u0430\u0446\u0438\u0435\u043d\u0442 \u043f\u0440\u0435\u0437 \u0441\u0430\u0439\u0442\u0430 \u2192 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u043d\u043e \u0414\u0410\n- \u0420\u0435\u0434\u043e\u0432\u0435\u043d \u043f\u0430\u0446\u0438\u0435\u043d\u0442 \u043f\u0440\u0435\u0437 \u0441\u0430\u0439\u0442\u0430 \u2192 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u043d\u043e \u041d\u0415\n- \u0427\u0430\u0441 \u0441\u044a\u0437\u0434\u0430\u0434\u0435\u043d \u043e\u0442 \u0430\u0434\u043c\u0438\u043d \u043f\u0430\u043d\u0435\u043b\u0430 \u2192 \u043a\u0430\u043a\u0432\u043e\u0442\u043e \u0435 \u0446\u044a\u043a\u043d\u0430\u043b\u0430 \u0434\u043e\u043a\u0442\u043e\u0440\u043a\u0430\u0442\u0430\n- Edit \u043d\u0430 \u0447\u0430\u0441 \u2192 \u043a\u0430\u043a\u0432\u043e\u0442\u043e \u0435 \u0446\u044a\u043a\u043d\u0430\u0442\u043e \u043f\u0440\u0438 \u0440\u0435\u0434\u0430\u043a\u0446\u0438\u044f\n\n**SMS \u0442\u0435\u043a\u0441\u0442:**\n```\n\u0420\u043e\u0434\u043e\u043f\u0438 \u0414\u0435\u043d\u0442: \u041d\u0430\u043f\u043e\u043c\u043d\u044f\u043d\u0435 - \u0438\u043c\u0430\u0442\u0435 \u0447\u0430\u0441 \u0443\u0442\u0440\u0435 (DD.MM.YYYY) \u0432 HH:MM. \u041e\u0447\u0430\u043a\u0432\u0430\u043c\u0435 \u0432\u0438!\n```\n\n**\u0418\u0437\u0438\u0441\u043a\u0432\u0430\u043d\u0438\u044f:**\n- Google Calendar credentials\n- FCM/Google API credentials (RODOPI_DENT_SMS)\n- Android \u0442\u0435\u043b\u0435\u0444\u043e\u043d\u044a\u0442 \u0442\u0440\u044f\u0431\u0432\u0430 \u0434\u0430 \u0435 \u0441\u0432\u044a\u0440\u0437\u0430\u043d \u0438 \u0441 \u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442",
        "height": 380,
        "width": 360
      },
      "id": "sticky-note-001",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -1200,
        -80
      ]
    },
    {
      "parameters": {},
      "id": "no-op-001",
      "name": "No Events - Skip",
      "type": "n8n-nodes-base.noOp",
      "typeVersion": 1,
      "position": [
        -100,
        400
      ]
    }
  ],
  "connections": {
    "Daily at 12:00": {
      "main": [
        [
          {
            "node": "Calculate Tomorrow",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Calculate Tomorrow": {
      "main": [
        [
          {
            "node": "Get Tomorrow Events",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Tomorrow Events": {
      "main": [
        [
          {
            "node": "Filter Events Needing Reminder",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Has Events?": {
      "main": [
        [
          {
            "node": "Prepare Reminder SMS",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "No Events - Skip",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Reminder SMS": {
      "main": [
        [
          {
            "node": "Send FCM to Android",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filter Events Needing Reminder": {
      "main": [
        [
          {
            "node": "Has Events?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1",
    "timezone": "Europe/Sofia"
  },
  "versionId": "reminder-v1-001",
  "meta": {
    "templateCredsSetupCompleted": false
  },
  "id": "sms-reminder-workflow",
  "tags": [
    {
      "id": "U4AUJ7Gip9wo0aTE",
      "name": "Rodopi Dent"
    }
  ]
}

Credentials you'll need

Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.

Pro

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

About this workflow

Rodopi Dent - SMS Reminder (Напомняне). Uses googleCalendar, httpRequest. Scheduled trigger; 9 nodes.

Source: https://github.com/Georgi-Piskov/RODOPI-DENT/blob/ad5b6829b5d1009f64d2cce3db8393d3c11947c2/n8n-workflows/21-sms-reminder.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

[TEMPLATE] Full Class -> Calendar Sync. Uses httpRequest, googleCalendar. Scheduled trigger; 24 nodes.

HTTP Request, Google Calendar
Web Scraping

Teams that track absences in Everhour and want a shared Google Calendar view for quick planning. Ideal for managers, HR/OPS, and teammates who need instant visibility into approved time off. Pulls app

HTTP Request, Google Calendar
Web Scraping

🕌 How it works

Google Calendar, HTTP Request
Web Scraping

This workflow automates the process of finding local events and adding them directly to your Google Calendar. It eliminates the need for manual event tracking by automatically scraping event informati

HTTP Request, Google Calendar
Web Scraping

Import Forex Factory Calendar events into Google Calendar. Delete past Forex Factory Calendar events from Google Calendar. Get reminders for important economic data releases — especially High Impact n

HTTP Request, Google Calendar