8
n8n 中文网amn8n.com

将Google Calendar的面试提醒(10分钟前)发送至Slack私信

中级

这是一个HR领域的自动化工作流,包含 10 个节点。主要使用 If, Set, Cron, Slack, Function 等节点。 将Google Calendar的面试提醒(10分钟前)发送至Slack私信

前置要求
  • Slack Bot Token 或 Webhook URL
工作流预览
可视化展示节点连接关系,支持缩放和平移
导出工作流
复制以下 JSON 配置到 n8n 导入,即可使用此工作流
{
  "id": "HrXWJRDS0O4PpRJ5",
  "meta": {
    "instanceId": "14e4c77104722ab186539dfea5182e419aecc83d85963fe13f6de862c875ebfa"
  },
  "name": "将Google Calendar的面试提醒(10分钟前)发送至Slack私信",
  "tags": [],
  "nodes": [
    {
      "id": "171b7858-d94e-409f-b368-7a59a11d09b8",
      "name": "定时触发器",
      "type": "n8n-nodes-base.cron",
      "position": [
        -1600,
        100
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "a569b753-23c3-4226-906e-6bfc5992b4ef",
      "name": "准备提醒",
      "type": "n8n-nodes-base.function",
      "position": [
        -940,
        100
      ],
      "parameters": {
        "functionCode": "const now = new Date();\nconst leadMinutes = parseInt($node['Set: Config'].json['LEAD_MINUTES']);\nconst companyDomain = $node['Set: Config'].json['COMPANY_DOMAIN'];\nconst fileritems = [];\n\n// Process each calendar event\nfor (const item of items) {\n  const eventData = item.json;\n  \n  // Skip if already started\n  if (!eventData.attendees || !eventData.start || !eventData.start.dateTime) {\n    continue;\n  }\n  \n  const startTime = new Date(eventData.start.dateTime);\n  const timeDiff = (startTime - now) / (1000 * 60); // minutes\n  \n  // Only process events starting within lead time\n\n  if (timeDiff > 0 && timeDiff <= leadMinutes) {\n\n    // Extract links from description\n    const description = eventData.description || '';\n    const cvMatch = description.match(/CV:\\s*(https?:\\/\\/[^\\s\\n]+)/i);\n    const notesMatch = description.match(/Notes:\\s*(https?:\\/\\/[^\\s\\n]+)/i);\n    const meetingLinkMatch = description.match(/(https?:\\/\\/[^\\s\\n]*(?:meet\\.google\\.com|zoom\\.us|teams\\.microsoft\\.com)[^\\s\\n]*)/i) || \n                           eventData.hangoutLink ? [eventData.hangoutLink] : null;\n\n    // Process each attendee\n\n    for (const attendee of eventData.attendees) {\n      // Skip organizer, declined attendees, and external emails\n      if (attendee.organizer || \n          attendee.responseStatus === 'declined' ||\n          !attendee.email) {\n        continue;\n      }\n      \n      // Create item for each valid attendee\n      fileritems.push({\n        eventId: eventData.id,\n        eventTitle: eventData.summary || 'Interview',\n        startTime: eventData.start.dateTime,\n        attendeeEmail: attendee.email,\n        attendeeName: attendee.displayName || attendee.email.split('@')[0],\n        cvLink: cvMatch ? cvMatch[1] : null,\n        notesLink: notesMatch ? notesMatch[1] : null,\n        meetingLink: meetingLinkMatch ? meetingLinkMatch[0] : eventData.htmlLink,\n        localStartTime: new Date(eventData.start.dateTime).toLocaleString('en-IN', {\n          timeZone: $node['Set: Config'].json['TIMEZONE'],\n          weekday: 'short',\n          month: 'short',\n          day: 'numeric',\n          hour: '2-digit',\n          minute: '2-digit'\n        }),\n        uniqueKey: `${eventData.id}_${attendee.email}`\n      });\n    }\n  }\n}\n\nreturn fileritems;"
      },
      "typeVersion": 1
    },
    {
      "id": "d3e1408e-d8a4-4536-bb8e-afba9eb29048",
      "name": "检查记录",
      "type": "n8n-nodes-base.function",
      "position": [
        -720,
        100
      ],
      "parameters": {
        "functionCode": "const uniqueKey = $json.uniqueKey;\nconst sentPings = this.getWorkflowStaticData('global').sentPings || {};\n\n// Check if already sent\nconst alreadySent = sentPings[uniqueKey] || false;\n\nreturn {\n  json: {\n    ...$json,\n    alreadySent: alreadySent\n  }\n};"
      },
      "typeVersion": 1
    },
    {
      "id": "979bf30e-5ada-4e3b-a523-2a66d34a174c",
      "name": "若未发送",
      "type": "n8n-nodes-base.if",
      "position": [
        -500,
        100
      ],
      "parameters": {
        "conditions": {
          "boolean": [
            {
              "value1": "={{$json.alreadySent}}"
            }
          ]
        }
      },
      "typeVersion": 1
    },
    {
      "id": "f3bb5c60-887b-4263-8624-82506edf1bdf",
      "name": "找到用户?",
      "type": "n8n-nodes-base.if",
      "position": [
        -280,
        100
      ],
      "parameters": {
        "conditions": {
          "boolean": [
            {
              "value1": "={{$json.ok}}",
              "value2": true
            }
          ]
        }
      },
      "typeVersion": 1
    },
    {
      "id": "2157b372-c81a-4563-9c38-a46bf7b16192",
      "name": "发送私信",
      "type": "n8n-nodes-base.slack",
      "position": [
        -60,
        0
      ],
      "parameters": {
        "text": "🕙 *Interview starting in {{$node['Set: Config'].json['LEAD_MINUTES']}} minutes*\n\n*Candidate:* {{$json.eventTitle.replace('Interview', '').replace(' - ', '').trim() || 'TBD'}}\n*Time:* {{$json.localStartTime}}\n*Role:* {{$json.eventTitle.includes(' - ') ? $json.eventTitle.split(' - ')[1] : 'Interview'}}\n\n📅 <{{$json.meetingLink}}|Join Meeting>{{$json.cvLink ? '\n📋 <' + $json.cvLink + '|View CV>' : ''}}{{$json.notesLink ? '\n📝 <' + $json.notesLink + '|View Notes>' : ''}}",
        "channel": "={{$json.user.id}}",
        "attachments": [],
        "otherOptions": {
          "mrkdwn": true
        },
        "authentication": "oAuth2"
      },
      "typeVersion": 1
    },
    {
      "id": "63b10066-efa9-4801-a817-4a8eab96c0b4",
      "name": "发布至备用频道",
      "type": "n8n-nodes-base.slack",
      "position": [
        -60,
        200
      ],
      "parameters": {
        "text": "=🕙 *Interview Reminder - Unable to DM*\n\n*Interviewer:* <mailto:{{ $('Check Ledger').item.json.attendeeEmail }}|{{ $('Check Ledger').item.json.attendeeName }}>\n*Candidate:* {{$('Check Ledger').item.json.eventTitle.replace('Interview', '').replace(' - ', '').trim() || 'TBD'}}\n*Time:* {{ $('Check Ledger').item.json.startTime }}\n*Role:* {{$('Check Ledger').item.json.eventTitle.includes(' - ') ? $('Check Ledger').item.json.eventTitle.split(' - ')[1] : 'Interview'}}\n\n📅 <{{ $('Check Ledger').item.json.meetingLink }}|Join Meeting>\n{{$('Check Ledger').item.json.cvLink ? '📋 <' + $('Check Ledger').item.json.cvLink + '|View CV>' : ''}}\n{{$('Check Ledger').item.notesLink ? '📝 <' + $('Check Ledger').item.notesLink + '|View Notes>' : ''}}",
        "channel": "={{$node['Set: Config'].json['FALLBACK_CHANNEL']}}",
        "attachments": [],
        "otherOptions": {
          "mrkdwn": true
        },
        "authentication": "oAuth2"
      },
      "typeVersion": 1
    },
    {
      "id": "ddfb1f3a-98f6-4426-bbfd-5afeebd50425",
      "name": "记录已发送提醒",
      "type": "n8n-nodes-base.function",
      "position": [
        160,
        100
      ],
      "parameters": {
        "functionCode": "// Record that ping was sent\nconst uniqueKey = $json.uniqueKey;\nconst staticData = this.getWorkflowStaticData('global');\n\nif (!staticData.sentPings) {\n  staticData.sentPings = {};\n}\n\nstaticData.sentPings[uniqueKey] = {\n  sentAt: new Date().toISOString(),\n  eventId: $json.eventId,\n  attendeeEmail: $json.attendeeEmail\n};\n\nreturn {\n  json: {\n    ...$json,\n    recorded: true\n  }\n};"
      },
      "typeVersion": 1
    },
    {
      "id": "f30eecea-8611-4653-8d75-cda59233246c",
      "name": "设置:配置",
      "type": "n8n-nodes-base.set",
      "position": [
        -1380,
        100
      ],
      "parameters": {
        "values": {
          "string": [
            {
              "name": "CALENDAR_NAME",
              "value": "Interviews"
            },
            {
              "name": "COMPANY_DOMAIN",
              "value": "domain.com"
            },
            {
              "name": "TIMEZONE",
              "value": "Asia/Kolkata"
            },
            {
              "name": "LEAD_MINUTES",
              "value": "10"
            },
            {
              "name": "FALLBACK_CHANNEL",
              "value": "#recruiting-alerts"
            }
          ]
        },
        "options": {}
      },
      "typeVersion": 1
    },
    {
      "id": "af6a6ed8-0370-4f7a-9573-eeec96eb001b",
      "name": "获取多个事件",
      "type": "n8n-nodes-base.googleCalendar",
      "position": [
        -1160,
        100
      ],
      "parameters": {
        "options": {},
        "calendar": {
          "__rl": true,
          "mode": "list",
          "value": "",
          "cachedResultName": ""
        },
        "operation": "getAll"
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "pinData": {},
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "97c7d32f-1785-4a9b-87fd-d3b58ddf3de5",
  "connections": {
    "Send DM": {
      "main": [
        [
          {
            "node": "Record Ping Sent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set: Config": {
      "main": [
        [
          {
            "node": "Get many events",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "User Found?": {
      "main": [
        [
          {
            "node": "Send DM",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Post to Fallback Channel",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check Ledger": {
      "main": [
        [
          {
            "node": "If Not Already Sent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Cron Trigger": {
      "main": [
        [
          {
            "node": "Set: Config",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Pings": {
      "main": [
        [
          {
            "node": "Check Ledger",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get many events": {
      "main": [
        [
          {
            "node": "Prepare Pings",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "If Not Already Sent": {
      "main": [
        [
          {
            "node": "User Found?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Post to Fallback Channel": {
      "main": [
        [
          {
            "node": "Record Ping Sent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
常见问题

如何使用这个工作流?

复制上方的 JSON 配置代码,在您的 n8n 实例中创建新工作流并选择「从 JSON 导入」,粘贴配置后根据需要修改凭证设置即可。

这个工作流适合什么场景?

中级 - 人力资源

需要付费吗?

本工作流完全免费,您可以直接导入使用。但请注意,工作流中使用的第三方服务(如 OpenAI API)可能需要您自行付费。

工作流信息
难度等级
中级
节点数量10
分类1
节点类型6
难度说明

适合有一定经验的用户,包含 6-15 个节点的中等复杂度工作流

作者
WeblineIndia

WeblineIndia

@weblineindia

A Leading Software Engineering, Consulting & Outsourcing Services Company in USA & India serving Clients Globally since 1999.

外部链接
在 n8n.io 查看

分享此工作流