This workflow corresponds to n8n.io template #15412 — we link there as the canonical source.
This workflow follows the Agent → Emailsend 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 →
{
"id": "OPIxHm3OK9wg5pWu",
"meta": {
"templateCredsSetupCompleted": true
},
"name": "Job Crawler",
"tags": [],
"nodes": [
{
"id": "50f2ea4e-1de9-4683-b0b3-553e9c679f1f",
"name": "Score Filter1",
"type": "n8n-nodes-base.if",
"position": [
4320,
240
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"id": "1499f1ac-a7ae-4983-85c7-aa7b8445b2e2",
"operator": {
"type": "number",
"operation": "gte"
},
"leftValue": "={{ $json.output.Score }}",
"rightValue": 60
}
]
},
"looseTypeValidation": true
},
"typeVersion": 2.2
},
{
"id": "b231abbc-0062-439c-8c1a-71a4c2e30dc7",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
3760,
144
],
"parameters": {
"color": 7,
"width": 428,
"height": 472,
"content": "AI model comparing the found job with the applicant from 0-100, (0 does not match, 100 fully match."
},
"typeVersion": 1
},
{
"id": "ba5733f7-9768-4994-8e90-cd978a31e045",
"name": "Sticky Note15",
"type": "n8n-nodes-base.stickyNote",
"position": [
4256,
160
],
"parameters": {
"color": 4,
"width": 260,
"height": 252,
"content": "Filter job that might not be that suitable. Above 60% suitability"
},
"typeVersion": 1
},
{
"id": "2e1e507c-4ffa-4059-bd3a-983854159df3",
"name": "OpenAI Chat Model5",
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
"position": [
3872,
496
],
"parameters": {
"model": {
"__rl": true,
"mode": "list",
"value": "gpt-4.1-mini",
"cachedResultName": "gpt-4.1-mini"
},
"options": {}
},
"credentials": {
"openAiApi": {
"name": "<your credential>"
}
},
"typeVersion": 1.2
},
{
"id": "7bfd6ceb-8fa9-4a4c-b73e-b52c0b984f6e",
"name": "HTML1",
"type": "n8n-nodes-base.html",
"position": [
3792,
-464
],
"parameters": {
"html": "<!DOCTYPE html>\n\n<html>\n<head>\n <meta charset=\"UTF-8\" />\n <title>Jobs, Jobs, Jobs</title>\n<style>\n .job-card {\n font-family: Arial, sans-serif;\n max-width: 800px;\n margin: 24px auto;\n padding: 24px;\n border: 1px solid #e5e7eb;\n border-radius: 16px;\n background: #ffffff;\n color: #111827;\n box-shadow: 0 4px 14px rgba(0,0,0,0.06);\n }\n\n .job-header {\n border-bottom: 1px solid #e5e7eb;\n padding-bottom: 16px;\n margin-bottom: 20px;\n }\n\n .job-title {\n margin: 0 0 8px;\n font-size: 28px;\n line-height: 1.2;\n }\n\n .job-subtitle {\n margin: 0;\n font-size: 15px;\n color: #6b7280;\n }\n\n .score-box {\n display: inline-block;\n margin-top: 14px;\n padding: 10px 14px;\n border-radius: 10px;\n background: #f3f4f6;\n font-weight: bold;\n }\n\n .section {\n margin-top: 24px;\n }\n\n .section h2 {\n margin: 0 0 12px;\n font-size: 18px;\n border-left: 4px solid #d1d5db;\n padding-left: 10px;\n }\n\n .grid {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 12px 20px;\n }\n\n .field {\n padding: 12px;\n background: #f9fafb;\n border-radius: 10px;\n }\n\n .label {\n display: block;\n font-size: 12px;\n text-transform: uppercase;\n letter-spacing: 0.04em;\n color: #6b7280;\n margin-bottom: 4px;\n }\n\n .value {\n font-size: 15px;\n font-weight: 500;\n }\n\n .tags {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n }\n\n .tag {\n display: inline-block;\n padding: 8px 12px;\n border-radius: 999px;\n background: #eef2ff;\n color: #3730a3;\n font-size: 13px;\n font-weight: 500;\n }\n\n .muted {\n color: #6b7280;\n }\n\n .cta {\n margin-top: 28px;\n display: flex;\n gap: 12px;\n flex-wrap: wrap;\n }\n\n .button {\n display: inline-block;\n padding: 12px 18px;\n border-radius: 10px;\n text-decoration: none;\n font-weight: bold;\n background: #111827;\n color: white;\n }\n\n .button-secondary {\n display: inline-block;\n padding: 12px 18px;\n border-radius: 10px;\n text-decoration: none;\n font-weight: bold;\n background: #f3f4f6;\n color: #111827;\n }\n\n .sources {\n margin: 0;\n padding-left: 18px;\n }\n\n .explanation,\n .fit-text {\n line-height: 1.6;\n color: #374151;\n }\n\n @media (max-width: 640px) {\n .grid {\n grid-template-columns: 1fr;\n }\n\n .job-title {\n font-size: 22px;\n }\n }\n</style>\n</head>\n<body>\n\n{{ $json.output }}\n \n</body>\n</html>\n\n<style>\n.container {\n background-color: #ffffff;\n text-align: center;\n padding: 16px;\n border-radius: 8px;\n}\n\nh1 {\n color: #ff6d5a;\n font-size: 24px;\n font-weight: bold;\n padding: 8px;\n}\n\nh2 {\n color: #909399;\n font-size: 18px;\n font-weight: bold;\n padding: 8px;\n}\n</style>\n\n<script>\n</script>"
},
"typeVersion": 1.2
},
{
"id": "44da7c53-9696-4d68-b3b9-b854dc5a2984",
"name": "Code in JavaScript1",
"type": "n8n-nodes-base.code",
"position": [
3312,
-368
],
"parameters": {
"jsCode": "const output = items\n .map(item => item.json.html)\n .filter(html => !!html)\n .join('\\n\\n');\n\nreturn [\n {\n json: {\n output\n }\n }\n];"
},
"typeVersion": 2
},
{
"id": "20b83f90-c877-4e47-b211-15463457374a",
"name": "Rate suitablility",
"type": "@n8n/n8n-nodes-langchain.agent",
"onError": "continueRegularOutput",
"position": [
3872,
240
],
"parameters": {
"text": "=You are a highly skilled headhunter. Calculate a job matching score (0 -> not matching at all 100 -> perfect fit) based on the candidates profile and wishes and the job posting. If it does not fit give an explanation why the candidate does not fit to the job, if it fits give an explanation why the candidate is especially suitable for this position. Keep an extra focus on the candidate deal breakers, which should immediately disqualify the job.\n\njob: \n{{ JSON.stringify($json, null, 2) }}\n\n\napplicant:\n{{ JSON.stringify($('Raw Values').first().json.applicant, null, 2) }}",
"options": {},
"promptType": "define",
"hasOutputParser": true
},
"typeVersion": 2
},
{
"id": "bb54a738-f3ca-4d50-8904-5af5d1714238",
"name": "Firecrawl Execute",
"type": "n8n-nodes-base.manualTrigger",
"position": [
-640,
128
],
"parameters": {},
"typeVersion": 1
},
{
"id": "71aaf084-c3ac-4da6-a363-25cd4459e471",
"name": "WeAreDevelopers Query",
"type": "n8n-nodes-base.code",
"position": [
-96,
-352
],
"parameters": {
"jsCode": "const input = $json;\n\nfunction buildKeywords(data) {\n const parts = [];\n\n if (data.applicant.role) {\n parts.push(data.applicant.role);\n }\n\n if (Array.isArray(data.applicant.stack) && data.applicant.stack.length) {\n parts.push(data.applicant.stack.join(' '));\n }\n\n return parts.join(' ');\n}\n\nfunction encode(value) {\n return encodeURIComponent(value);\n}\n\nfunction buildQuery(params) {\n return Object.entries(params)\n .filter(([_, v]) => v !== '' && v !== null && v !== undefined)\n .map(([k, v]) => `${k}=${encode(v)}`)\n .join('&');\n}\n\nfunction buildUrl(data) {\n const isRemote =\n data.applicant.location &&\n data.applicant.location.toLowerCase().includes('remote');\n\n const params = {\n q: buildKeywords(data),\n remote: isRemote ? 'true' : undefined,\n };\n\n return `https://www.wearedevelopers.com/en/jobs?${buildQuery(params)}`;\n}\n\nconst searchUrl = buildUrl(input);\n\nreturn [\n {\n json: {\n searchUrl,\n mapped: {\n q: buildKeywords(input),\n remote: input.location?.toLowerCase().includes('remote') || false,\n },\n notAppliedDirectlyToUrl: {\n location: input.location ?? null,\n experience_years: input.experience_years ?? null,\n salary_minimum_eur: input.salary_minimum_eur ?? null,\n deal_breakers: input.deal_breakers ?? [],\n nice_to_have: input.nice_to_have ?? [],\n max_listing_age_days: input.max_listing_age_days ?? null,\n },\n },\n },\n];"
},
"typeVersion": 2
},
{
"id": "111b9406-b3da-4987-b95d-f4d341a7ad8a",
"name": "Indeed Query",
"type": "n8n-nodes-base.code",
"position": [
-128,
-32
],
"parameters": {
"jsCode": "const input = $json;\n\nfunction buildKeywords(data) {\n const parts = [];\n\n // role as exact phrase\n if (data.applicant.role) {\n parts.push(`\"${data.applicant.role}\"`);\n }\n\n// stack as OR group\nif (Array.isArray(data.applicant.stack) && data.applicant.stack.length) {\n parts.push(\n data.applicant.stack\n .map(item => `\"${item}\"`)\n .join(' OR ')\n );\n}\n\n return parts.join(' OR ');\n}\n\nfunction mapLocation(data) {\n if (!data.applicant.location) return '';\n return data.applicant.location.toLowerCase().includes('remote')\n ? 'Remote'\n : data.applicant.location;\n}\n\nfunction buildQuery(params) {\n return Object.entries(params)\n .filter(([_, v]) => v !== '' && v !== null && v !== undefined)\n .map(([k, v]) => `${k}=${encodeURIComponent(v)}`)\n .join('&');\n}\n\nfunction buildUrl(data) {\n const params = {\n q: buildKeywords(data),\n l: mapLocation(data),\n fromage: data.applicant.max_listing_age_days || 7,\n from: 'searchOnDesktopSerp',\n };\n\n return `https://de.indeed.com/jobs?${buildQuery(params)}`;\n}\n\nconst searchUrl = buildUrl(input);\n\nreturn [\n {\n json: {\n searchUrl,\n mapped: {\n q: buildKeywords(input),\n l: mapLocation(input),\n fromage: input.max_listing_age_days || 7,\n },\n },\n },\n];"
},
"typeVersion": 2
},
{
"id": "886ffd33-9bfb-4909-9b87-2e2aefdb69ba",
"name": "Edit Fields",
"type": "n8n-nodes-base.set",
"position": [
160,
-32
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "83359463-a0d7-4768-bf9e-1227b05ad4ce",
"name": "data.links",
"type": "array",
"value": "={{ $json.data.links }}"
},
{
"id": "fdc6f3b5-b76c-4ef1-8a08-b10f5c8e91c7",
"name": "",
"type": "string",
"value": ""
}
]
}
},
"typeVersion": 3.4
},
{
"id": "94f4b3ba-22c9-4532-a49f-38ba9cd10090",
"name": "Code in JavaScript2",
"type": "n8n-nodes-base.code",
"onError": "continueRegularOutput",
"position": [
304,
-32
],
"parameters": {
"jsCode": "const links = $input.first().json.data?.links || [];\n\nreturn links .filter(link => typeof link === 'string' && link.includes('rc/clk?')) .map(link => ({ json: { url: link } }));"
},
"typeVersion": 2,
"alwaysOutputData": true
},
{
"id": "066a11d1-d431-47f0-96e2-a0606eabb34d",
"name": "Edit Fields1",
"type": "n8n-nodes-base.set",
"position": [
192,
-352
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "83359463-a0d7-4768-bf9e-1227b05ad4ce",
"name": "data.links",
"type": "array",
"value": "={{ $json.data.links }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "dc297b09-ae68-4f90-81d6-9656b1683eb5",
"name": "Code in JavaScript3",
"type": "n8n-nodes-base.code",
"position": [
336,
-352
],
"parameters": {
"jsCode": "const links = $input.first().json.data.links || [];\n\nreturn links\n .filter(link => link.includes('/jobs/'))\n .map(link => ({ json: { url: link } }));"
},
"typeVersion": 2
},
{
"id": "8de01084-8b3a-44ad-a9f2-5deaf00df877",
"name": "Merge",
"type": "n8n-nodes-base.merge",
"position": [
512,
48
],
"parameters": {},
"typeVersion": 3.2
},
{
"id": "f4c5dcd2-3dc0-4550-8d2b-f2268859021b",
"name": "OpenAI Chat Model6",
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
"position": [
4736,
416
],
"parameters": {
"model": {
"__rl": true,
"mode": "list",
"value": "gpt-4.1-mini",
"cachedResultName": "gpt-4.1-mini"
},
"options": {}
},
"credentials": {
"openAiApi": {
"name": "<your credential>"
}
},
"typeVersion": 1.2
},
{
"id": "f4fc0723-a121-4b34-a40d-f3902a82845a",
"name": "/scrape in Firecrawl1",
"type": "@mendable/n8n-nodes-firecrawl.firecrawlTool",
"position": [
4736,
624
],
"parameters": {
"url": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('URL', ``, 'string') }}",
"operation": "scrape",
"requestOptions": {}
},
"credentials": {
"firecrawlApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "bf14a8f6-94b5-4659-905a-7dbe42f47f74",
"name": "/search in Firecrawl1",
"type": "@mendable/n8n-nodes-firecrawl.firecrawlTool",
"position": [
4880,
560
],
"parameters": {
"query": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Query', ``, 'string') }}",
"resource": "MapSearch",
"operation": "search",
"requestOptions": {}
},
"credentials": {
"firecrawlApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "a1eebd5c-cbc5-414e-ae33-89be67569461",
"name": "Crawl a website and scrape all pages in Firecrawl1",
"type": "@mendable/n8n-nodes-firecrawl.firecrawlTool",
"position": [
5024,
608
],
"parameters": {
"url": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('URL', ``, 'string') }}",
"prompt": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Prompt', ``, 'string') }}",
"resource": "Crawling",
"operation": "crawl",
"crawlOptions": {},
"requestOptions": {}
},
"credentials": {
"firecrawlApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "22236b62-ade6-4d7d-ab16-e7392002e47e",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
-176,
-784
],
"parameters": {
"width": 688,
"height": 384,
"content": "Blocked by Firecrawl :("
},
"typeVersion": 1
},
{
"id": "631cf566-17b0-4cb0-9c18-b7655ed69e2b",
"name": "LinkedIn Query",
"type": "n8n-nodes-base.code",
"position": [
-80,
-576
],
"parameters": {
"jsCode": "const input = $input.first().json.applicant;\n\nconst {\n Keyword: keyword = input.stack,\n Location: location = input.location,\n \"Experience Level\": experienceLevel = input.experience_years,\n Remote: remote = \"Remote\",\n} = input;\n\nconst remoteMap = {\n \"On-Site\": \"1\",\n Remote: \"2\",\n Hybrid: \"3\",\n};\n\nconst jobTypeMap = {\n \"Full-time\": \"F\",\n \"Part-time\": \"P\",\n Contract: \"C\",\n Temporary: \"T\",\n Other: \"O\",\n Internship: \"I\",\n};\n\nconst transformMultiValue = (value, map) => {\n if (!value) return \"\";\n return String(value)\n .split(\",\")\n .map((item) => map[item.trim()])\n .filter(Boolean)\n .join(\",\");\n};\n\nconst query = [\n [\"f_TPR\", \"r86400\"],\n];\n\nconst keywordFormatted = keyword\n .join(\" OR \");\n\nquery.push([\"keywords\", keywordFormatted]);\n\nif (location && location.trim()) {\n query.push([\"location\", location.trim()]);\n}\n\nconst exp = Number(experienceLevel);\nif (Number.isInteger(exp) && exp >= 1 && exp <= 6) {\n query.push([\"f_E\", String(exp)]);\n}\n\nconst remoteValue = transformMultiValue(remote, remoteMap);\nif (remoteValue) {\n query.push([\"f_WT\", remoteValue]);\n}\n\n/**const jobTypeValue = transformMultiValue(jobType, jobTypeMap);\nif (jobTypeValue) {\n query.push([\"f_JT\", jobTypeValue]);\n}**/\n\n/**if (easyApply === true) {\n query.push([\"f_EA\", \"true\"]);\n}**/\n\nconst queryString = query\n .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)\n .join(\"&\");\n\nconst url = `https://www.linkedin.com/jobs/search/?${queryString}`;\n\nreturn { url };"
},
"typeVersion": 2
},
{
"id": "bd321232-5014-4d81-8dc4-0d42872fa5fc",
"name": "Detailled Research",
"type": "@n8n/n8n-nodes-langchain.agent",
"onError": "continueErrorOutput",
"maxTries": 2,
"position": [
4848,
224
],
"parameters": {
"text": "=You are skilled resarch engineer. \n\n### OUTPUT FORMAT\nPublicReview can be a review from kununu.com or trustpilot etc.\n\n### Task 1\nPlease double check the provided information and try to find answers for all entries with \"N/A\". Be very thorough. If company name is not disclosed on the job portal write \"NOT DISCLOSED\", make sure to not return a job portal company or head hunter company when the job is not explicitly working for that company but instead is only conveyed by them.\n\n### Task 2\nFunding infromation might be a recent news regarding the company name.\n\n### Task 3\nIf salary range is not defined so far, try to find this infromation of the same job listing on the company career page. Job has to match exactly. When in doubt return \"N/A\"\n\n### Task 4\nIf name of the company is known, try to find out how many employees are employed in this company.\nSource might be wikipedia or the company homepage.\n\n### WEB SEARCH\nUtilize the firecrawl tool to research positions on pages. Add every visited paged to SourceLinks in the output json.\n\n### JOB DESCRIPTION\n{{ JSON.stringify($('Loop over all jobs').item.json, null, 2) }}",
"options": {
"maxIterations": 20
},
"promptType": "define",
"hasOutputParser": true
},
"retryOnFail": true,
"typeVersion": 2
},
{
"id": "7c75d018-b080-43e4-b191-61ff59a64c26",
"name": "Sticky Note6",
"type": "n8n-nodes-base.stickyNote",
"position": [
-464,
256
],
"parameters": {
"height": 384,
"content": "{\n \"applicant\": {\n \"role\": \"Senior Backend Engineer\",\n \"stack\": [\"Python\", \"Django\", \"PostgreSQL\", \"Redis\", \"AWS\"],\n \"experience_years\": 8,\n \"location\": \"Remote (EU-friendly timezones)\",\n \"salary_minimum_eur\": 90000,\n \"deal_breakers\": [\"on-site only\", \"requires US citizenship\", \"crypto/web3\"],\n \"nice_to_have\": [\"4-day week\", \"open source culture\", \"Series A-C\"],\n \"max_listing_age_days\": 7\n }\n}"
},
"typeVersion": 1
},
{
"id": "d4fd3b0a-c0da-4441-aa05-728c0f412838",
"name": "Sticky Note12",
"type": "n8n-nodes-base.stickyNote",
"position": [
4656,
144
],
"parameters": {
"width": 656,
"height": 640,
"content": "### Let's burn some tokens!"
},
"typeVersion": 1
},
{
"id": "c4d20793-21d0-44df-9455-bf06cdbb5789",
"name": "JSON Suitability Format",
"type": "@n8n/n8n-nodes-langchain.outputParserStructured",
"position": [
4016,
496
],
"parameters": {
"jsonSchemaExample": "{\n\"Score\": 100,\n\"Explanation\": \"\"\n}"
},
"typeVersion": 1.3
},
{
"id": "8f39d73e-b6a2-4df7-b5bd-27127d5d4e7e",
"name": "HTML Success Research",
"type": "n8n-nodes-base.html",
"position": [
5408,
128
],
"parameters": {
"html": "{{\n(() => {\n\nconst job = $(\"Detailled Research\").first().json.output;\n\n const rating = $(\"Rate suitablility\").item.json.output || {};\n\n const stackHtml = Array.isArray(job.Stack) && job.Stack.length\n ? job.Stack.map(skill => `<span class=\"tag\">${skill}</span>`).join(\" - \")\n : `<span class=\"muted\">N/A</span>`;\n\n const sourcesHtml = Array.isArray(job.SourceLinks) && job.SourceLinks.length\n ? job.SourceLinks.map((link, index) =>\n `<li><a href=\"${link}\" target=\"_blank\" rel=\"noopener noreferrer\">Source ${index + 1}</a></li>`\n ).join(\"\")\n : `<li class=\"muted\">No sources available</li>`;\n\n return `\n<div class=\"job-card\">\n <div class=\"job-header\">\n <h1 class=\"job-title\">${job.Role || \"Untitled role\"}</h1>\n <p class=\"job-subtitle\">Company: ${job.Company || \"Company name not specified\"}</p>\n <p class=\"job-subtitle\">Location: ${job.JobLocation || \"Location not specified\"}</p>\n <div class=\"score-box\">Match score: ${rating.Score ?? \"N/A\"}</div>\n </div>\n\n <div class=\"section\">\n <h2>Description</h2>\n <p class=\"explanation\">${job.JobDescription || \"No description available. What a lacy AI...\"}</p>\n </div>\n\n <div class=\"section\">\n <h2>Assessment</h2>\n <p class=\"explanation\">${rating.Explanation || \"No explanation available.\"}</p>\n </div>\n\n <div class=\"section\">\n <h2>Job details</h2>\n <div class=\"grid\">\n <div class=\"field\">\n <span class=\"label\">Salary range: </span>\n <span class=\"value\">${job.SalaryRange || \"N/A\"}</span>\n </div>\n\n <div class=\"field\">\n <span class=\"label\">Posted: </span>\n <span class=\"value\">${job.Posted || \"N/A\"}</span>\n </div>\n\n <div class=\"field\">\n <span class=\"label\">Company size: </span>\n <span class=\"value\">${job.CompanySize || \"N/A\"}</span>\n </div>\n\n <div class=\"field\">\n <span class=\"label\">Funding: </span>\n <span class=\"value\">${job.Funding || \"N/A\"}</span>\n </div>\n\n <div class=\"field\">\n <span class=\"label\">Public review: </span>\n <span class=\"value\">${job.PublicReview || \"N/A\"}</span>\n </div>\n </div>\n </div>\n\n <div class=\"section\">\n <h2>Why it fits</h2>\n <p class=\"fit-text\">${job.WhyItFits || \"No fit summary available.\"}</p>\n </div>\n\n <div class=\"section\">\n <h2>Tech stack</h2>\n <div class=\"tags\">${stackHtml}</div>\n </div>\n\n <div class=\"section\">\n <h2>Sources</h2>\n <ul class=\"sources\">${sourcesHtml}</ul>\n </div>\n\n <div class=\"cta\">\n <a class=\"button\" href=\"${job.Link || \"#\"}\" target=\"_blank\" rel=\"noopener noreferrer\">Apply now</a>\n </div>\n</div>\n`;\n})()\n}}"
},
"typeVersion": 1.2
},
{
"id": "e714ba97-3bc1-49e0-9ded-f92cd134d927",
"name": "Sticky Note16",
"type": "n8n-nodes-base.stickyNote",
"position": [
3232,
-544
],
"parameters": {
"color": 4,
"width": 1040,
"height": 480,
"content": "## Pling! You got mail!"
},
"typeVersion": 1
},
{
"id": "814c22f0-2d01-4db4-a2e7-68dea9e3a676",
"name": "JSON Job Format1",
"type": "@n8n/n8n-nodes-langchain.outputParserStructured",
"position": [
5168,
560
],
"parameters": {
"jsonSchemaExample": "{\n\"Company\": \"Company name\",\n\"CompanyLocation\": \"Company location\",\n\"Role\": \"Role\",\n\"JobLocation\": \"Job loation\",\n\"JobDescription\": \"Job Description\",\n\"SalaryRange\": \"Salery Range\",\n\"Posted\": \"Date\",\n\"Stack\": [\"Tech Stack 1\",\"Tech Stack 2\"],\n\"CompanySize\": \"company size\",\n\"Funding\": \"Funding Type\",\n\"PublicReview\": \"Review Signal\",\n\"WhyItFits\": \"Description\",\n\"Link\": \"URL\",\n\"SourceLinks\": [\"URL1\", \"URL2\"]\n}"
},
"typeVersion": 1.3
},
{
"id": "cda29b55-089f-4089-92a4-3b411a2298bc",
"name": "scrape URL Dev",
"type": "@mendable/n8n-nodes-firecrawl.firecrawl",
"onError": "continueRegularOutput",
"position": [
48,
-352
],
"parameters": {
"url": "={{ $json.searchUrl }}",
"operation": "scrape",
"scrapeOptions": {
"options": {
"formats": {
"format": [
{
"type": "links"
}
]
},
"headers": {}
}
},
"requestOptions": {}
},
"credentials": {
"firecrawlApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "b6b2751b-6a87-4615-8ea6-a766ecfdae3a",
"name": "OpenAI Chat Model7",
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
"position": [
2080,
272
],
"parameters": {
"model": {
"__rl": true,
"mode": "list",
"value": "gpt-4.1-mini",
"cachedResultName": "gpt-4.1-mini"
},
"options": {}
},
"credentials": {
"openAiApi": {
"name": "<your credential>"
}
},
"typeVersion": 1.2
},
{
"id": "c593030e-4361-410d-8bf1-8acf879bee9b",
"name": "JSON Job Format2",
"type": "@n8n/n8n-nodes-langchain.outputParserStructured",
"onError": "continueErrorOutput",
"position": [
2352,
256
],
"parameters": {
"jsonSchemaExample": "{\n\"Posted\": \"Date\",\n\"SalaryRange\": \"Salary\"\n}"
},
"typeVersion": 1.3
},
{
"id": "0a67c46b-2a42-443c-a763-392a872f88f7",
"name": "Process Date AI",
"type": "@n8n/n8n-nodes-langchain.agent",
"onError": "continueRegularOutput",
"maxTries": 2,
"position": [
2144,
32
],
"parameters": {
"text": "=#### Task 1\nBring this date \"{{ $json.data.json.Posted }}\" in the format YYYY-MM-DD (today is {{ $now }}) if possible.\n\n#### Task 2\nConvert the Salery Range \"{{ $json.data.json.SalaryRange }}\" to \u20ac (p.A) with grouping dots. Call the firecrawl tool if you need recent exchange rates. Add original value in brakets () after that value. If not possible return null. Use Websearch if you need clarifications for \"Entgeldgruppen\" etc.\n\n### Clarification\n\nAlways output the json, use the value null if no information can be found\n\n{\n\"Posted\": \"Date\",\n\"SalaryRange\": \"Salary\"\n}",
"options": {},
"promptType": "define",
"hasOutputParser": true
},
"retryOnFail": true,
"typeVersion": 2
},
{
"id": "3140e3c8-f082-4203-a399-2ebccc6cefbf",
"name": "Code with Posted Date",
"type": "n8n-nodes-base.code",
"position": [
2512,
32
],
"parameters": {
"jsCode": "const original = $('Add Link').all();\nconst input = $input.all();\n\n\nfor (let i = 0; i < original.length; i++) {\n const newPostedDate = input[i].json.output?.Posted;\n const newSalaryRange = input[i].json.output?.SalaryRange;\n\n if(newPostedDate)\n original[i].json.data.json.Posted = newPostedDate;\n\n if(newSalaryRange)\n original[i].json.data.json.SalaryRange = newSalaryRange;\n}\n\nreturn original;"
},
"typeVersion": 2
},
{
"id": "0fe4026a-5f1c-401f-b26d-a021469771cc",
"name": "Fetch Jobs from Linkedin1",
"type": "n8n-nodes-base.httpRequest",
"position": [
64,
-576
],
"parameters": {
"url": "={{ $json.url }}",
"options": {}
},
"typeVersion": 4.2
},
{
"id": "1a3eb5a6-1fc2-493a-b15e-5c448c71d7c6",
"name": "Extract Job Links1",
"type": "n8n-nodes-base.html",
"position": [
208,
-576
],
"parameters": {
"options": {},
"operation": "extractHtmlContent",
"extractionValues": {
"values": [
{
"key": "links",
"attribute": "href",
"cssSelector": "ul.jobs-search__results-list li div a[class*=\"base-card\"]",
"returnArray": true,
"returnValue": "attribute"
}
]
}
},
"typeVersion": 1.2
},
{
"id": "29844177-0f88-4974-a300-5fe582800e74",
"name": "scrape URL indeed",
"type": "@mendable/n8n-nodes-firecrawl.firecrawl",
"onError": "continueRegularOutput",
"position": [
16,
-32
],
"parameters": {
"url": "={{ $json.searchUrl }}",
"operation": "scrape",
"scrapeOptions": {
"options": {
"formats": {
"format": [
{
"type": "links"
}
]
},
"headers": {}
}
},
"requestOptions": {}
},
"credentials": {
"firecrawlApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "7b34a0c8-063d-4af2-a5eb-d4cb87845c50",
"name": "If",
"type": "n8n-nodes-base.if",
"position": [
3520,
-368
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 3,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "1bc36f08-e131-446a-b607-fe22a79dff3d",
"operator": {
"type": "string",
"operation": "notEmpty",
"singleValue": true
},
"leftValue": "={{ $json.output }}",
"rightValue": ""
}
]
}
},
"typeVersion": 2.3
},
{
"id": "e4aba33e-4683-4fa8-9817-833ddece7925",
"name": "/scrape exchange rates",
"type": "@mendable/n8n-nodes-firecrawl.firecrawlTool",
"position": [
2176,
384
],
"parameters": {
"url": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('URL', ``, 'string') }}",
"operation": "scrape",
"scrapeOptions": {
"options": {
"formats": {
"format": [
{
"type": "html"
}
]
},
"headers": {}
}
},
"requestOptions": {},
"descriptionType": "manual",
"toolDescription": "Call this function to get current exchange rates.\nURL has the form: https://www.xe.com/currencyconverter/convert/?Amount=118000&From=CHF&To=EUR"
},
"credentials": {
"firecrawlApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "2850b5af-9d71-4443-8423-806dfab0e162",
"name": "/search in Firecrawl",
"type": "@mendable/n8n-nodes-firecrawl.firecrawlTool",
"position": [
2304,
384
],
"parameters": {
"query": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Query', ``, 'string') }}",
"resource": "MapSearch",
"operation": "search",
"requestOptions": {}
},
"credentials": {
"firecrawlApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "998d41cd-12aa-4544-84f2-a3fa218e2c3b",
"name": "Reduce to job data",
"type": "n8n-nodes-base.set",
"position": [
1728,
32
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "2e365002-a95d-4765-87a2-b8a23d71663e",
"name": "data.json",
"type": "object",
"value": "={{ $json.output }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "eeebc712-3e74-4c7c-9c64-2ade86f62384",
"name": "Stepstone Query",
"type": "n8n-nodes-base.code",
"position": [
-80,
-736
],
"parameters": {
"jsCode": "// n8n Code node (no URLSearchParams)\n\nfunction slugify(value) {\n return String(value || \"\")\n .trim()\n .toLowerCase()\n .replace(/[\u00e4]/g, \"ae\")\n .replace(/[\u00f6]/g, \"oe\")\n .replace(/[\u00fc]/g, \"ue\")\n .replace(/[\u00df]/g, \"ss\")\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\");\n}\n\nfunction mapExperienceToCode(years) {\n if (years == null) return null;\n if (years < 2) return \"90001\";\n return \"90002\"; // matches your example\n}\n\nfunction buildKeywords(applicant) {\n const parts = [];\n\n if (applicant.role) parts.push(applicant.role);\n if (Array.isArray(applicant.stack)) parts.push(...applicant.stack);\n\n const unique = [...new Set(parts.map(v => String(v).trim()).filter(Boolean))];\n\n // Keep query concise\n return unique.slice(0, 5).join(\" \");\n}\n\nfunction encode(value) {\n return encodeURIComponent(value);\n}\n\nconst input = $input.first().json;\nconst applicant = input.applicant || {};\n\nconst keywords = buildKeywords(applicant);\nconst location = applicant.location || \"Stuttgart\";\nconst experienceCode = mapExperienceToCode(applicant.experience_years);\n\nconst keywordSlug = slugify(keywords);\nconst locationSlug = slugify(location);\n\n// Base URL\nlet url = \"https://www.stepstone.de/jobs/\" + keywordSlug + \"/in-\" + locationSlug;\n\n// Build query string manually\nlet queryParts = [];\n\nqueryParts.push(\"whereType=autosuggest\");\nqueryParts.push(\"radius=30\");\nqueryParts.push(\"searchOrigin=Resultlist_top-search\");\n\nif (experienceCode) {\n queryParts.push(\"action=\" + encode(\"facet_selected;experiences;\" + experienceCode));\n queryParts.push(\"ex=\" + experienceCode);\n}\n\n// Join query\nconst queryString = queryParts.join(\"&\");\nconst finalUrl = url + \"?\" + queryString;\n\nreturn [\n {\n json: {\n stepstone_query_url: finalUrl,\n stepstone_keywords: keywords,\n stepstone_location: location,\n filters: {\n experience_code: experienceCode,\n salary_minimum_eur: applicant.salary_minimum_eur || null,\n max_listing_age_days: applicant.max_listing_age_days || null\n }\n }\n }\n];"
},
"typeVersion": 2
},
{
"id": "77097e3a-c891-4fab-afa6-0a3a7a74b90d",
"name": "scrape URL stepstone",
"type": "@mendable/n8n-nodes-firecrawl.firecrawl",
"onError": "continueRegularOutput",
"position": [
64,
-736
],
"parameters": {
"url": "={{ $json.stepstone_query_url }}",
"operation": "scrape",
"scrapeOptions": {
"options": {
"formats": {
"format": [
{
"type": "json",
"schema": "{\n \"type\": \"object\",\n \"properties\": {\n \"Links\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n },\n \"description\": \"Links to Job Descriptions.\"\n }\n}}"
}
]
},
"headers": {}
}
},
"requestOptions": {}
},
"credentials": {
"firecrawlApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "c9366239-c9a2-4167-bd33-59c566f1cbb2",
"name": "Edit Fields3",
"type": "n8n-nodes-base.set",
"position": [
208,
-736
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "83359463-a0d7-4768-bf9e-1227b05ad4ce",
"name": "data.links",
"type": "array",
"value": "={{ $json.data.links }}"
},
{
"id": "fdc6f3b5-b76c-4ef1-8a08-b10f5c8e91c7",
"name": "",
"type": "string",
"value": ""
}
]
}
},
"typeVersion": 3.4
},
{
"id": "15156bf3-1cf6-42db-ba54-facf547c8337",
"name": "Edit Fields4",
"type": "n8n-nodes-base.set",
"position": [
160,
144
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "83359463-a0d7-4768-bf9e-1227b05ad4ce",
"name": "data.links",
"type": "array",
"value": "={{ $json.data.links }}"
},
{
"id": "fdc6f3b5-b76c-4ef1-8a08-b10f5c8e91c7",
"name": "",
"type": "string",
"value": ""
}
]
}
},
"typeVersion": 3.4
},
{
"id": "0a98eed6-9907-4d48-9fc3-2cc6312c3d63",
"name": "Xing Query",
"type": "n8n-nodes-base.code",
"position": [
-128,
144
],
"parameters": {
"jsCode": "// n8n Code node for Xing search URL\n// No URLSearchParams used\n\nfunction encode(value) {\n return encodeURIComponent(String(value || \"\"));\n}\n\nfunction mapCareerLevel(years) {\n // Based on your example:\n // careerLevel=3.2ebf16 appears to represent a more experienced profile\n if (years == null) return null;\n if (years < 2) return \"1\";\n if (years < 5) return \"2\";\n return \"3.2ebf16\";\n}\n\nfunction mapSincePeriod(days) {\n if (days == null) return null;\n if (days <= 1) return \"LAST_DAY\";\n if (days <= 7) return \"LAST_WEEK\";\n if (days <= 30) return \"LAST_MONTH\";\n return null;\n}\n\nfunction buildKeywords(applicant) {\n const parts = [];\n\n if (applicant.role) parts.push(applicant.role);\n if (Array.isArray(applicant.stack) && applicant.stack.length) {\n // For Xing, keep keywords tighter than Stepstone\n // Usually better to focus on the strongest signal\n parts.unshift(applicant.stack[0]);\n }\n\n const unique = [...new Set(parts.map(v => String(v).trim()).filter(Boolean))];\n\n // Prefer just the strongest search phrase\n // Example target: \"unreal engine\"\n return applicant.stack && applicant.stack.length\n ? applicant.stack[0]\n : unique.slice(0, 2).join(\" \");\n}\n\nconst input = $input.first().json;\nconst applicant = input.applicant || {};\n\nconst keywords = buildKeywords(applicant);\nconst location = applicant.location || \"Stuttgart\";\nconst sincePeriod = mapSincePeriod(applicant.max_listing_age_days);\nconst careerLevel = mapCareerLevel(applicant.experience_years);\n\n// From your example, these Xing values are location specific.\n// Keep them static for Stuttgart unless you want to maintain a mapping table.\nconst cityId = \"2825297.22f76d\";\nconst searchId = \"292b8e8de4bdedf62217bc9d7b4290b2\";\n\n// Salary range\nconst salaryMin = applicant.salary_minimum_eur || 80000;\nconst salaryMax = 200000;\nconst salary = salaryMin + \"_\" + salaryMax;\n\nlet queryParts = [];\n\nqueryParts.push(\"id=\" + encode(searchId));\nqueryParts.push(\"keywords=\" + encode(keywords));\nqueryParts.push(\"location=\" + encode(location));\nqueryParts.push(\"cityId=\" + encode(cityId));\n\nif (sincePeriod) {\n queryParts.push(\"sincePeriod=\" + encode(sincePeriod));\n}\n\nqueryParts.push(\"salary=\" + encode(salary));\n\nif (careerLevel) {\n queryParts.push(\"careerLevel=\" + encode(careerLevel));\n}\n\nconst finalUrl = \"https://www.xing.com/jobs/search/ki?\" + queryParts.join(\"&\");\n\nreturn [\n {\n json: {\n xing_query_url: finalUrl,\n xing_keywords: keywords,\n xing_location: location,\n xing_filters: {\n city_id: cityId,\n since_period: sincePeriod,\n salary: salary,\n career_level: careerLevel\n }\n }\n }\n];"
},
"typeVersion": 2
},
{
"id": "dbda99dd-2eec-4459-86e2-8538de25385a",
"name": "scrape URL Xing",
"type": "@mendable/n8n-nodes-firecrawl.firecrawl",
"onError": "continueRegularOutput",
"position": [
16,
144
],
"parameters": {
"url": "={{ $json.xing_query_url }}",
"operation": "scrape",
"scrapeOptions": {
"options": {
"formats": {
"format": [
{
"type": "links"
}
]
},
"headers": {}
}
},
"requestOptions": {}
},
"credentials": {
"firecrawlApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "4900c37f-1042-48be-98ae-d71fc5e494bc",
"name": "Code in JavaScript4",
"type": "n8n-nodes-base.code",
"onError": "continueRegularOutput",
"position": [
304,
144
],
"parameters": {
"jsCode": "const links = $input.first().json.data?.links || [];\n\nreturn links .filter(link => typeof link === 'string' && link.includes('xing.com/jobs')) .map(link => ({ json: { url: link } }));"
},
"typeVersion": 2,
"alwaysOutputData": true
},
{
"id": "4cfbc7fd-7608-4626-aa6e-ff93ae31e914",
"name": "HTML",
"type": "n8n-nodes-base.html",
"position": [
3792,
-272
],
"parameters": {
"html": "<!DOCTYPE html>\n\n<html>\n<head>\n <meta charset=\"UTF-8\" />\n <title>No Jobs, Jobs, Jobs</title>\n<style>\n .job-card {\n font-family: Arial, sans-serif;\n max-width: 800px;\n margin: 24px auto;\n padding: 24px;\n border: 1px solid #e5e7eb;\n border-radius: 16px;\n background: #ffffff;\n color: #111827;\n box-shadow: 0 4px 14px rgba(0,0,0,0.06);\n }\n\n .job-header {\n border-bottom: 1px solid #e5e7eb;\n padding-bottom: 16px;\n margin-bottom: 20px;\n }\n\n .job-title {\n margin: 0 0 8px;\n font-size: 28px;\n line-height: 1.2;\n }\n\n .job-subtitle {\n margin: 0;\n font-size: 15px;\n color: #6b7280;\n }\n\n .score-box {\n display: inline-block;\n margin-top: 14px;\n padding: 10px 14px;\n border-radius: 10px;\n background: #f3f4f6;\n font-weight: bold;\n }\n\n .section {\n margin-top: 24px;\n }\n\n .section h2 {\n margin: 0 0 12px;\n font-size: 18px;\n border-left: 4px solid #d1d5db;\n padding-left: 10px;\n }\n\n .grid {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 12px 20px;\n }\n\n .field {\n padding: 12px;\n background: #f9fafb;\n border-radius: 10px;\n }\n\n .label {\n display: block;\n font-size: 12px;\n text-transform: uppercase;\n letter-spacing: 0.04em;\n color: #6b7280;\n margin-bottom: 4px;\n }\n\n .value {\n font-size: 15px;\n font-weight: 500;\n }\n\n .tags {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n }\n\n .tag {\n display: inline-block;\n padding: 8px 12px;\n border-radius: 999px;\n background: #eef2ff;\n color: #3730a3;\n font-size: 13px;\n font-weight: 500;\n }\n\n .muted {\n color: #6b7280;\n }\n\n .cta {\n margin-top: 28px;\n display: flex;\n gap: 12px;\n flex-wrap: wrap;\n }\n\n .button {\n display: inline-block;\n padding: 12px 18px;\n border-radius: 10px;\n text-decoration: none;\n font-weight: bold;\n background: #111827;\n color: white;\n }\n\n .button-secondary {\n display: inline-block;\n padding: 12px 18px;\n border-radius: 10px;\n text-decoration: none;\n font-weight: bold;\n background: #f3f4f6;\n color: #111827;\n }\n\n .sources {\n margin: 0;\n padding-left: 18px;\n }\n\n .explanation,\n .fit-text {\n line-height: 1.6;\n color: #374151;\n }\n\n @media (max-width: 640px) {\n .grid {\n grid-template-columns: 1fr;\n }\n\n .job-title {\n font-size: 22px;\n }\n }\n</style>\n</head>\n<body>\n\n<div class=\"job-card\">\n <div class=\"job-header\">\n <h1 class=\"job-title\">Unfortunately,</br>today I don't have any jobs for you.</br></br>\ud83d\ude2a</h1>\n </div>\n</div>\n \n</body>\n</html>\n\n<style>\n.container {\n background-color: #ffffff;\n text-align: center;\n padding: 16px;\n border-radius: 8px;\n}\n\nh1 {\n color: #ff6d5a;\n font-size: 24px;\n font-weight: bold;\n padding: 8px;\n}\n\nh2 {\n color: #909399;\n font-size: 18px;\n font-weight: bold;\n padding: 8px;\n}\n</style>\n\n<script>\n</script>"
},
"typeVersion": 1.2
},
{
"id": "aa10406c-2c99-49ed-9b54-50c259e3ad17",
"name": "No Jobs Mail",
"type": "n8n-nodes-base.emailSend",
"position": [
4048,
-272
],
"parameters": {
"html": "={{ $json.html }}",
"options": {
"appendAttribution": false
},
"subject": "No Job Alert!",
"toEmail": "mail@domain.de",
"fromEmail": "job@domain.de"
},
"credentials": {
"smtp": {
"name": "<your credential>"
}
},
"typeVersion": 2.1
},
{
"id": "64dad2b1-a331-4421-9ba2-bda7d883ad5a",
"name": "Job Mail",
"type": "n8n-nodes-base.emailSend",
"position": [
4048,
-464
],
"parameters": {
"html": "={{ $json.html }}",
"options": {
"appendAttribution": false
},
"subject": "Job Alert!",
"toEmail": "mail@domain.de",
"fromEmail": "job@domain.de"
},
"credentials": {
"smtp": {
"name": "<your credential>"
}
},
"typeVersion": 2.1
},
{
"id": "9e3728f1-286a-4fbf-aa24-5d00d8b526a8",
"name": "Add Link",
"type": "n8n-nodes-base.code",
"position": [
1936,
32
],
"parameters": {
"jsCode": "const input = $input.all();\nconst links = $('Merge').all();\n\nfor (let i = 0; i < input.length; i++) {\n input[i].json.data.json.Link = links[i]?.json?.url || null;\n}\n\nreturn input;"
},
"typeVersion": 2
},
{
"id": "cef8ef43-4d74-4bbc-8084-6a3283263b3d",
"name": "Loop over all jobs",
"type": "n8n-nodes-base.splitInBatches",
"position": [
2816,
32
],
"parameters": {
"options": {}
},
"typeVersion": 3
},
{
"id": "3eb9f512-198f-42f6-8c2a-7d603537f264",
"name": "Raw Values",
"type": "n8n-nodes-base.set",
"position": [
-400,
64
],
"parameters": {
"mode": "raw",
"options": {},
"jsonOutput": "{\n \"applicant\": {\n \"role\": \"Senior Backend Engineer\",\n \"stack\": [\"Python\", \"Django\", \"PostgreSQL\", \"Redis\", \"AWS\"],\n \"experience_years\": 8,\n \"location\": \"Remote (EU-friendly timezones)\",\n \"salary_minimum_eur\": 90000,\n \"deal_breakers\": [\"on-site only\", \"requires US citizenship\", \"crypto/web3\"],\n \"nice_to_have\": [\"4-day week\", \"open source culture\", \"Series A-C\"],\n \"max_listing_age_days\": 7\n }\n}"
},
"typeVersion": 3.4
},
{
"id": "03a0a66c-3038-4195-99e2-0179e84dc93b",
"name": "Sticky Note14",
"type": "n8n-nodes-base.stickyNote",
"position": [
-176,
-384
],
"parameters": {
"width": 688,
"height": 176,
"content": "Results are a bit crappy"
},
"typeVersion": 1
},
{
"id": "00fe8604-f7dd-4573-8a66-5d8923305b34",
"name": "Schedule Trigger",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
-640,
0
],
"parameters": {
"rule": {
"interval": [
{
"daysInterval": 7,
"triggerAtHour": 7
}
]
}
},
"typeVersion": 1.3
},
{
"id": "b74e0fdb-10df-4c5d-a869-8ceeaa7068cb",
"name": "Valid only",
"type": "n8n-nodes-base.filter",
"position": [
1536,
32
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 3,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "f064317f-8e61-4ee1-bfe2-294892640b40",
"operator": {
"type": "string",
"operation": "exists",
"singleValue": true
},
"leftValue": "={{$json.output.JobDescription}}",
"rightValue": ""
}
]
}
},
"typeVersion": 2.3
},
{
"id": "f4515224-a923-4183-aded-96ffa6593db3",
"name": "/scrape1",
"type": "@mendable/n8n-nodes-firecrawl.firecrawl",
"onError": "continueErrorOutput",
"position": [
992,
144
],
"parameters": {
"url": "={{ $json.url }}",
"operation": "scrape",
"scrapeOptions": {
"options": {
"proxy": "stealth",
"mobile": true,
"formats": {
"format": [
{}
]
},
"headers": {}
}
},
"requestOptions": {}
},
"credentials": {
"firecrawlApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "a53a1bcc-85ae-4084-a686-56573b620280",
"name": "Loop Over Items",
"type": "n8n-nodes-base.splitInBatches",
"position": [
768,
48
],
"parameters": {
"options": {}
},
"typeVersion": 3
},
{
"id": "f3852834-1897-48bb-8a3e-a547c5a00e75",
"name": "OpenAI Chat Model",
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
"position": [
1120,
384
],
"parameters": {
"model": {
"__rl": true,
"mode": "list",
"value": "gpt-4.1-nano",
"cachedResultName": "gpt-4.1-nano"
},
"options": {}
},
"credentials": {
"openAiApi": {
"name": "<your credential>"
}
},
"typeVersion": 1.2
},
{
"id": "6ea331ab-f826-4682-bce6-d0f6de2e8f79",
"name": "JSON Job Format",
"type": "@n8n/n8n-nodes-langchain.outputParserStructured",
"onError": "continueErrorOutput",
"position": [
1392,
368
],
"parameters": {
"jsonSchemaExample": "{\n\"Company\": \"Company name\",\n\"CompanyLocation\": \"Company location\",\n\"Role\": \"Role\",\n\"JobLocation\": \"Job loation\",\n\"JobDescription\": \"Job Description\",\n\"SalaryRange\": \"Salery Range\",\n\"Posted\": \"Date\",\n\"Stack\": [\"Tech Stack 1\",\"Tech Stack 2\"],\n\"CompanySize\": \"company size\"\n}"
},
"typeVersion": 1.3
},
{
"id": "66ded79b-e631-42f0-90aa-ed3ff1c518a1",
"name": "Process Scraped Data",
"type": "@n8n/n8n-nodes-langchain.agent",
"onError": "continueErrorOutput",
"maxTries": 2,
"position": [
1184,
144
],
"parameters": {
"text": "=### SOURCE\n{{ $json.data.markdown }}",
"options": {
"systemMessage": "You are an expert researcher. Extract all needed information from the source.\nPosted Date might be in the form of \"x days ago\" (vor x Tagen).\nIf no information can be found write \"N/A\". Do not hallucinate/invent infromation.\n\n{\n\"Company\": \"The company name. Do not add if the mentioned company is a job placement or headhunter agency.\",\n\"CompanyLocation\": \"The Company location.\",\n\"Role\": \"The Job Role\",\n\"JobLocation\": \"The Job loation. Might be remote, if it is a remote position.\",\n\"JobDescription\": \"The complete Job Description.\",\n\"SalaryRange\": \"The Salery Range for this job posting.\",\n\"Posted\": \"The date the job posting was published. Might be in the form of x days ago (vor x Tagen)\",\n\"Stack\": [\"The tech stack that this jobs requieres\",\"Tech Stack 2\"],\n\"CompanySize\": \"The amount of employees in this company.\"\n}\n\n"
},
"promptType": "define",
"hasOutputParser": true
},
"retryOnFail": true,
"typeVersion": 2
},
{
"id": "b6f6b762-e014-413c-9245-2537f32286fc",
"name": "HTML Failed Research",
"type": "n8n-nodes-base.html",
"position": [
5408,
352
],
"parameters": {
"html": "{{\n(() => {\n\nconst job = $(\"Loop over all jobs\").first().json.output;\n\n const rating = $(\"Rate suitablility\").item.json.output || {};\n\n const stackHtml = Array.isArray(job.Stack) && job.Stack.length\n ? job.Stack.map(skill => `<span class=\"tag\">${skill}</span>`).join(\" - \")\n : `<span class=\"muted\">N/A</span>`;\n\n const sourcesHtml = Array.isArray(job.SourceLinks) && job.SourceLinks.length\n ? job.SourceLinks.map((link, index) =>\n `<li><a href=\"${link}\" target=\"_blank\" rel=\"noopener noreferrer\">Source ${index + 1}</a></li>`\n ).join(\"\")\n : `<li class=\"muted\">No sources available</li>`;\n\n return `\n<div class=\"job-card\">\n <div class=\"job-header\">\n <h1 class=\"job-title\">${job.Role || \"Untitled role\"}</h1>\n <p class=\"job-subtitle\">Company: ${job.Company || \"Company name not specified\"}</p>\n <p class=\"job-subtitle\">Location: ${job.JobLocation || \"Location not specified\"}</p>\n <div class=\"score-box\">Match score: ${rating.Score ?? \"N/A\"}</div>\n </div>\n\n <div class=\"section\">\n <h2>Description</h2>\n <p class=\"explanation\">${job.JobDescription || \"No description available. What a lacy AI...\"}</p>\n </div>\n\n <div class=\"section\">\n <h2>Assessment</h2>\n <p class=\"explanation\">${rating.Explanation || \"No explanation available.\"}</p>\n </div>\n\n <div class=\"section\">\n <h2>Job details</h2>\n <div class=\"grid\">\n <div class=\"field\">\n <span class=\"label\">Salary range: </span>\n <span class=\"value\">${job.SalaryRange || \"N/A\"}</span>\n </div>\n\n <div class=\"field\">\n <span class=\"label\">Posted: </span>\n <span class=\"value\">${job.Posted || \"N/A\"}</span>\n </div>\n\n <div class=\"field\">\n <span class=\"label\">Company size: </span>\n <span class=\"value\">${job.CompanySize || \"N/A\"}</span>\n </div>\n\n <div class=\"field\">\n <span class=\"label\">Funding: </span>\n <span class=\"value\">${job.Funding || \"N/A\"}</span>\n </div>\n\n <div class=\"field\">\n <span class=\"label\">Public review: </span>\n <span class=\"value\">${job.PublicReview || \"N/A\"}</span>\n </div>\n </div>\n </div>\n\n <div class=\"section\">\n <h2>Why it fits</h2>\n <p class=\"fit-text\">${job.WhyItFits || \"No fit summary available.\"}</p>\n </div>\n\n <div class=\"section\">\n <h2>Tech stack</h2>\n <div class=\"tags\">${stackHtml}</div>\n </div>\n\n <div class=\"section\">\n <h2>Sources</h2>\n <ul class=\"sources\">${sourcesHtml}</ul>\n </div>\n\n <div class=\"cta\">\n <a class=\"button\" href=\"${job.Link || \"#\"}\" target=\"_blank\" rel=\"noopener noreferrer\">Apply now</a>\n </div>\n</div>\n`;\n})()\n}}"
},
"typeVersion": 1.2
}
],
"active": false,
"settings": {
"binaryMode": "separate",
"executionOrder": "v1"
},
"versionId": "2cb2ada4-a384-47d3-8ed3-f32b642e9bee",
"connections": {
"If": {
"main": [
[
{
"node": "HTML1",
"type": "main",
"index": 0
}
],
[
{
"node": "HTML",
"type": "main",
"index": 0
}
]
]
},
"HTML": {
"main": [
[
{
"node": "No Jobs Mail",
"type": "main",
"index": 0
}
]
]
},
"HTML1": {
"main": [
[
{
"node": "Job Mail",
"type": "main",
"index": 0
}
]
]
},
"Merge": {
"main": [
[
{
"node": "Loop Over Items",
"type": "main",
"index": 0
}
]
]
},
"/scrape1": {
"main": [
[
{
"node": "Process Scraped Data",
"type": "main",
"index": 0
}
],
[
{
"node": "Loop Over Items",
"type": "main",
"index": 0
}
]
]
},
"Add Link": {
"main": [
[
{
"node": "Process Date AI",
"type": "main",
"index": 0
}
]
]
},
"Raw Values": {
"main": [
[
{
"node": "Xing Query",
"type": "main",
"index": 0
},
{
"node": "Indeed Query",
"type": "main",
"index": 0
}
]
]
},
"Valid only": {
"main": [
[
{
"node": "Reduce to job data",
"type": "main",
"index": 0
}
]
]
},
"Xing Query": {
"main": [
[
{
"node": "scrape URL Xing",
"type": "main",
"index": 0
}
]
]
},
"Edit Fields": {
"main": [
[
{
"node": "Code in JavaScript2",
"type": "main",
"index": 0
}
]
]
},
"Edit Fields1": {
"main": [
[
{
"node": "Code in JavaScript3",
"type": "main",
"index": 0
}
]
]
},
"Edit Fields3": {
"main": [
[]
]
},
"Edit Fields4": {
"main": [
[
{
"node": "Code in JavaScript4",
"type": "main",
"index": 0
}
]
]
},
"Indeed Query": {
"main": [
[
{
"node": "scrape URL indeed",
"type": "main",
"index": 0
}
]
]
},
"Score Filter1": {
"main": [
[
{
"node": "Detailled Research",
"type": "main",
"index": 0
}
],
[
{
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.
firecrawlApiopenAiApismtp
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
Stop reading job listings that aren't a fit. This workflow automatically crawls five major job boards (LinkedIn, Indeed, WeAreDevelopers, Stepstone, and Xing) and scores every listing against your personal applicant profile, and emails you the jobs worth your time.
Source: https://n8n.io/workflows/15412/ — 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.
Code Schedule. Uses memoryBufferWindow, agent, stickyNote, outputParserStructured. Event-driven trigger; 45 nodes.
3790. Uses memoryBufferWindow, agent, outputParserStructured, lmChatOpenAi. Event-driven trigger; 45 nodes.
Stock Analysis Agent (Hebrew, RTL, GPT-4o)
Turn any product page into ready-to-run Meta ads—fast, consistent, and client-friendly.
Before adding a new npm package as a dependency, you should know if it's actively maintained, widely used, and safe to build on. This workflow does that analysis automatically.