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 →
{
"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.
googleApigoogleCalendarOAuth2Api
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 →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
[TEMPLATE] Full Class -> Calendar Sync. Uses httpRequest, googleCalendar. Scheduled trigger; 24 nodes.
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
🕌 How it works
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
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