
Track Upwork Jobs from Vollna RSS with Google Sheets Logging and Slack Alerts
Description
Categories
🤖 AI & Machine Learning
Nodes Used
n8n-nodes-base.coden8n-nodes-base.sortn8n-nodes-base.gmailn8n-nodes-base.slackn8n-nodes-base.filtern8n-nodes-base.aggregaten8n-nodes-base.stickyNoten8n-nodes-base.stickyNoten8n-nodes-base.stickyNoten8n-nodes-base.stickyNote
PriceKostenlos
Views0
Last Updated11/28/2025
workflow.json
{
"meta": {
"instanceId": "f92b2f5dd06caa798e438c28ffbfaefb7248b0e550ddfec39077609f45e21ab8",
"templateCredsSetupCompleted": true
},
"nodes": [
{
"id": "a13633fc-3429-4840-8a86-0f3f552fc4f5",
"name": "Check for foreign characters",
"type": "n8n-nodes-base.filter",
"position": [
240,
-16
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "da8372f6-230d-4bdd-9c59-a6ead041739e",
"operator": {
"type": "string",
"operation": "regex"
},
"leftValue": "={{ $json.title }}",
"rightValue": "=^[\\x00-\\x7F]+$"
}
]
}
},
"typeVersion": 2.2
},
{
"id": "b52a5f5f-fd41-4dab-8b5b-a2e1935ae0e0",
"name": "Title, Budget, Link, Posted, Date, Job Description, Skills, Categories",
"type": "n8n-nodes-base.code",
"position": [
816,
-16
],
"parameters": {
"jsCode": "/**\n * Function node (not Function Item)\n * Processes all incoming items and returns one item per job.\n */\nconst inputs = $input.all();\n\nconst safeMultiDecode = (str, times = 3) => {\n let prev = str, cur = str;\n for (let i = 0; i < times; i++) {\n try { cur = decodeURIComponent(prev); } catch { break; }\n if (cur === prev) break;\n prev = cur;\n }\n return cur;\n};\n\nconst tryExtractParam = (urlStr) => {\n if (typeof urlStr !== \"string\" || !urlStr) return \"\";\n try {\n const u = new URL(urlStr);\n const param = u.searchParams.get(\"url\");\n if (param) return safeMultiDecode(param);\n if (u.hostname.includes(\"upwork.com\")) return urlStr;\n return \"\";\n } catch {\n const part = urlStr.split(\"url=\")[1];\n return part ? safeMultiDecode(part) : \"\";\n }\n};\n\nconst formatPosted = (pubDateStr) => {\n if (typeof pubDateStr !== \"string\" || !pubDateStr.trim()) {\n return { posted: \"\", date: \"\" };\n }\n const pubDate = new Date(pubDateStr);\n if (isNaN(pubDate.getTime())) return { posted: \"\", date: \"\" };\n\n const now = new Date();\n const diffMs = now - pubDate;\n const diffMinutes = Math.floor(diffMs / 60000);\n const diffHours = Math.floor(diffMinutes / 60);\n const diffDays = Math.floor(diffHours / 24);\n\n let posted = \"\";\n if (diffMinutes < 1) posted = \"Posted just now\";\n else if (diffMinutes < 60) posted = `Posted ${diffMinutes} minute${diffMinutes !== 1 ? \"s\" : \"\"} ago`;\n else if (diffHours < 24) posted = `Posted ${diffHours} hour${diffHours !== 1 ? \"s\" : \"\"} ago`;\n else posted = `Posted ${diffDays} day${diffDays !== 1 ? \"s\" : \"\"} ago`;\n\n const formattedDate = pubDate.toLocaleDateString(\"en-GB\", { day: \"numeric\", month: \"long\", year: \"numeric\" });\n return { posted, date: formattedDate };\n};\n\nconst outputs = inputs.map(({ json }) => {\n const out = {};\n\n // 1) Title → { title, budget }\n (() => {\n const input = json?.title;\n if (typeof input !== \"string\") {\n out.title = \"\";\n out.budget = \"\";\n return;\n }\n const regex = /^(.*?)\\s*\\((.*?)\\)$/;\n const match = input.match(regex);\n out.title = match ? match[1].trim() : input;\n out.budget = match ? match[2].trim() : \"\";\n })();\n\n // 2) Link → upwork_link\n out.upwork_link = tryExtractParam(json?.link) || \"\";\n\n // 3) pubDate → { posted, date }\n Object.assign(out, formatPosted(json?.pubDate));\n\n // 4) content → { job_description, skills, categories }\n (() => {\n const raw = json?.content;\n if (typeof raw !== \"string\" || !raw) {\n out.job_description = \"\";\n out.skills = \"\";\n out.categories = \"\";\n return;\n }\n const text = raw\n .replace(/&/g, \"&\")\n .replace(/ /g, \" \")\n .replace(/\\r\\n/g, \"\\n\")\n .replace(/\\r/g, \"\\n\");\n\n const skillsMatch = text.match(/^\\s*Skills:\\s*([^\\n\\r]+)/im);\n const categoriesMatch = text.match(/^\\s*Categories:\\s*([^\\n\\r]+)/im);\n\n out.skills = skillsMatch ? skillsMatch[1].trim() : \"\";\n out.categories = categoriesMatch ? categoriesMatch[1].trim() : \"\";\n\n out.job_description = text\n .replace(/^\\s*Skills:.*$/gim, \"\")\n .replace(/^\\s*Categories:.*$/gim, \"\");\n })();\n\n return { json: out };\n});\n\nreturn outputs;"
},
"typeVersion": 2
},
{
"id": "407c809a-89a6-4727-b6af-f7596077bf66",
"name": "Append row in sheet",
"type": "n8n-nodes-base.googleSheets",
"position": [
1776,
-32
],
"parameters": {
"columns": {
"value": {
"DATE": "={{ $json.date }}",
"TITLE": "={{ $json.title }}",
"BUDGET": "={{ $json.budget }}",
"POSTED": "={{ $json.posted }}",
"SKILLS": "={{ $json.skills }}",
"CATEGORIES": "={{ $json.categories }}",
"JOB DESCRIPTION": "={{ $json.job_description }}",
"UPWORK JOB LINK": "={{ $json.upwork_link }}"
},
"schema": [
{
"id": "TITLE",
"type": "string",
"display": true,
"required": false,
"displayName": "TITLE",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "BUDGET",
"type": "string",
"display": true,
"required": false,
"displayName": "BUDGET",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "POSTED",
"type": "string",
"display": true,
"required": false,
"displayName": "POSTED",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "JOB DESCRIPTION",
"type": "string",
"display": true,
"required": false,
"displayName": "JOB DESCRIPTION",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "DATE",
"type": "string",
"display": true,
"required": false,
"displayName": "DATE",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "SKILLS",
"type": "string",
"display": true,
"required": false,
"displayName": "SKILLS",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "CATEGORIES",
"type": "string",
"display": true,
"required": false,
"displayName": "CATEGORIES",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "UPWORK JOB LINK",
"type": "string",
"display": true,
"required": false,
"displayName": "UPWORK JOB LINK",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "defineBelow",
"matchingColumns": [],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {},
"operation": "append",
"sheetName": {
"__rl": true,
"mode": "list",
"value": "gid=0",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1_66goIMtz3uZpxzhDGvG9a1GZ6t96i7Q72o6wzZIJ2s/edit#gid=0",
"cachedResultName": "Sheet1"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "1_66goIMtz3uZpxzhDGvG9a1GZ6t96i7Q72o6wzZIJ2s",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1_66goIMtz3uZpxzhDGvG9a1GZ6t96i7Q72o6wzZIJ2s/edit?usp=drivesdk",
"cachedResultName": "Upwork Jobs Automation"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"id": "jOa9HPz3eugURQ5I",
"name": "Google Sheets account"
}
},
"typeVersion": 4.7
},
{
"id": "eff67652-6bd6-4ccc-8fb6-0b1a443b40cf",
"name": "Schedule Trigger",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
-304,
-16
],
"parameters": {
"rule": {
"interval": [
{
"field": "minutes",
"minutesInterval": 3
}
]
}
},
"typeVersion": 1.2
},
{
"id": "2cb9b9fc-0221-4039-8e32-6fb1d45c6d11",
"name": "RSS Read",
"type": "n8n-nodes-base.rssFeedRead",
"position": [
-48,
-16
],
"parameters": {
"url": "https://www.vollna.com/rss/rftHMpSQCGeEfr2Zwjzb",
"options": {
"customFields": ""
}
},
"typeVersion": 1.2
},
{
"id": "9e4f6014-dce7-4923-9cd3-b1655f1ac925",
"name": "Sort",
"type": "n8n-nodes-base.sort",
"position": [
512,
-16
],
"parameters": {
"options": {},
"sortFieldsUi": {
"sortField": [
{
"order": "descending",
"fieldName": "pubDate"
}
]
}
},
"typeVersion": 1
},
{
"id": "c6d4316a-f13f-464d-b3d7-7315f50b1a28",
"name": "Get row(s) in sheet",
"type": "n8n-nodes-base.googleSheets",
"position": [
1088,
144
],
"parameters": {
"options": {},
"filtersUI": {
"values": [
{
"lookupValue": "={{ $json.upwork_link }}",
"lookupColumn": "UPWORK JOB LINK"
}
]
},
"sheetName": {
"__rl": true,
"mode": "list",
"value": "gid=0",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1_66goIMtz3uZpxzhDGvG9a1GZ6t96i7Q72o6wzZIJ2s/edit#gid=0",
"cachedResultName": "Sheet1"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "1_66goIMtz3uZpxzhDGvG9a1GZ6t96i7Q72o6wzZIJ2s",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1_66goIMtz3uZpxzhDGvG9a1GZ6t96i7Q72o6wzZIJ2s/edit?usp=drivesdk",
"cachedResultName": "Upwork Jobs Automation"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"id": "jOa9HPz3eugURQ5I",
"name": "Google Sheets account"
}
},
"typeVersion": 4.7
},
{
"id": "5925e47c-7174-4811-acbf-5aa7499922bc",
"name": "Compare Datasets",
"type": "n8n-nodes-base.compareDatasets",
"position": [
1360,
-16
],
"parameters": {
"options": {},
"mergeByFields": {
"values": [
{
"field1": "upwork_link",
"field2": "UPWORK JOB LINK"
}
]
}
},
"typeVersion": 2.3
},
{
"id": "dd503da0-f241-42e2-a8dc-14794fc73bef",
"name": "Send a message",
"type": "n8n-nodes-base.slack",
"position": [
2112,
-32
],
"webhookId": "f6a937ae-0374-4164-a4f2-2706b006c013",
"parameters": {
"text": "=New Job Alert :fire: :fire:\n\n{{ $json.TITLE }} - {{ $json.BUDGET }}\n\n{{ $json.POSTED }}\n\n{{ $json['UPWORK JOB LINK'] }}",
"select": "channel",
"channelId": {
"__rl": true,
"mode": "list",
"value": "C09DV2HQ2M9",
"cachedResultName": "n8n-jobs"
},
"otherOptions": {
"includeLinkToWorkflow": false
},
"authentication": "oAuth2"
},
"credentials": {
"slackOAuth2Api": {
"id": "5KZwdKZ1j1iyAkZV",
"name": "Slack account"
}
},
"typeVersion": 2.3
},
{
"id": "7dc37ecc-6fe2-4fa3-8c54-0b1a76221a4d",
"name": "Send a message1",
"type": "n8n-nodes-base.gmail",
"position": [
2672,
-32
],
"webhookId": "41128593-6e44-40aa-bf1a-60f783744beb",
"parameters": {
"sendTo": "[email protected]",
"message": "✅ A message was just sent to #n8n-jobs!",
"options": {
"appendAttribution": false
},
"subject": "Slack #n8n",
"emailType": "text"
},
"credentials": {
"gmailOAuth2": {
"id": "tKmhWACh32U5uZX8",
"name": "Gmail account"
}
},
"typeVersion": 2.1
},
{
"id": "86e8692d-42e3-4598-9e59-efecbae62c67",
"name": "Aggregate",
"type": "n8n-nodes-base.aggregate",
"position": [
2368,
-32
],
"parameters": {
"options": {},
"fieldsToAggregate": {
"fieldToAggregate": [
{
"fieldToAggregate": "message"
}
]
}
},
"typeVersion": 1
},
{
"id": "27b4377b-c6b7-410c-a896-9e74b5af9c05",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
-320,
-528
],
"parameters": {
"width": 720,
"height": 448,
"content": "Trigger & Input\n\nLocation: Near the Schedule Trigger and RSS Read nodes\nNote Text:\nTrigger & Input\n\n Runs every 3 minutes\n Fetches new jobs from Vollna RSS feed\n\nData Cleaning & Filtering\n\nLocation: Next to the Check for foreign characters node\nNote Text:\nFilter Jobs\n\n Only allow jobs with English (ASCII) titles\n Prevents non-English/foreign character jobs\n"
},
"typeVersion": 1
},
{
"id": "46cf316d-a0d9-493b-b933-f44925065ab2",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
464,
-528
],
"parameters": {
"color": 5,
"width": 800,
"height": 448,
"content": "Sorting\n\nLocation: Next to the Sort node\nNote Text:\nSort Jobs\n\n Sorts jobs by most recent (descending by pubDate)\n\nData Extraction & Formatting\n\nLocation: Next to the Title, Budget, Link, Posted, Date, Job Description, Skills, Categories node\nNote Text:\nExtract & Format Data\n\n Splits title and budget\n Extracts Upwork link\n Formats posted date\n Extracts job description, skills, and categories\n"
},
"typeVersion": 1
},
{
"id": "b7c8fbe2-09b9-4a8f-9d57-372d4ac69e43",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
1360,
-528
],
"parameters": {
"color": 6,
"width": 720,
"height": 448,
"content": "Duplicate Check\n\nLocation: Between Get row(s) in sheet and Compare Datasets nodes\nNote Text:\nCheck for Duplicates\n\n Looks up job link in Google Sheet\n Compares to avoid duplicate entries\n\nAppend to Sheet\n\nLocation: Next to Append row in sheet node\nNote Text:\nSave New Job\n\n Appends new, unique jobs to Google Sheet\n"
},
"typeVersion": 1
},
{
"id": "dca71453-7f0f-4e52-8862-a29e191372fd",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
2144,
-528
],
"parameters": {
"color": 4,
"width": 704,
"height": 448,
"content": "Notification\n\nLocation: Next to Send a message node\nNote Text:\nSlack Notification\n\n Sends new job alert to #n8n-jobs Slack channel\n\n(Optional) Email Confirmation\n\nLocation: Next to Aggregate and Send a message1 nodes\nNote Text:\nEmail Confirmation\n\n Sends email to confirm Slack message was sent\n"
},
"typeVersion": 1
}
],
"pinData": {},
"connections": {
"Sort": {
"main": [
[
{
"node": "Title, Budget, Link, Posted, Date, Job Description, Skills, Categories",
"type": "main",
"index": 0
}
]
]
},
"RSS Read": {
"main": [
[
{
"node": "Check for foreign characters",
"type": "main",
"index": 0
}
]
]
},
"Aggregate": {
"main": [
[
{
"node": "Send a message1",
"type": "main",
"index": 0
}
]
]
},
"Send a message": {
"main": [
[]
]
},
"Compare Datasets": {
"main": [
[
{
"node": "Append row in sheet",
"type": "main",
"index": 0
}
]
]
},
"Schedule Trigger": {
"main": [
[
{
"node": "RSS Read",
"type": "main",
"index": 0
}
]
]
},
"Append row in sheet": {
"main": [
[
{
"node": "Send a message",
"type": "main",
"index": 0
}
]
]
},
"Get row(s) in sheet": {
"main": [
[
{
"node": "Compare Datasets",
"type": "main",
"index": 1
}
]
]
},
"Check for foreign characters": {
"main": [
[
{
"node": "Sort",
"type": "main",
"index": 0
}
]
]
},
"Title, Budget, Link, Posted, Date, Job Description, Skills, Categories": {
"main": [
[
{
"node": "Get row(s) in sheet",
"type": "main",
"index": 0
},
{
"node": "Compare Datasets",
"type": "main",
"index": 0
}
]
]
}
}
}