This workflow follows the Google Sheets → Slack 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": "Jen Daily Schedule Alert",
"nodes": [
{
"parameters": {
"documentId": {
"__rl": true,
"value": "1B19nehOp-PMweCWRcsyEne5Ih1dztN6Zc4JEp2ZgxsY",
"mode": "id"
},
"sheetName": {
"__rl": true,
"value": "Sheet1",
"mode": "name"
},
"options": {}
},
"id": "cad75dff-5113-4b13-a352-83d579d3967a",
"name": "\u8b80\u53d6\u6392\u73ed\u8868",
"type": "n8n-nodes-base.googleSheets",
"typeVersion": 4,
"position": [
220,
0
],
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"jsCode": "// Jen \u6392\u73ed\u901a\u77e5\u8655\u7406\u908f\u8f2f - \u4fee\u6b63\u6642\u5340\u5224\u65b7\u908f\u8f2f\uff08\u542b\u590f\u4ee4\u6642\uff09\nconst items = $input.all();\n\nconsole.log('=== \u8cc7\u6599\u7d50\u69cb\u5206\u6790 ===');\nconsole.log('items \u9577\u5ea6:', items.length);\n\n// \ud83d\udd50 \u52d5\u614b\u6642\u5340\u504f\u79fb\u51fd\u6578\uff08\u8655\u7406\u590f\u4ee4\u6642\uff09\nfunction getCalgaryOffset(date) {\n const year = date.getFullYear();\n \n // \u8a08\u7b97\u590f\u4ee4\u6642\u958b\u59cb\uff1a3\u6708\u7b2c\u4e8c\u500b\u9031\u65e5\n const marchFirst = new Date(year, 2, 1); // 3\u67081\u65e5\n const firstSunday = 7 - marchFirst.getDay();\n const secondSunday = firstSunday + 7;\n const dstStart = new Date(year, 2, secondSunday, 2, 0, 0); // 3\u6708\u7b2c\u4e8c\u500b\u9031\u65e5 2:00 AM\n \n // \u8a08\u7b97\u590f\u4ee4\u6642\u7d50\u675f\uff1a11\u6708\u7b2c\u4e00\u500b\u9031\u65e5\n const novemberFirst = new Date(year, 10, 1); // 11\u67081\u65e5\n const novFirstSunday = 7 - novemberFirst.getDay();\n const dstEnd = new Date(year, 10, novFirstSunday, 2, 0, 0); // 11\u6708\u7b2c\u4e00\u500b\u9031\u65e5 2:00 AM\n \n // \u5224\u65b7\u662f\u5426\u5728\u590f\u4ee4\u6642\u671f\u9593\n if (date >= dstStart && date < dstEnd) {\n console.log('\ud83c\udf1e \u5361\u52a0\u5229\u590f\u4ee4\u6642\u671f\u9593 (MDT): UTC-6');\n return -6 * 60; // UTC-6 (Mountain Daylight Time)\n } else {\n console.log('\u2744\ufe0f \u5361\u52a0\u5229\u6a19\u6e96\u6642\u671f\u9593 (MST): UTC-7');\n return -7 * 60; // UTC-7 (Mountain Standard Time)\n }\n}\n\n// \u6642\u5340\u8a2d\u7f6e\nconst taipeiOffset = 8 * 60; // UTC+8 (\u5206\u9418)\nconst now = new Date();\nconst calgaryOffset = getCalgaryOffset(now); // \u52d5\u614b\u8a08\u7b97\u5361\u52a0\u5229\u6642\u5340\n\n// \u53d6\u5f97\u7576\u524d\u6642\u9593\nconst taipeiNow = new Date(now.getTime() + (taipeiOffset * 60000));\nconst calgaryNow = new Date(now.getTime() + (calgaryOffset * 60000));\n\nconsole.log('\ud83c\udf0d \u6642\u5340\u8cc7\u8a0a:');\nconsole.log('\u53f0\u5317\u6642\u5340\u504f\u79fb:', taipeiOffset / 60, '\u5c0f\u6642');\nconsole.log('\u5361\u52a0\u5229\u6642\u5340\u504f\u79fb:', calgaryOffset / 60, '\u5c0f\u6642');\n\n// \ud83c\udfaf \u65b0\u7684\u6642\u9593\u5224\u65b7\u908f\u8f2f\nconst taipeiHour = taipeiNow.getHours();\nlet targetDate;\nlet displayDay;\nlet isShowingTomorrow;\n\nif (taipeiHour >= 8 && taipeiHour <= 19) {\n // \u53f0\u5317 08:00-19:59 \u2192 \u986f\u793a\u5361\u52a0\u5229\u300c\u660e\u5929\u300d\u73ed\u8868\n const calgaryTomorrow = new Date(calgaryNow);\n calgaryTomorrow.setDate(calgaryTomorrow.getDate() + 1);\n targetDate = calgaryTomorrow.toISOString().split('T')[0];\n displayDay = '\u660e\u5929';\n isShowingTomorrow = true;\n console.log('\ud83c\udf05 \u53f0\u5317\u767d\u5929\u6642\u6bb5 (08:00-19:59) - \u986f\u793a\u5361\u52a0\u5229\u660e\u5929\u73ed\u8868');\n} else {\n // \u53f0\u5317 20:00-07:59 \u2192 \u986f\u793a\u5361\u52a0\u5229\u300c\u4eca\u5929\u300d\u73ed\u8868\n targetDate = calgaryNow.toISOString().split('T')[0];\n displayDay = '\u4eca\u5929';\n isShowingTomorrow = false;\n console.log('\ud83c\udf19 \u53f0\u5317\u591c\u9593\u6642\u6bb5 (20:00-07:59) - \u986f\u793a\u5361\u52a0\u5229\u4eca\u5929\u73ed\u8868');\n}\n\nconsole.log('\u53f0\u5317\u7576\u524d\u6642\u9593:', taipeiNow.toISOString());\nconsole.log('\u5361\u52a0\u5229\u7576\u524d\u6642\u9593:', calgaryNow.toISOString());\nconsole.log('\u76ee\u6a19\u65e5\u671f:', targetDate);\nconsole.log('\u986f\u793a\u5929\u6578:', displayDay);\nconsole.log('\u53f0\u5317\u5c0f\u6642:', taipeiHour);\n\n// \u6536\u96c6\u6240\u6709\u8cc7\u6599\nconst scheduleData = [];\n\nconsole.log('\u958b\u59cb\u8655\u7406\u6240\u6709 items...');\n\n// \u76f4\u63a5\u8655\u7406\u6bcf\u500b item\uff08\u6bcf\u500b item \u4ee3\u8868\u4e00\u884c\u8cc7\u6599\uff09\nfor (let i = 0; i < items.length; i++) {\n const item = items[i];\n const row = item.json;\n \n console.log(`\u8655\u7406\u7b2c ${i+1} \u500b item:`, JSON.stringify(row, null, 2));\n \n // \u8df3\u904e\u7121\u6548\u8cc7\u6599\n if (!row || !row.Employee || !row.Date) {\n console.log(`\u8df3\u904e\u7b2c ${i+1} \u500b item (\u7121\u54e1\u5de5\u540d\u7a31\u6216\u65e5\u671f)`);\n continue;\n }\n \n const employee = row.Employee;\n const dateStr = row.Date;\n const day = row.Day;\n const shiftTime = row.Shift_Time;\n const status = row.Status;\n const location = row.Manager_Location;\n \n console.log(`\u8655\u7406\u54e1\u5de5: ${employee}, \u65e5\u671f: ${dateStr}, \u73ed\u6b21: ${shiftTime}, \u72c0\u614b: ${status}, \u4f4d\u7f6e: ${location}`);\n \n // \u65e5\u671f\u683c\u5f0f\u8f49\u63db\n let formattedDate = '';\n if (dateStr && dateStr.includes('/')) {\n try {\n const parts = dateStr.split('/');\n if (parts.length === 3) {\n const month = parts[0].padStart(2, '0');\n const day = parts[1].padStart(2, '0');\n const year = parts[2];\n formattedDate = `${year}-${month}-${day}`;\n console.log(`\u65e5\u671f\u8f49\u63db: ${dateStr} \u2192 ${formattedDate}`);\n }\n } catch (e) {\n console.log('\u65e5\u671f\u89e3\u6790\u932f\u8aa4:', dateStr);\n continue;\n }\n }\n \n if (formattedDate && employee) {\n const processedRow = {\n employee: employee.trim(),\n date: formattedDate,\n day: day,\n shiftTime: shiftTime || '',\n status: status || '',\n location: location || ''\n };\n \n scheduleData.push(processedRow);\n console.log(`\u2705 \u6210\u529f\u8655\u7406:`, processedRow);\n }\n}\n\nconsole.log('\u8655\u7406\u5f8c\u7684 scheduleData \u9577\u5ea6:', scheduleData.length);\nconsole.log('\u524d5\u7b46\u8655\u7406\u5f8c\u7684\u8cc7\u6599:', scheduleData.slice(0, 5));\n\n// \u627e\u51fa\u76ee\u6a19\u65e5\u671f\u7684\u73ed\u8868\nconst targetSchedules = scheduleData.filter(item => \n item.date === targetDate\n);\n\nconsole.log(`${displayDay}\u7684\u73ed\u8868\u7b46\u6578:`, targetSchedules.length);\nconsole.log(`${displayDay}\u7684\u6240\u6709\u73ed\u8868:`, targetSchedules);\n\n// \u627e\u51fa Jen \u7684\u73ed\u8868\nconst jenTargetSchedule = targetSchedules.find(item => \n item.employee && item.employee.toLowerCase().includes('jen')\n);\n\n// \u627e\u51fa\u5176\u4ed6\u540c\u4e8b\u7684\u73ed\u8868\nconst othersTargetSchedules = targetSchedules.filter(item => \n item.employee && !item.employee.toLowerCase().includes('jen')\n);\n\nconsole.log(`Jen ${displayDay}\u73ed\u8868:`, jenTargetSchedule);\nconsole.log(`\u5176\u4ed6\u540c\u4e8b${displayDay}\u73ed\u8868\u6578\u91cf:`, othersTargetSchedules.length);\n\n// \u683c\u5f0f\u5316\u901a\u77e5\u8a0a\u606f - \u4fee\u6b63\u4f5c\u7528\u57df\u554f\u984c\nfunction formatScheduleMessage(taipeiNow, calgaryNow, jenSchedule, othersSchedules, displayDay, targetDate, isShowingTomorrow, calgaryOffset) {\n const taipeiDateStr = taipeiNow.toLocaleDateString('zh-TW', {\n year: 'numeric',\n month: 'numeric', \n day: 'numeric',\n weekday: 'short'\n });\n \n const taipeiTimeStr = taipeiNow.toLocaleTimeString('zh-TW', {\n hour: '2-digit',\n minute: '2-digit',\n hour12: false\n });\n \n const calgaryDateStr = calgaryNow.toLocaleDateString('en-CA', {\n year: 'numeric',\n month: 'numeric',\n day: 'numeric', \n weekday: 'short'\n });\n \n const calgaryTimeStr = calgaryNow.toLocaleTimeString('en-CA', {\n hour: '2-digit',\n minute: '2-digit',\n hour12: true\n });\n \n // \u8a08\u7b97\u76ee\u6a19\u65e5\u671f\u7684\u986f\u793a\u683c\u5f0f\n const targetDateObj = new Date(targetDate + 'T12:00:00');\n const targetDateStr = targetDateObj.toLocaleDateString('zh-TW', {\n month: 'numeric',\n day: 'numeric', \n weekday: 'short'\n });\n \n // \u986f\u793a\u6642\u5340\u8cc7\u8a0a\n const timeZoneInfo = calgaryOffset === -6 * 60 ? 'MDT (UTC-6)' : 'MST (UTC-7)';\n \n let message = `\ud83c\udf41 Jen ${displayDay}\u73ed\u8868\u63d0\u9192\\n\\n`;\n message += `\u26a0\ufe0f \u6642\u5340\u63d0\u9192\uff1a\u9019\u662f Jen \u5728\u5361\u52a0\u5229\u7684\u300c${displayDay}\u300d\u73ed\u8868\uff01\\n`;\n message += `\ud83d\udcc5 \u53f0\u5317\u73fe\u5728\uff1a${taipeiDateStr} ${taipeiTimeStr}\\n`;\n message += `\ud83d\udcc5 \u5361\u52a0\u5229\u73fe\u5728\uff1a${calgaryDateStr} ${calgaryTimeStr} (${timeZoneInfo})\\n\\n`;\n \n // Jen \u7684\u73ed\u8868\n if (jenSchedule) {\n message += `\ud83d\udc64 Jen \u7684\u5361\u52a0\u5229${displayDay}\u73ed\u6b21 (${targetDateStr})\uff1a\\n`;\n \n if (jenSchedule.status === 'OFF') {\n message += `\ud83c\udfe0 \u4f11\u5047\\n`;\n } else {\n const timeInfo = jenSchedule.shiftTime;\n if (timeInfo) {\n message += `\u23f0 ${timeInfo} (\u5361\u52a0\u5229\u6642\u9593)\\n`;\n }\n message += `\ud83d\udccd \u72c0\u614b: ${jenSchedule.status}\\n`;\n if (jenSchedule.location) {\n message += `\ud83d\udccd \u4f4d\u7f6e: ${jenSchedule.location}\\n`;\n }\n }\n } else {\n message += `\ud83d\udc64 Jen \u7684\u5361\u52a0\u5229${displayDay}\u73ed\u6b21 (${targetDateStr})\uff1a\\n`;\n message += `\u2753 \u67e5\u7121\u8cc7\u6599\\n`;\n }\n \n message += `\\n\ud83d\udc65 \u540c\u65e5\u5176\u4ed6\u540c\u4e8b\uff1a\\n`;\n \n if (othersSchedules.length > 0) {\n othersSchedules.forEach(schedule => {\n const employeeName = schedule.employee;\n \n // Karen \u7684\u908f\u8f2f\uff1a\u6839\u64da Manager_Location \u986f\u793a\u4f4d\u7f6e\n if (employeeName.toLowerCase().includes('karen')) {\n if (schedule.location) {\n message += `\u2022 ${employeeName}: Manager (${schedule.location})\\n`;\n } else {\n message += `\u2022 ${employeeName}: Manager\\n`;\n }\n } else {\n // \u5176\u4ed6\u54e1\u5de5\u7684\u908f\u8f2f\n if (schedule.status === 'OFF') {\n message += `\u2022 ${employeeName}: \u4f11\u5047\\n`;\n } else {\n const timeInfo = schedule.shiftTime || schedule.status;\n const locationInfo = schedule.location ? ` (${schedule.location})` : '';\n message += `\u2022 ${employeeName}: ${timeInfo}${locationInfo}\\n`;\n }\n }\n });\n } else {\n message += `\u2022 \u67e5\u7121\u5176\u4ed6\u540c\u4e8b\u8cc7\u6599\\n`;\n }\n \n return message;\n}\n\n// \u547c\u53eb\u683c\u5f0f\u5316\u51fd\u5f0f\uff0c\u50b3\u5165\u6240\u9700\u7684\u53c3\u6578\nconst notificationMessage = formatScheduleMessage(\n taipeiNow, \n calgaryNow, \n jenTargetSchedule, \n othersTargetSchedules,\n displayDay,\n targetDate,\n isShowingTomorrow,\n calgaryOffset\n);\n\nconsole.log('\u6700\u7d42\u901a\u77e5\u8a0a\u606f:', notificationMessage);\n\n// \u56de\u50b3\u8655\u7406\u7d50\u679c\nreturn {\n json: {\n message: notificationMessage,\n jenSchedule: jenTargetSchedule,\n othersSchedules: othersTargetSchedules,\n targetDate: targetDate,\n displayDay: displayDay,\n isShowingTomorrow: isShowingTomorrow,\n taipeiTime: taipeiNow.toISOString(),\n calgaryTime: calgaryNow.toISOString(),\n timeZoneInfo: {\n taipeiOffset: taipeiOffset / 60,\n calgaryOffset: calgaryOffset / 60,\n isDST: calgaryOffset === -6 * 60,\n timeZoneDisplay: calgaryOffset === -6 * 60 ? 'MDT (UTC-6)' : 'MST (UTC-7)'\n },\n debugInfo: {\n totalItems: items.length,\n processedDataLength: scheduleData.length,\n targetSchedulesLength: targetSchedules.length,\n jenFound: !!jenTargetSchedule,\n taipeiHour: taipeiHour\n }\n }\n};"
},
"id": "f5dc57ad-22cd-48fc-abea-5ea9c5cbd196",
"name": "\u8655\u7406\u6392\u73ed\u8cc7\u6599",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
440,
0
]
},
{
"parameters": {
"channel": "#n8n-work-schedule",
"text": "={{$json.message}}",
"otherOptions": {},
"attachments": []
},
"id": "bd7cea7e-3893-4e1d-8142-1ee6051f83fc",
"name": "\u767c\u9001 Slack \u901a\u77e5",
"type": "n8n-nodes-base.slack",
"typeVersion": 1,
"position": [
660,
0
],
"credentials": {
"slackApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"rule": {
"interval": [
{
"field": "cronExpression",
"expression": "0 8,20 * * *"
}
]
}
},
"id": "9ad9e047-9354-4488-b891-0fd997553866",
"name": "\u6bcf\u65e5\u65e9\u665a 8:00 \u89f8\u767c",
"type": "n8n-nodes-base.scheduleTrigger",
"typeVersion": 1.1,
"position": [
0,
0
]
}
],
"connections": {
"\u8b80\u53d6\u6392\u73ed\u8868": {
"main": [
[
{
"node": "\u8655\u7406\u6392\u73ed\u8cc7\u6599",
"type": "main",
"index": 0
}
]
]
},
"\u8655\u7406\u6392\u73ed\u8cc7\u6599": {
"main": [
[
{
"node": "\u767c\u9001 Slack \u901a\u77e5",
"type": "main",
"index": 0
}
]
]
},
"\u6bcf\u65e5\u65e9\u665a 8:00 \u89f8\u767c": {
"main": [
[
{
"node": "\u8b80\u53d6\u6392\u73ed\u8868",
"type": "main",
"index": 0
}
]
]
}
},
"active": true,
"settings": {
"executionOrder": "v1"
},
"versionId": "d6d750dc-23d3-4723-9a5e-9773b89132d5",
"meta": {
"templateCredsSetupCompleted": true
},
"id": "IpjZzAXLRwSjcLza",
"tags": []
}
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.
googleSheetsOAuth2ApislackApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
Jen Daily Schedule Alert. Uses googleSheets, slack. Scheduled trigger; 4 nodes.
Source: https://github.com/lettucebo/N8N-Flows/blob/52ab732270953f61117544747dae01a81fdde982/flows/JenDailyScheduleAlert/Jen_Daily_Schedule_Alert.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.
This workflow continuously monitors the Meta Ads Library for new creatives from a specific competitor pages, logs them into Google Sheets, and sends a concise Telegram notification with the number of
Enhance financial oversight with this automated n8n workflow. Triggered every 5 minutes, it fetches real-time bank transactions via an API, enriches and transforms the data, and applies smart logic to
This workflow automates competitive price intelligence using Bright Data's enterprise web scraping API. On a scheduled basis (default: daily at 9 AM), the system loops through configured competitor pr
Ensure your customer SLAs never slip with this n8n automation template. The workflow runs on a schedule, fetching open tickets from Zendesk, calculating SLA time remaining, and sending proactive alert
> n8n, Binance API, Google Sheets, Slack, Telegram, Jira & Email