Automate Your Entire Job Search With Custom Resume Generator with AI (LinkedIn + n8n)
Shared 12/1/2025
62 views
Visual Workflow
JSON Code
{
"id": "olCgxDQqnZ4VYFJ9",
"meta": {
"instanceId": "9fb634bb746cbe3fe9f7fa86fdd635510d2f0c24c4ba919fbba1ca6aec3c4e57",
"templateCredsSetupCompleted": true
},
"name": "LinkedIn Job Search + Resume Builder Agent",
"tags": [],
"nodes": [
{
"id": "ff08db49-bf0f-4e13-a1d2-ef55635b0e4b",
"name": "Error Trigger",
"type": "n8n-nodes-base.errorTrigger",
"position": [
3504,
-144
],
"parameters": {},
"typeVersion": 1
},
{
"id": "5d847601-8a11-4846-8249-dd3614d46a3b",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
3392,
-336
],
"parameters": {
"color": 3,
"width": 688,
"height": 480,
"content": "## 🚨 Error and Loop Control Flow\n---"
},
"typeVersion": 1
},
{
"id": "f75ada58-7ca3-492d-9aca-c8679b2e9113",
"name": "Send Error Email",
"type": "n8n-nodes-base.mailjet",
"position": [
3776,
-144
],
"parameters": {
"text": "=Error: {{ $json.execution.error.message }}\nPlease check workflow {{ $json.execution.url }}\nLast Node Exe.: {{ $json.execution.lastNodeExecuted }}",
"subject": "Error In Linked Job Search With Resume",
"toEmail": "={{ $('Set Global Vars').first().json.to_email }}",
"fromEmail": "={{ $('Set Global Vars').first().json.from_email }}",
"additionalFields": {}
},
"credentials": {
"mailjetEmailApi": {
"id": "goG8DkfduUdx12gE",
"name": "Mailjet Email account"
}
},
"typeVersion": 1
},
{
"id": "adda0e1e-9377-4d4f-82e9-c7ead2f78589",
"name": "Sticky Note12",
"type": "n8n-nodes-base.stickyNote",
"position": [
2816,
208
],
"parameters": {
"width": 528,
"height": 1040,
"content": "# 🛠️ Setup Guide \n\n**Author: [Sohail Jafri](https://link.thesohailjafri.me/web_ai) | [Youtube](https://link.thesohailjafri.me/yt_ai) | [Instagram](https://link.thesohailjafri.me/ig_ai) | [Join Skool](https://link.thesohailjafri.me/join-skool)**\n\nFollow the steps below to get your LinkedIn Job Scraper running:\n\n### ✅ Step 1: Copy the Google Sheet template \nMake a copy of the sheet and connect it to the **[Append row in sheet](https://docs.google.com/spreadsheets/d/1awPFjM8r-fimvwiYznWLMF1KxRx2MRDrqs-fYWnaOs4/copy)** node. \nTemplate: \n\n### ✅ Step 2: Add [Apify Data](https://console.apify.com/) credentials \nAdd your Apify Data token to the Apify nodes. \n\n### ✅ Step 3: Connect your AI models \nAdd your OpenRouter (or preferred LLM) API key to both LM nodes used for screening and email composition. \n- [OpenRouter](https://openrouter.ai/)\n- [OpenAI](https://platform.openai.com/)\n\n### ✅ Step 4: Add [Resend](https://resend.com/) API key \nConfigure the Resend bearer token in the **Send Mail** node to enable daily emails. \n\n\n### ✅ Step 5: Populate Edit Fields \nFill these values in the **Edit Fields** node: \n- **ABOUT_ME** — short resume summary \n- **MY_SKILL** — comma-separated skill list \n- **JOB_INPUT** — array of search queries (location, keyword, time_range, job_type, experience_level, remote)\n\n### ✅ Step 6: Test the flow \nRun the workflow manually with a small JOB_INPUT to confirm end-to-end success.\n\n### 💡 Quick config tips\n- **Poll interval** = 2 minutes (adjust in **Wait - Polling Apify Data** if needed). \n- Ensure the Google Sheet columns match the node schema. \n- Verify the email **to** address in **Send Mail** before enabling production.\n\n### ✅ Final step: Enable workflow \nSwitch the workflow to active after confirming credentials and a successful test run.\n "
},
"typeVersion": 1
},
{
"id": "2b99ec13-6286-4ec6-84f1-db8687916237",
"name": "Schedule Trigger",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
3472,
416
],
"parameters": {
"rule": {
"interval": [
{
"triggerAtHour": 7
}
]
}
},
"typeVersion": 1.2
},
{
"id": "7e77f910-e1b2-4905-88ef-c2e240b12aa9",
"name": "Get a document",
"type": "n8n-nodes-base.googleDocs",
"position": [
3472,
640
],
"parameters": {
"operation": "get",
"documentURL": "https://docs.google.com/document/d/1RNn2GSQ25VNJ585dPNvnycd6NwdQJ41EpbpLpUBl9ZI/edit?usp=sharing"
},
"credentials": {
"googleDocsOAuth2Api": {
"id": "lLgokgGVQ6UHx1G3",
"name": "Google Docs account"
}
},
"typeVersion": 2
},
{
"id": "49e18951-4366-4547-b699-f4ac83f4b285",
"name": "Array Of Job Inputs",
"type": "n8n-nodes-base.code",
"position": [
3872,
640
],
"parameters": {
"jsCode": "const inputs = [\n {\n \"experience_level\": \"4\",\n \"job_post_time\": \"r86400\",\n \"job_title\": \"automation engineer\",\n \"job_type\": \"F\",\n \"jobs_entries\": 5,\n \"location\": \"United States\",\n \"start_jobs\": 0,\n \"work_schedule\": \"2\"\n },\n {\n \"experience_level\": \"4\",\n \"job_post_time\": \"r86400\",\n \"job_title\": \"ai engineer\",\n \"job_type\": \"F\",\n \"jobs_entries\": 5,\n \"location\": \"United States\",\n \"start_jobs\": 0,\n \"work_schedule\": \"2\"\n }\n]\n\nreturn inputs.map(i=> ({ json: i }))\n"
},
"typeVersion": 2
},
{
"id": "f5a32e52-7d43-416d-9224-e26958adc891",
"name": "Format Input",
"type": "n8n-nodes-base.code",
"position": [
4368,
432
],
"parameters": {
"jsCode": "let input = JSON.stringify($input.first().json)\nreturn {\n json: {\n input\n }\n}"
},
"typeVersion": 2
},
{
"id": "7a2f7aa6-bf98-4432-92a6-a5fe97b18b23",
"name": "Run an Actor",
"type": "@apify/n8n-nodes-apify.apify",
"position": [
4576,
432
],
"parameters": {
"actorId": {
"__rl": true,
"mode": "list",
"value": "JkfTWxtpgfvcRQn3p",
"cachedResultUrl": "https://console.apify.com/actors/JkfTWxtpgfvcRQn3p/input",
"cachedResultName": "⚡️Rapid Linkedin Jobs Scraper (No Cookies) | Free Jobs Scraper (worldunboxer/rapid-linkedin-scraper)"
},
"timeout": {},
"customBody": "={{ $json.input }}"
},
"credentials": {
"apifyApi": {
"id": "QYF5ZYibqkW9a0UB",
"name": "Apify account"
}
},
"typeVersion": 1
},
{
"id": "1bb1ebfc-8d5a-4f66-936f-2ecd9f457267",
"name": "Wait",
"type": "n8n-nodes-base.wait",
"position": [
4816,
432
],
"webhookId": "9450a98c-ded2-40c6-b2cd-22fa1679bc1e",
"parameters": {
"unit": "minutes",
"amount": 1
},
"typeVersion": 1.1
},
{
"id": "0e0abd2c-9e7e-4ca9-a67f-551980bd02f0",
"name": "Get run",
"type": "@apify/n8n-nodes-apify.apify",
"position": [
5008,
432
],
"parameters": {
"runId": {
"__rl": true,
"mode": "id",
"value": "={{ $json.id }}"
},
"resource": "Actor runs",
"operation": "Get run"
},
"credentials": {
"apifyApi": {
"id": "QYF5ZYibqkW9a0UB",
"name": "Apify account"
}
},
"typeVersion": 1
},
{
"id": "799be29d-4666-47a7-b02f-f617cacab16c",
"name": "If SUCCEEDED",
"type": "n8n-nodes-base.if",
"position": [
5408,
448
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "c5caa229-b0db-42d9-bc94-c97ee11cc9a7",
"operator": {
"name": "filter.operator.equals",
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.status }}",
"rightValue": "SUCCEEDED"
}
]
}
},
"typeVersion": 2.2
},
{
"id": "68edb611-8bff-45f9-bc79-971c3e6f987d",
"name": "If RUNNING",
"type": "n8n-nodes-base.if",
"position": [
5200,
432
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "a6e65b5b-2e60-458d-91ad-ac90aea7943d",
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.status }}",
"rightValue": "RUNNING"
}
]
}
},
"typeVersion": 2.2
},
{
"id": "3e930311-fa14-4bbd-9138-7cbdb389e1cc",
"name": "Stop and Error",
"type": "n8n-nodes-base.stopAndError",
"position": [
5616,
464
],
"parameters": {
"errorMessage": "Apify Status is not SUCCEEDED/RUNNING"
},
"typeVersion": 1
},
{
"id": "e21a162c-64f5-442c-85aa-b8f1147a643f",
"name": "Get dataset items",
"type": "@apify/n8n-nodes-apify.apify",
"position": [
5632,
656
],
"parameters": {
"offset": {},
"resource": "Datasets",
"datasetId": "={{ $json.defaultDatasetId }}"
},
"credentials": {
"apifyApi": {
"id": "QYF5ZYibqkW9a0UB",
"name": "Apify account"
}
},
"typeVersion": 1
},
{
"id": "f74a860b-2358-4734-ac70-411f6cbe53b5",
"name": "Remove Duplicate Jobs",
"type": "n8n-nodes-base.code",
"position": [
4496,
624
],
"parameters": {
"jsCode": "const jobs = $input.all().map(i=>i.json)\nconst seen = new Set();\nconst uniqueJobs = [];\n\nfor (const job of jobs) {\n if (!seen.has(job.job_id)) {\n seen.add(job.job_id);\n uniqueJobs.push({ json:job });\n }\n}\n\nreturn uniqueJobs\n"
},
"typeVersion": 2
},
{
"id": "6c9f44e6-04ce-4d6d-9e71-6f900cc6367b",
"name": "Set Global Vars",
"type": "n8n-nodes-base.set",
"position": [
3680,
640
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "e4ce6e58-d537-42d3-9566-5555fbcd1ab3",
"name": "resume",
"type": "string",
"value": "={{ $json.content }}"
},
{
"id": "d7c207ae-5f67-4de7-a1bd-5d8d7706ae5b",
"name": "telegram_chat_id",
"type": "string",
"value": "your_val"
},
{
"id": "a5ff23c2-635f-4f1a-bb87-78df4d199949",
"name": "from_email",
"type": "string",
"value": "your_val"
},
{
"id": "3d304c86-c2f1-49f1-a90a-e581e5b8d4a6",
"name": "to_email",
"type": "string",
"value": "your_val"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "bbf151cb-2bdd-4075-9d80-0b7179acd659",
"name": "Job Match AI",
"type": "@n8n/n8n-nodes-langchain.agent",
"position": [
3888,
1328
],
"parameters": {
"text": "=Here is the job description and my current resume. Evaluate alignment and generate output as defined in the System Prompt.\n\n### Job Description:\n{{ $json.job_description }}\n\n### My Resume:\n{{ $('Set Global Vars').item.json.resume }}",
"options": {
"systemMessage": "You are an expert ATS evaluator and résumé matching assistant.\n\nYour job is to analyze a Job Description (JD) and compare it with the user’s résumé.\n\nYour output must be a JSON object with the following structure:\n{\n \"relevancy\": boolean,\n \"score\": \"XX%\",\n \"justification\": [\n \"Short bullet reasoning 1\",\n \"Short bullet reasoning 2\",\n \"Short bullet reasoning 3\"\n ]\n}\n\nScoring rules:\n- Identify key JD requirements (skills, technologies, responsibilities, seniority)\n- Compare them against the résumé\n- Score must be an integer % between \"0%\" and \"100%\"\n- relevancy = true if score ≥ 50%, else false\n\nRules:\n- Justification must be 3–7 short bullets\n- JSON only — no text outside JSON\n- No hallucinations: base everything strictly on the provided résumé and JD"
},
"promptType": "define",
"hasOutputParser": true
},
"typeVersion": 2.2
},
{
"id": "9e6c2fda-a3ae-4bb9-8328-0bc7b6bdd70d",
"name": "Structured Output Parser",
"type": "@n8n/n8n-nodes-langchain.outputParserStructured",
"position": [
4096,
1552
],
"parameters": {
"schemaType": "manual",
"inputSchema": "{\n \"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\n \"title\": \"Job Match & Tailored Resume Output\",\n \"type\": \"object\",\n \"required\": [\"relevancy\", \"score\", \"justification\"],\n \"properties\": {\n \"relevancy\": {\n \"type\": \"boolean\",\n \"description\": \"True if candidate suitability score >= 50%, else False\"\n },\n \"score\": {\n \"type\": \"string\",\n \"pattern\": \"^(100|[1-9][0-9]?|0)%$\",\n \"description\": \"Percentage string from 0% to 100% without leading zeros\"\n },\n \"justification\": {\n \"type\": \"array\",\n \"minItems\": 1,\n \"maxItems\": 10,\n \"items\": {\n \"type\": \"string\",\n \"minLength\": 5,\n \"description\": \"Short bullet point reasoning for relevancy\"\n }\n }\n },\n \"additionalProperties\": false\n}\n"
},
"typeVersion": 1.3
},
{
"id": "8f1bf62a-125c-47d7-b4a8-a9320ebc965a",
"name": "OpenRouter Chat Model",
"type": "@n8n/n8n-nodes-langchain.lmChatOpenRouter",
"position": [
3888,
1552
],
"parameters": {
"options": {}
},
"credentials": {
"openRouterApi": {
"id": "6z6zYFNsqyqFRUEB",
"name": "OpenRouter account"
}
},
"typeVersion": 1
},
{
"id": "de3ee6dc-0bf3-4e89-806b-bd3216893ad8",
"name": "If Relevant",
"type": "n8n-nodes-base.if",
"position": [
4240,
1328
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "978abce9-0189-45fe-9e28-ba74633b4561",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
},
"leftValue": "={{ $json.output.relevancy }}",
"rightValue": ""
}
]
}
},
"typeVersion": 2.2
},
{
"id": "3e9bcfa7-40c4-4124-9d2f-2e487a142f22",
"name": "Resume Tailoring Ai",
"type": "@n8n/n8n-nodes-langchain.agent",
"position": [
4448,
1312
],
"parameters": {
"text": "=Here is the job description and my current resume. Evaluate alignment and generate output as defined in the System Prompt.\n\n### Job Description:\n{{ $('Set Job Details').item.json.job_description }}\n\n### My Resume:\n{{ $('Set Global Vars').item.json.resume }}",
"options": {
"systemMessage": "You are an expert ATS résumé optimizer.\nYour goal: rewrite the provided resume into a structured, clean HTML resume tailored to the given Job Description (JD).\n\nRules:\n- Only semantic HTML tags: <h1>, <h2>, <section>, <article>, <ul>, <li>, <p>, <strong>, <em>, <time>\n- No CSS, no inline style, no class or id attributes\n- Must strictly follow the exact section structure below:\n\n<h1>{FULL NAME}</h1>\n\n<h2>Contact</h2>\n<ul>\n <li>Email: {EMAIL}</li>\n <li>Location: {CITY, COUNTRY}</li>\n <li>GitHub: {URL or Not provided.}</li>\n <li>Portfolio: {URL or Not provided.}</li>\n <li>LinkedIn: {URL or Not provided.}</li>\n</ul>\n\n<h2>Title</h2>\n<p>{ROLE TITLE tailored to JD}</p>\n\n<h2>Summary</h2>\n<ul>\n <li>3 bullet achievements aligned to JD</li>\n</ul>\n\n<h2>Technical Skills</h2>\n<ul>\n <li><strong>Programming Languages:</strong> …</li>\n <li><strong>Frameworks & Libraries:</strong> …</li>\n <li><strong>Cloud & DevOps:</strong> …</li>\n <li><strong>Databases:</strong> …</li>\n <li><strong>Tools & Others:</strong> …</li>\n</ul>\n\n<h2>Experience</h2>\n<section>\n <!-- 1+ article blocks based on provided resume -->\n</section>\n\n<h2>Projects</h2>\n<section>\n <!-- 1+ article blocks; if none, include placeholder -->\n</section>\n\n<h2>Education</h2>\n<ul>\n <li>{Degree} OR <em>Not provided.</em></li>\n</ul>\n\n<h2>Certifications</h2>\n<ul>\n <li>{Certification} OR <em>Not provided.</em></li>\n</ul>\n\n<h2>Achievements</h2>\n<ul>\n <li>{Achievement} OR <em>Not provided.</em></li>\n</ul>\n\n<h2>Additional Information</h2>\n<ul>\n <li>Languages, interests, volunteering, etc. OR <em>Not provided.</em></li>\n</ul>\n\nAdditional rules:\n- No fabricated employers, dates, or education\n- You may rephrase for clarity and impact\n- You may add realistic, conservative metrics if consistent with provided information\n- Preserve truth while optimizing alignment with JD\n- Output must be a single HTML string only — no JSON, no commentary\n"
},
"promptType": "define"
},
"typeVersion": 2.2
},
{
"id": "bf322d00-2793-491a-8e82-7e550faeadc4",
"name": "HTML Formatter",
"type": "n8n-nodes-base.code",
"position": [
4800,
1312
],
"parameters": {
"jsCode": "// Get the enhanced resume content from the AI agent\nlet custom_resume = $input.first().json.output\n\nconst company_name = $('Set Job Details').first().json.company_name\nconst job_title = $('Set Job Details').first().json.job_title\nconst job_id = $('Set Job Details').first().json.job_id\n\nconst resume_title = company_name + ' - ' + job_title + ' Resume - ' + job_id\n// Clean up any unwanted HTML artifacts or markdown formatting\nconst custom_resume_formatted = custom_resume\n .replace(/^```html\\s*/i, '') // Remove opening ```html\n .replace(/\\s*```\\s*$/i, '') // Remove closing ```\n .replace(/^`html\\s*/i, '') // Remove `html\n .replace(/^html\\s*/i, '') // Remove html at start\n .replace(/^\\s*}\\s*html\\s*/i, '') // Remove }html\n .replace(/^\\s*`\\s*/i, '') // Remove leading backticks\n .trim();\n\n// HTML template with styling to match original resume format exactly\nconst custom_resume_html = `\n<!DOCTYPE html>\n<html>\n<head>\n <meta charset=\"UTF-8\">\n <style>\n body {\n font-family: Arial, sans-serif;\n max-width: 800px;\n margin: 0 auto;\n padding: 40px;\n line-height: 1.3;\n color: #333;\n background-color: white;\n font-size: 14px;\n }\n \n h1 {\n font-size: 28px;\n font-weight: bold;\n margin-bottom: 8px;\n margin-top: 0;\n color: #000;\n }\n \n .job-title {\n font-size: 16px;\n font-weight: bold;\n margin-bottom: 15px;\n margin-top: 5px;\n color: #333;\n }\n \n .contact-section {\n margin-bottom: 20px;\n }\n \n .contact-section strong {\n font-weight: bold;\n display: block;\n margin-bottom: 8px;\n }\n \n .contact-section ul {\n list-style: disc;\n margin-left: 18px;\n margin-top: 0;\n margin-bottom: 0;\n padding: 0;\n }\n \n .contact-section li {\n margin-bottom: 2px;\n line-height: 1.2;\n }\n \n h2 {\n font-size: 16px;\n font-weight: bold;\n margin-top: 20px;\n margin-bottom: 10px;\n color: #000;\n }\n \n .job-entry {\n margin-bottom: 20px;\n }\n \n .job-title-line {\n font-weight: bold;\n margin-bottom: 2px;\n line-height: 1.2;\n }\n \n .job-company {\n font-style: italic;\n margin-bottom: 8px;\n line-height: 1.2;\n }\n \n .job-duties ul {\n list-style: disc;\n margin-left: 18px;\n margin-top: 5px;\n margin-bottom: 0;\n padding: 0;\n }\n \n .job-duties li {\n margin-bottom: 5px;\n line-height: 1.3;\n }\n \n p {\n margin-bottom: 10px;\n margin-top: 0;\n text-align: justify;\n line-height: 1.3;\n }\n \n a {\n color: #0066cc;\n text-decoration: none;\n }\n \n a:hover {\n text-decoration: underline;\n }\n \n .summary {\n text-align: justify;\n margin-bottom: 20px;\n line-height: 1.3;\n }\n \n /* Ensure proper spacing for different elements */\n strong {\n font-weight: bold;\n }\n \n em {\n font-style: italic;\n }\n \n /* Remove extra spacing from nested elements */\n ul {\n margin-top: 0;\n margin-bottom: 0;\n }\n \n li {\n line-height: 1.3;\n }\n </style>\n</head>\n<body>\n ${custom_resume_formatted}\n</body>\n</html>\n`;\n\n// Return the formatted HTML\nreturn [{ \n json: { \n resume_title: resume_title,\n custom_resume_html: custom_resume_html,\n custom_resume_formatted: custom_resume_formatted,\n original_ai_output: custom_resume\n } \n}];"
},
"typeVersion": 2
},
{
"id": "c1a5b728-2f9a-47b5-a109-3a0b3ed01101",
"name": "Upload Updated Resume",
"type": "n8n-nodes-base.httpRequest",
"position": [
5216,
1312
],
"parameters": {
"url": "=https://www.googleapis.com/upload/drive/v3/files/{{ $json.id }}?uploadType=media",
"body": "={{ $('HTML Formatter').item.json.custom_resume_html }}",
"method": "PATCH",
"options": {},
"sendBody": true,
"contentType": "raw",
"jsonHeaders": "{\n \"content-type\":\"text/html\"\n}",
"sendHeaders": true,
"authentication": "predefinedCredentialType",
"specifyHeaders": "json",
"nodeCredentialType": "googleDocsOAuth2Api"
},
"credentials": {
"googleDocsOAuth2Api": {
"id": "lLgokgGVQ6UHx1G3",
"name": "Google Docs account"
}
},
"typeVersion": 4.2
},
{
"id": "ba6a71e6-7d43-4f89-a956-3d2638573ca9",
"name": "Format Final Vals",
"type": "n8n-nodes-base.code",
"position": [
5232,
1504
],
"parameters": {
"jsCode": "\nconst {\n company_name, \n job_title,\n job_url,\n salary_range,\n time_posted,\n num_applicants,\n job_id,\n} = $('Set Job Details').first().json\n\nconst custom_resume_url = 'https://docs.google.com/document/d/'+$input.first().json.id\n\nconst added_on = new Date()\n\nconst data = {\n company_name, \n job_title,\n job_url,\n salary_range,\n time_posted,\n num_applicants,\n job_id,\n custom_resume_url,\n added_on,\n}\n\n\nreturn {\n json: data\n}"
},
"typeVersion": 2
},
{
"id": "d896cb9d-060d-422b-8ebc-19646d2d7b9d",
"name": "Format Response",
"type": "n8n-nodes-base.code",
"position": [
5680,
1168
],
"parameters": {
"jsCode": "\nfunction composeSubject(jobData, searchedTitles) {\n const jobCount = jobData.length;\n const date = new Date().toLocaleDateString(\"en-US\", {\n month: \"short\",\n day: \"numeric\",\n year: \"numeric\",\n });\n\n return `${jobCount} ${searchedTitles} jobs scraped — ${date}`;\n}\nfunction composeText(jobData, searchedTitles) {\n const jobCount = jobData.length;\n\n let message = `✅ *${jobCount} ${searchedTitles}* jobs fetched that are relevant to you!\\n\\n`;\n message += `📝 Custom resumes have been created for each role.\\n📊 You can also view everything in the Google Sheet.\\n\\n`;\n\n jobData.forEach((job, index) => {\n message += `*Job ${index + 1}:* ${job.company_name} — ${job.job_title}\\n`;\n message += `🔗 Job Link: ${job.job_url}\\n`;\n message += `📄 Resume: ${job.custom_resume_url}\\n\\n`;\n });\n\n message += `🚀 Keep applying and stay consistent!`;\n\n return message;\n}\nfunction composeHTML(jobData, searchedTitles) {\n // Job data count + formatted date\n const date = new Date().toLocaleDateString(\"en-US\", {\n year: \"numeric\",\n month: \"long\",\n day: \"numeric\"\n });\n\n const jobCount = jobData.length;\n\n // HTML table rows\n const rows = jobData.map((job, idx) => `\n <tr>\n <td>${idx + 1}</td>\n <td>${job.company_name}</td>\n <td>${job.job_title}</td>\n <td><a href=\"${job.job_url}\" target=\"_blank\">View Job</a></td>\n <td>${job.salary_range || \"N/A\"}</td>\n <td>${job.time_posted}</td>\n <td>${job.num_applicants}</td>\n <td><a href=\"${job.custom_resume_url}\" target=\"_blank\">View Resume</a></td>\n </tr>\n `).join(\"\");\n\n // Final HTML body\n return `\n <div style=\"font-family: Arial, sans-serif; color: #222;\">\n <h2>✅ ${jobCount} Job Listings Scraped</h2>\n <p><strong>Role Searched:</strong> ${searchedTitles}</p>\n <p><strong>Date:</strong> ${date}</p>\n <br/>\n\n <table style=\"border-collapse: collapse; width: 100%; text-align: left;\">\n <thead>\n <tr style=\"background-color: #f2f2f2;\">\n <th>#</th>\n <th>Company</th>\n <th>Job Title</th>\n <th>Job Link</th>\n <th>Salary Range</th>\n <th>Posted</th>\n <th>Applicants</th>\n <th>Resume Link</th>\n </tr>\n </thead>\n <tbody>\n ${rows}\n </tbody>\n </table>\n\n <br/>\n <p>📌 This email was auto-generated by your job scraper.</p>\n </div>\n `;\n}\n\n\nconst jobData = $input.all().map(i=>i.json)\nconst searchedTitles = $('Array Of Job Inputs').all().map(i=>i.json.job_title).join(', ')\n\nconst subject = composeSubject(jobData, searchedTitles);\nconst text = composeText(jobData, searchedTitles)\nconst html = composeHTML(jobData, searchedTitles)\n\nreturn {\n json:{\n subject,\n text,\n html\n }\n}\n"
},
"typeVersion": 2
},
{
"id": "d2c49a11-07fa-4c4c-9840-cabf964c53d6",
"name": "Send a text message",
"type": "n8n-nodes-base.telegram",
"position": [
5696,
1376
],
"webhookId": "2e0a65d0-ef08-4d54-803e-907a38f13ef4",
"parameters": {
"text": "={{ $json.subject }}",
"chatId": "={{ $('Set Global Vars').first().json.telegram_chat_id }}",
"additionalFields": {}
},
"credentials": {
"telegramApi": {
"id": "BlKzI4sSudPj57bV",
"name": "AiWithSohail Telegram"
}
},
"typeVersion": 1.2
},
{
"id": "0f15b7a9-d969-458a-918a-d4e3a122206f",
"name": "Send an email",
"type": "n8n-nodes-base.mailjet",
"position": [
5696,
1552
],
"parameters": {
"html": "={{ $json.html }}",
"text": "={{ $json.text }}",
"subject": "={{ $json.subject }}",
"toEmail": "={{ $('Set Global Vars').first().json.to_email }}",
"fromEmail": "={{ $('Set Global Vars').first().json.from_email }}",
"additionalFields": {}
},
"credentials": {
"mailjetEmailApi": {
"id": "goG8DkfduUdx12gE",
"name": "Mailjet Email account"
}
},
"typeVersion": 1
},
{
"id": "7bd8d4fb-8393-463a-8221-0030dcc7b4c5",
"name": "Create Enhanced Resume",
"type": "n8n-nodes-base.googleDocs",
"position": [
5008,
1312
],
"parameters": {
"title": "={{ $json.resume_title }}",
"folderId": "12EH1HAOQSV_VFK7kh9A5MI0kSfnis74D"
},
"credentials": {
"googleDocsOAuth2Api": {
"id": "lLgokgGVQ6UHx1G3",
"name": "Google Docs account"
}
},
"typeVersion": 2
},
{
"id": "89ae28b2-e8fd-4707-91f0-75a63dee344d",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
3408,
208
],
"parameters": {
"color": 5,
"width": 688,
"height": 672,
"content": "## 📄 Stage 1: Initial Setup & Configuration\n---\n\n- Get Resume: Retrieves the base resume document content (likely a text string) from a document storage service.\n- Set Global Vars: Establishes essential variables for the automation.\n- Array Of Job Inputs: Defines the job search criteria (e.g., job title, location) which drive the main LinkedIn search loop."
},
"typeVersion": 1
},
{
"id": "380ab654-c399-470c-a69e-46166a847fb7",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
4128,
224
],
"parameters": {
"color": 4,
"width": 1824,
"height": 656,
"content": "## 🔍 Stage 2: Job Data Retrieval & Screening\n---\n\n- Loop: Runs the entire search process for every job query defined in the input array.\n- Search & Wait: Executes an external service (Actor) to scrape LinkedIn for jobs, then pauses the workflow until the scraping is finished.\n- Validation: Checks the Actor's status; if it failed or errored, the automation stops.\n- Extraction: If successful, the full list of found jobs is retrieved from the Actor's dataset."
},
"typeVersion": 1
},
{
"id": "5654607d-8faa-409f-a3fa-b12cfc1b02da",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
3408,
928
],
"parameters": {
"color": 5,
"width": 1968,
"height": 784,
"content": "## 🧠 Stage 3: Job Screening & Resume Tailoring\n---\n\n- AI Job Match: Evaluate each job for relevance using the base resume.\n- Conditional Check: Only continue if the job is a good match.\n- AI Resume Tailoring: Create a role-optimized resume using the job description.\n- Resume Formatting & Upload: Convert tailored resume to a file and store it.\n- Communication Prep: Format final results for messaging.\n- Notifications: Send an update (email/Telegram) with the tailored resume attached.\n- Logging & Tracking: Save job application details into a spreadsheet for record-keeping."
},
"typeVersion": 1
},
{
"id": "d74b9368-3d6e-44a9-a368-38fe8b23bd81",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
5424,
928
],
"parameters": {
"color": 4,
"width": 528,
"height": 784,
"content": "## 📧 Stage 4: Save Jobs And Send Updates\n---\n\n- Save Jobs in google sheet with custom resume link\n- Send notification via telegram and mail"
},
"typeVersion": 1
},
{
"id": "cb2611a8-394b-448e-a94a-ecfe2f4fab87",
"name": "Loop Over Jobs",
"type": "n8n-nodes-base.splitInBatches",
"position": [
4176,
640
],
"parameters": {
"options": {}
},
"typeVersion": 3
},
{
"id": "5aecb421-602f-489f-89e8-f32835a03c1e",
"name": "Loop Over Unique Jobs",
"type": "n8n-nodes-base.splitInBatches",
"position": [
3456,
1184
],
"parameters": {
"options": {}
},
"typeVersion": 3
},
{
"id": "f045f435-30c7-4e7c-b598-e3a44c807b1c",
"name": "Set Job Details",
"type": "n8n-nodes-base.set",
"position": [
3680,
1328
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "69b33c3c-f9c5-4937-8fcd-68d19ccb6b30",
"name": "job_id",
"type": "string",
"value": "={{ $json.job_id }}"
},
{
"id": "0d898d14-aa96-416c-bc85-5112ebc2f493",
"name": "job_title",
"type": "string",
"value": "={{ $json.job_title }}"
},
{
"id": "eaee3ae6-4329-420d-a584-415db1952e44",
"name": "job_description",
"type": "string",
"value": "={{ $json.job_description }}"
},
{
"id": "ed2fc237-bb2a-4439-9dc7-1997b3b1545b",
"name": "job_url",
"type": "string",
"value": "={{ $json.job_url }}"
},
{
"id": "6dfdaf0a-dc65-4e39-babb-d613ab18009d",
"name": "salary_range",
"type": "string",
"value": "={{ $json.salary_range }}"
},
{
"id": "28c76a1e-037e-43b8-b8a8-29b302b08d79",
"name": "seniority_level",
"type": "string",
"value": "={{ $json.seniority_level }}"
},
{
"id": "69d585d6-de59-48f9-891c-f46ac63f53aa",
"name": "company_name",
"type": "string",
"value": "={{ $json.company_name }}"
},
{
"id": "ec4705c3-a146-476f-aa64-75b3128f4762",
"name": "company_url",
"type": "string",
"value": "={{ $json.company_url }}"
},
{
"id": "19a18e1f-89ba-4427-9ace-902b86058e26",
"name": "employment_type",
"type": "string",
"value": "={{ $json.employment_type }}"
},
{
"id": "e8a4a0e0-bcb0-4f7a-af4a-a5971c5697c1",
"name": "industries",
"type": "string",
"value": "={{ $json.industries }}"
},
{
"id": "67013fc0-1135-4ac6-a685-c1e296e36477",
"name": "apply_url",
"type": "string",
"value": "={{ $json.apply_url }}"
},
{
"id": "9cf0f51b-fd76-43d2-8306-13caa2ec2e1a",
"name": "num_applicants",
"type": "string",
"value": "={{ $json.num_applicants }}"
},
{
"id": "b6809ef9-3ee8-4d73-b7df-556061f69800",
"name": "time_posted",
"type": "string",
"value": "={{ $json.time_posted }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "e936edbd-4d80-4d5f-891a-10d7c02a51a0",
"name": "Filter Relevant Jobs",
"type": "n8n-nodes-base.filter",
"position": [
5216,
1168
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "10b55940-7fae-4ecd-a588-0b155d2b9c33",
"operator": {
"type": "string",
"operation": "exists",
"singleValue": true
},
"leftValue": "={{ $json.job_title }}",
"rightValue": ""
}
]
}
},
"typeVersion": 2.2
},
{
"id": "34312f73-1365-404f-bf22-50f21479300f",
"name": "Append or update row in sheet",
"type": "n8n-nodes-base.googleSheets",
"position": [
5488,
1168
],
"parameters": {
"columns": {
"value": {},
"schema": [
{
"id": "job_id",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "job_id",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "company_name",
"type": "string",
"display": true,
"required": false,
"displayName": "company_name",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "job_title",
"type": "string",
"display": true,
"required": false,
"displayName": "job_title",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "job_url",
"type": "string",
"display": true,
"required": false,
"displayName": "job_url",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "custom_resume_url",
"type": "string",
"display": true,
"required": false,
"displayName": "custom_resume_url",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "salary_range",
"type": "string",
"display": true,
"required": false,
"displayName": "salary_range",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "time_posted",
"type": "string",
"display": true,
"required": false,
"displayName": "time_posted",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "added_on",
"type": "string",
"display": true,
"required": false,
"displayName": "added_on",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "num_applicants",
"type": "string",
"display": true,
"required": false,
"displayName": "num_applicants",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "output",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "output",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "autoMapInputData",
"matchingColumns": [
"job_id"
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {},
"operation": "appendOrUpdate",
"sheetName": {
"__rl": true,
"mode": "list",
"value": "gid=0",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1iuGtOePFdUOk0IOKlhz9HgF6re2UnPjbRsWXVHlNjP8/edit#gid=0",
"cachedResultName": "Sheet1"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "1iuGtOePFdUOk0IOKlhz9HgF6re2UnPjbRsWXVHlNjP8",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1iuGtOePFdUOk0IOKlhz9HgF6re2UnPjbRsWXVHlNjP8/edit?usp=drivesdk",
"cachedResultName": "N8N - LinkedIn Job Search Custom Resumes"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"id": "TB2tCNqF1GBm2ag8",
"name": "Google Sheets account"
}
},
"typeVersion": 4.7
}
],
"active": false,
"pinData": {},
"settings": {
"executionOrder": "v1"
},
"versionId": "a436778f-b503-4d80-b5f4-df009d207e16",
"connections": {
"Wait": {
"main": [
[
{
"node": "Get run",
"type": "main",
"index": 0
}
]
]
},
"Get run": {
"main": [
[
{
"node": "If RUNNING",
"type": "main",
"index": 0
}
]
]
},
"If RUNNING": {
"main": [
[
{
"node": "Wait",
"type": "main",
"index": 0
}
],
[
{
"node": "If SUCCEEDED",
"type": "main",
"index": 0
}
]
]
},
"If Relevant": {
"main": [
[
{
"node": "Resume Tailoring Ai",
"type": "main",
"index": 0
}
],
[
{
"node": "Loop Over Unique Jobs",
"type": "main",
"index": 0
}
]
]
},
"Format Input": {
"main": [
[
{
"node": "Run an Actor",
"type": "main",
"index": 0
}
]
]
},
"If SUCCEEDED": {
"main": [
[
{
"node": "Get dataset items",
"type": "main",
"index": 0
}
],
[
{
"node": "Stop and Error",
"type": "main",
"index": 0
}
]
]
},
"Job Match AI": {
"main": [
[
{
"node": "If Relevant",
"type": "main",
"index": 0
}
]
]
},
"Run an Actor": {
"main": [
[
{
"node": "Wait",
"type": "main",
"index": 0
}
]
]
},
"Error Trigger": {
"main": [
[
{
"node": "Send Error Email",
"type": "main",
"index": 0
}
]
]
},
"Get a document": {
"main": [
[
{
"node": "Set Global Vars",
"type": "main",
"index": 0
}
]
]
},
"HTML Formatter": {
"main": [
[
{
"node": "Create Enhanced Resume",
"type": "main",
"index": 0
}
]
]
},
"Loop Over Jobs": {
"main": [
[
{
"node": "Remove Duplicate Jobs",
"type": "main",
"index": 0
}
],
[
{
"node": "Format Input",
"type": "main",
"index": 0
}
]
]
},
"Format Response": {
"main": [
[
{
"node": "Send a text message",
"type": "main",
"index": 0
},
{
"node": "Send an email",
"type": "main",
"index": 0
}
]
]
},
"Set Global Vars": {
"main": [
[
{
"node": "Array Of Job Inputs",
"type": "main",
"index": 0
}
]
]
},
"Set Job Details": {
"main": [
[
{
"node": "Job Match AI",
"type": "main",
"index": 0
}
]
]
},
"Schedule Trigger": {
"main": [
[
{
"node": "Get a document",
"type": "main",
"index": 0
}
]
]
},
"Format Final Vals": {
"main": [
[
{
"node": "Loop Over Unique Jobs",
"type": "main",
"index": 0
}
]
]
},
"Get dataset items": {
"main": [
[
{
"node": "Loop Over Jobs",
"type": "main",
"index": 0
}
]
]
},
"Array Of Job Inputs": {
"main": [
[
{
"node": "Loop Over Jobs",
"type": "main",
"index": 0
}
]
]
},
"Resume Tailoring Ai": {
"main": [
[
{
"node": "HTML Formatter",
"type": "main",
"index": 0
}
]
]
},
"Filter Relevant Jobs": {
"main": [
[
{
"node": "Append or update row in sheet",
"type": "main",
"index": 0
}
]
]
},
"Loop Over Unique Jobs": {
"main": [
[
{
"node": "Filter Relevant Jobs",
"type": "main",
"index": 0
}
],
[
{
"node": "Set Job Details",
"type": "main",
"index": 0
}
]
]
},
"OpenRouter Chat Model": {
"ai_languageModel": [
[
{
"node": "Job Match AI",
"type": "ai_languageModel",
"index": 0
},
{
"node": "Resume Tailoring Ai",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Remove Duplicate Jobs": {
"main": [
[
{
"node": "Loop Over Unique Jobs",
"type": "main",
"index": 0
}
]
]
},
"Upload Updated Resume": {
"main": [
[
{
"node": "Format Final Vals",
"type": "main",
"index": 0
}
]
]
},
"Create Enhanced Resume": {
"main": [
[
{
"node": "Upload Updated Resume",
"type": "main",
"index": 0
}
]
]
},
"Structured Output Parser": {
"ai_outputParser": [
[
{
"node": "Job Match AI",
"type": "ai_outputParser",
"index": 0
}
]
]
},
"Append or update row in sheet": {
"main": [
[
{
"node": "Format Response",
"type": "main",
"index": 0
}
]
]
}
}
}