This workflow corresponds to n8n.io template #14065 — we link there as the canonical source.
This workflow follows the Agent → Google Docs 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": "KkVSte6Jl5Ss0cNR",
"name": "Auto-generate job descriptions from briefing notes with OpenAI and Google Docs",
"tags": [],
"nodes": [
{
"id": "5dcfac82-9cca-4401-bc98-986fa571d049",
"name": "Google Drive Trigger",
"type": "n8n-nodes-base.googleDriveTrigger",
"position": [
496,
368
],
"parameters": {
"event": "fileCreated",
"options": {
"fileType": "application/vnd.google-apps.document"
},
"pollTimes": {
"item": [
{
"mode": "everyMinute"
}
]
},
"triggerOn": "specificFolder",
"folderToWatch": {
"__rl": true,
"mode": "list",
"value": "YOUR_GOOGLE_DRIVE_FOLDER_ID",
"cachedResultUrl": "https://drive.google.com/drive/folders/YOUR_GOOGLE_DRIVE_FOLDER_ID",
"cachedResultName": "Your_Folder"
}
},
"typeVersion": 1
},
{
"id": "334206ef-b5b5-4258-a5ff-68af37a4212f",
"name": "Create timestamped subfolder",
"type": "n8n-nodes-base.googleDrive",
"position": [
720,
368
],
"parameters": {
"name": "={{ $json.name }}_{{ $json.createdTime }}",
"driveId": {
"__rl": true,
"mode": "list",
"value": "My Drive"
},
"options": {},
"folderId": {
"__rl": true,
"mode": "list",
"value": "YOUR_GOOGLE_DRIVE_FOLDER_ID",
"cachedResultUrl": "https://drive.google.com/drive/folders/YOUR_GOOGLE_DRIVE_FOLDER_ID",
"cachedResultName": "Your_Folder"
},
"resource": "folder"
},
"typeVersion": 3
},
{
"id": "c4468b02-0860-4320-bcc2-5778312368c5",
"name": "Move briefing doc to subfolder",
"type": "n8n-nodes-base.googleDrive",
"position": [
944,
368
],
"parameters": {
"fileId": {
"__rl": true,
"mode": "id",
"value": "={{ $('Google Drive Trigger').item.json.id }}"
},
"driveId": {
"__rl": true,
"mode": "list",
"value": "My Drive"
},
"folderId": {
"__rl": true,
"mode": "id",
"value": "={{ $json.id }}"
},
"operation": "move"
},
"typeVersion": 3
},
{
"id": "5f15c82a-42d7-4292-92e3-b8378c064717",
"name": "Read Google Doc content",
"type": "n8n-nodes-base.googleDocs",
"position": [
1168,
368
],
"parameters": {
"operation": "get",
"documentURL": "={{ $json.id }}"
},
"typeVersion": 2
},
{
"id": "0ba80b55-ae17-4009-ad7c-b5acce3c7e63",
"name": "Extract job data from transcript",
"type": "@n8n/n8n-nodes-langchain.agent",
"position": [
1392,
368
],
"parameters": {
"text": "=ROLE & OBJECTIVE\nYou are an extraction assistant. Analyze a German transcript between a Hiring Manager and a Recruiter and produce a structured Job Description in professional Business English. Rephrase only what is present in the transcript; do not add new facts.\n\nINPUT\nYou will receive raw German transcript text.\n\nSTRICT RULES\n- Extract ONLY from the transcript. Do not invent information.\n- Do NOT copy examples, instructions, or schema descriptions into any field.\n- Translate to professional Business English and elevate phrasing, but stay faithful to the source facts.\n- If an item is missing or not clearly stated:\n - Strings => null\n - Arrays => []\n - Booleans => null if unclear\n- Responsibilities vs. Requirements:\n - Responsibilities = action-oriented tasks the role performs.\n - Requirements = skills/experience needed to qualify.\n- Seniority inference:\n - Only infer if years of experience or responsibility scope clearly justify it (e.g., ownership of strategy, leadership). Otherwise set null.\n- Tech stack:\n - Extract exact tool names from the transcript. Correct obvious transcription errors (e.g., \"Uipath\" -> \"UiPath\") only if certain; if unsure, omit.\n- Remote policy:\n - Set is_remote_friendly = true/false only if clearly stated; else null.\n- Output format:\n - Return ONLY a single JSON object conforming to the structure below.\n - No Markdown, no code fences, no comments.\n - Deduplicate array items. Do not include placeholder text like \"TBD\".\n\nOUTPUT JSON STRUCTURE\n{\n \"type\": \"object\",\n \"properties\": {\n \"meta_data\": {\n \"type\": \"object\",\n \"properties\": {\n \"job_title\": {\n \"type\": \"string\",\n \"description\": \"The official title of the position.\"\n },\n \"job_subtitle\": {\n \"type\": \"string\",\n \"description\": \"Any specific focus mentioned, e.g., 'Focus on HR Processes'.\"\n },\n \"department\": {\n \"type\": \"string\",\n \"description\": \"The department or team name.\"\n },\n \"employment_type\": {\n \"type\": \"string\",\n \"description\": \"Full-time, Part-time, etc.\"\n },\n \"seniority_level\": {\n \"type\": \"string\",\n \"description\": \"Inferred level, e.g., 'Professional', 'Senior', 'Lead'.\"\n }\n },\n \"required\": [\"job_title\", \"department\"]\n },\n \"location_details\": {\n \"type\": \"object\",\n \"properties\": {\n \"locations\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n },\n \"description\": \"List of physical cities or office locations mentioned.\"\n },\n \"is_remote_friendly\": {\n \"type\": \"boolean\",\n \"description\": \"True if home office, remote work, or 'work from anywhere' is mentioned.\"\n },\n \"location_policy_text\": {\n \"type\": \"string\",\n \"description\": \"A short sentence describing the policy, e.g., 'Remote working across Germany possible'.\"\n }\n },\n \"required\": [\"locations\", \"is_remote_friendly\"]\n },\n \"content_core\": {\n \"type\": \"object\",\n \"properties\": {\n \"mission_statement\": {\n \"type\": \"string\",\n \"description\": \"A compelling 2-3 sentence pitch explaining why this role exists and its impact.\"\n },\n \"responsibilities\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n },\n \"description\": \"List of 4-6 key tasks. Use active verbs (e.g., 'Develop', 'Manage', 'Implement').\"\n },\n \"tech_stack\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n },\n \"description\": \"Specific tools, languages, or platforms mentioned (e.g., n8n, UiPath, Python, Workday).\"\n }\n },\n \"required\": [\"mission_statement\", \"responsibilities\"]\n },\n \"requirements_profile\": {\n \"type\": \"object\",\n \"properties\": {\n \"hard_skills\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n },\n \"description\": \"Professional experience and technical skills required.\"\n },\n \"soft_skills\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n },\n \"description\": \"Personality traits, mindset, or social skills mentioned.\"\n },\n \"languages\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n },\n \"description\": \"Spoken languages required including proficiency level if mentioned.\"\n }\n },\n \"required\": [\"hard_skills\"]\n },\n \"benefits_and_culture\": {\n \"type\": \"object\",\n \"properties\": {\n \"benefits_list\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n },\n \"description\": \"Perks offered (e.g., holidays, learning budget, mobility).\"\n },\n \"culture_statement\": {\n \"type\": \"string\",\n \"description\": \"Statements regarding DE&I, company values, or working atmosphere.\"\n }\n }\n },\n \"contact_info\": {\n \"type\": \"object\",\n \"properties\": {\n \"recruiter_name\": {\n \"type\": \"string\"\n },\n \"recruiter_role\": {\n \"type\": \"string\"\n },\n \"call_to_action\": {\n \"type\": \"string\",\n \"description\": \"Closing sentence encouraging application.\"\n }\n }\n }\n },\n \"required\": [\n \"meta_data\",\n \"location_details\",\n \"content_core\",\n \"requirements_profile\"\n ]\n}\n\nTRANSCRIPT\n{{ $json.content }}\n",
"options": {},
"promptType": "define",
"hasOutputParser": true
},
"typeVersion": 3.1
},
{
"id": "d09466f2-74e4-406e-af64-007f135e822a",
"name": "OpenAI Chat Model",
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
"position": [
1472,
592
],
"parameters": {
"model": {
"__rl": true,
"mode": "list",
"value": "gpt-5.1",
"cachedResultName": "gpt-5.1"
},
"options": {
"textFormat": {
"textOptions": {
"type": "json_schema",
"schema": "{\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"properties\": {\n \"meta_data\": {\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"properties\": {\n \"job_title\": {\n \"type\": [\"string\", \"null\"],\n \"description\": \"The official title of the position.\"\n },\n \"job_subtitle\": {\n \"type\": [\"string\", \"null\"],\n \"description\": \"Any specific focus mentioned, e.g., 'Focus on HR Processes'.\"\n },\n \"department\": {\n \"type\": [\"string\", \"null\"],\n \"description\": \"The department or team name.\"\n },\n \"employment_type\": {\n \"type\": [\"string\", \"null\"],\n \"description\": \"Full-time, Part-time, etc.\"\n },\n \"seniority_level\": {\n \"type\": [\"string\", \"null\"],\n \"description\": \"Inferred level, e.g., 'Professional', 'Senior', 'Lead'.\"\n }\n },\n \"required\": [\"job_title\", \"job_subtitle\", \"department\", \"employment_type\", \"seniority_level\"]\n },\n \"location_details\": {\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"properties\": {\n \"locations\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n },\n \"description\": \"List of physical cities or office locations mentioned.\"\n },\n \"is_remote_friendly\": {\n \"type\": [\"boolean\", \"null\"],\n \"description\": \"True if home office, remote work, or 'work from anywhere' is mentioned.\"\n },\n \"location_policy_text\": {\n \"type\": [\"string\", \"null\"],\n \"description\": \"A short sentence describing the policy, e.g., 'Remote working across Germany possible'.\"\n }\n },\n \"required\": [\"locations\", \"is_remote_friendly\", \"location_policy_text\"]\n },\n \"content_core\": {\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"properties\": {\n \"mission_statement\": {\n \"type\": [\"string\", \"null\"],\n \"description\": \"A compelling 2-3 sentence pitch explaining why this role exists and its impact.\"\n },\n \"responsibilities\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n },\n \"description\": \"List of 4-6 key tasks. Use active verbs (e.g., 'Develop', 'Manage', 'Implement').\"\n },\n \"tech_stack\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n },\n \"description\": \"Specific tools, languages, or platforms mentioned (e.g., n8n, UiPath, Python, Workday).\"\n }\n },\n \"required\": [\"mission_statement\", \"responsibilities\", \"tech_stack\"]\n },\n \"requirements_profile\": {\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"properties\": {\n \"hard_skills\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n },\n \"description\": \"Professional experience and technical skills required.\"\n },\n \"soft_skills\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n },\n \"description\": \"Personality traits, mindset, or social skills mentioned.\"\n },\n \"languages\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n },\n \"description\": \"Spoken languages required including proficiency level if mentioned.\"\n }\n },\n \"required\": [\"hard_skills\", \"soft_skills\", \"languages\"]\n },\n \"benefits_and_culture\": {\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"properties\": {\n \"benefits_list\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n },\n \"description\": \"Perks offered (e.g., holidays, learning budget, mobility).\"\n },\n \"culture_statement\": {\n \"type\": [\"string\", \"null\"],\n \"description\": \"Statements regarding DE&I, company values, or working atmosphere.\"\n }\n },\n \"required\": [\"benefits_list\", \"culture_statement\"]\n },\n \"contact_info\": {\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"properties\": {\n \"recruiter_name\": {\n \"type\": [\"string\", \"null\"]\n },\n \"recruiter_role\": {\n \"type\": [\"string\", \"null\"]\n },\n \"call_to_action\": {\n \"type\": [\"string\", \"null\"],\n \"description\": \"Closing sentence encouraging application.\"\n }\n },\n \"required\": [\"recruiter_name\", \"recruiter_role\", \"call_to_action\"]\n }\n },\n \"required\": [\n \"meta_data\",\n \"location_details\",\n \"content_core\",\n \"requirements_profile\",\n \"benefits_and_culture\",\n \"contact_info\"\n ]\n}"
}
}
},
"builtInTools": {}
},
"typeVersion": 1.3
},
{
"id": "7ca4d113-efb5-4152-bd49-9dd87366fb1c",
"name": "Parse AI JSON output",
"type": "n8n-nodes-base.code",
"position": [
1744,
368
],
"parameters": {
"jsCode": "const aiOutput = $input.all().map((item) => item.json);\n\nconst parsedOutput = aiOutput\n .map((output) => {\n try {\n return JSON.parse(output.output);\n } catch (error) {\n console.log(`Failed to parse output: ${output.output}`);\n return null;\n }\n })\n .filter(Boolean); // filter out null values\n\nreturn parsedOutput;\n"
},
"typeVersion": 2
},
{
"id": "355dc4d1-26e5-4c10-aa92-e4a044c16247",
"name": "Log job data in Google Sheets",
"type": "n8n-nodes-base.googleSheets",
"position": [
1968,
368
],
"parameters": {
"columns": {
"value": {
"job_title": "={{ $json.meta_data.job_title }}",
"languages": "={{ $json.requirements_profile.languages }}",
"locations": "={{ $json.location_details.locations }}",
"department": "={{ $json.meta_data.department }}",
"tech_stack": "={{ $json.content_core.tech_stack }}",
"hard_skills": "={{ $json.requirements_profile.hard_skills }}",
"soft_skills": "={{ $json.requirements_profile.soft_skills }}",
"contact_info": "={{ $json.contact_info }}",
"job_subtitle": "={{ $json.meta_data.job_subtitle }}",
"benefits_list": "={{ $json.benefits_and_culture.benefits_list }}",
"employment_type": "={{ $json.meta_data.employment_type }}",
"seniority_level": "={{ $json.meta_data.seniority_level }}",
"responsibilities": "={{ $json.content_core.responsibilities }}",
"culture_statement": "={{ $json.benefits_and_culture.culture_statement }}",
"mission_statement": "={{ $json.content_core.mission_statement }}",
"is_remote_friendly": "={{ $json.location_details.is_remote_friendly }}",
"location_policy_text": "={{ $json.location_details.location_policy_text }}"
},
"schema": [
{
"id": "job_title",
"type": "string",
"display": true,
"required": false,
"displayName": "job_title",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "job_subtitle",
"type": "string",
"display": true,
"required": false,
"displayName": "job_subtitle",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "department",
"type": "string",
"display": true,
"required": false,
"displayName": "department",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "employment_type",
"type": "string",
"display": true,
"required": false,
"displayName": "employment_type",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "seniority_level",
"type": "string",
"display": true,
"required": false,
"displayName": "seniority_level",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "locations",
"type": "string",
"display": true,
"required": false,
"displayName": "locations",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "is_remote_friendly",
"type": "string",
"display": true,
"required": false,
"displayName": "is_remote_friendly",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "location_policy_text",
"type": "string",
"display": true,
"required": false,
"displayName": "location_policy_text",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "mission_statement",
"type": "string",
"display": true,
"required": false,
"displayName": "mission_statement",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "responsibilities",
"type": "string",
"display": true,
"required": false,
"displayName": "responsibilities",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "tech_stack",
"type": "string",
"display": true,
"required": false,
"displayName": "tech_stack",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "hard_skills",
"type": "string",
"display": true,
"required": false,
"displayName": "hard_skills",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "soft_skills",
"type": "string",
"display": true,
"required": false,
"displayName": "soft_skills",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "languages",
"type": "string",
"display": true,
"required": false,
"displayName": "languages",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "benefits_list",
"type": "string",
"display": true,
"required": false,
"displayName": "benefits_list",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "culture_statement",
"type": "string",
"display": true,
"required": false,
"displayName": "culture_statement",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "contact_info",
"type": "string",
"display": true,
"required": false,
"displayName": "contact_info",
"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/YOUR_GOOGLE_SHEETS_ID/edit#gid=0",
"cachedResultName": "Sheet1"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "YOUR_GOOGLE_SHEETS_ID",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/YOUR_GOOGLE_SHEETS_ID/edit?usp=drivesdk",
"cachedResultName": "Your_Spreadsheet"
}
},
"typeVersion": 4.7
},
{
"id": "808116d1-46f7-44b6-abd7-8e08f9700992",
"name": "OpenAI Chat Model (JD-Writer)",
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
"position": [
2416,
480
],
"parameters": {
"model": {
"__rl": true,
"mode": "list",
"value": "gpt-5.1",
"cachedResultName": "gpt-5.1"
},
"options": {},
"builtInTools": {}
},
"typeVersion": 1.3
},
{
"id": "36d479a6-8dd4-49ee-bd3a-e3f9ebc5067b",
"name": "Check if JD is approved",
"type": "n8n-nodes-base.if",
"position": [
2992,
368
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 3,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "239cd203-6076-4865-9ce7-f27019b33fbb",
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.data['Approved?'] }}",
"rightValue": "yes"
}
]
}
},
"typeVersion": 2.3
},
{
"id": "040f33ce-bb7c-420b-a086-62c22c84201e",
"name": "Build JD-Writer prompt",
"type": "n8n-nodes-base.code",
"position": [
2192,
368
],
"parameters": {
"jsCode": "// Build the full prompt for JD-Writer depending on path (create vs. revise)\n\nconst AGENT_NODE_NAME = 'JD-Writer'; \nconst IF_NODE_NAME = 'Check if JD is approved'; \nconst TEAMS_NODE_NAME = 'Send JD to Teams for approval';\n\n\nconst S = v => (v === undefined || v === null ? \"\" : String(v));\nconst getByPath = (obj, path) => {\n try {\n let cur = obj;\n for (const seg of path.split('.')) {\n const m = seg.match(/^([^\\[]+)(?:\\[(\\d+)\\])?$/); // supports \"choices[0]\"\n if (!m) return undefined;\n const key = m[1];\n const idx = m[2];\n cur = cur?.[key];\n if (cur === undefined || cur === null) return undefined;\n if (idx !== undefined) {\n if (!Array.isArray(cur)) return undefined;\n cur = cur[Number(idx)];\n }\n }\n return cur;\n } catch { return undefined; }\n};\nconst getLastItemFromNode = (nodeName) => {\n let items = [];\n try { items = items.concat($items(nodeName, 0) || []); } catch {}\n try { items = items.concat($items(nodeName, 1) || []); } catch {}\n if (!items.length) {\n try { items = $items(nodeName) || []; } catch {}\n }\n return items.length ? items[items.length - 1] : null;\n};\n// Deep scan: find the first string that looks like HTML\nconst findHtmlString = (obj) => {\n const seen = new Set();\n const looksLikeHtml = (str) => {\n const s = S(str);\n return /<\\s*html[\\s>]/i.test(s) || /<\\s*body[\\s>]/i.test(s) || /<\\/(p|div|ul|li|h[1-6])>/i.test(s);\n };\n const dfs = (val) => {\n if (val === null || val === undefined) return null;\n if (typeof val === 'string') return looksLikeHtml(val) ? val : null;\n if (typeof val !== 'object') return null;\n if (seen.has(val)) return null;\n seen.add(val);\n if (Array.isArray(val)) {\n for (const v of val) {\n const hit = dfs(v);\n if (hit) return hit;\n }\n } else {\n for (const k of Object.keys(val)) {\n const hit = dfs(val[k]);\n if (hit) return hit;\n }\n }\n return null;\n };\n return dfs(obj) || \"\";\n};\n\n// ---------- 1) Collect sheet/input data ----------\nconst firstItem = $input.first();\nconst inputData = firstItem?.json ?? {};\nconst data = inputData.data ?? {};\n\nconst FIELDS = [\n 'job_title','job_subtitle','department','employment_type','locations',\n 'is_remote_friendly','location_policy_text','mission_statement','responsibilities',\n 'tech_stack','hard_skills','soft_skills','languages','benefits_list',\n 'culture_statement','contact_info'\n];\n\nconst base = {};\nfor (const f of FIELDS) base[f] = S(getByPath(inputData, f) ?? getByPath(data, f) ?? \"\");\n\n// ---------- 2) Detect feedback / IF path ----------\nconst teamsItem = getLastItemFromNode(TEAMS_NODE_NAME);\n// IF outputs\nlet ifTrueItems = [], ifFalseItems = [];\ntry { ifTrueItems = $items(IF_NODE_NAME, 0) || []; } catch {}\ntry { ifFalseItems = $items(IF_NODE_NAME, 1) || []; } catch {}\nconst cameFromIfTrue = Boolean(ifTrueItems.length);\nconst cameFromIfFalse = Boolean(ifFalseItems.length);\n\nconst feedback = S(\n getByPath(teamsItem?.json, 'feedback') ??\n getByPath(inputData, 'feedback') ??\n getByPath(data, 'feedback') ??\n \"\"\n).trim();\n\n// ---------- 3) Get previous agent output (HTML) ----------\nlet previous_html = \"\";\nconst agentItem = getLastItemFromNode(AGENT_NODE_NAME);\nif (agentItem?.json) {\n const j = agentItem.json;\n // Try typical paths first (including array indices)\n const candidates = [\n 'output.html',\n 'output.job_description_html',\n 'output.response',\n 'output.text',\n 'response',\n 'text',\n 'data.response',\n 'data.choices[0].message.content',\n 'choices[0].message.content',\n 'message.content'\n ];\n for (const p of candidates) {\n const v = getByPath(j, p);\n if (typeof v === 'string' && /<\\s*html|<\\s*body|<\\/(p|div|ul|li|h[1-6])>/i.test(v)) {\n previous_html = v;\n break;\n }\n }\n // Fallback: deep scan\n if (!previous_html) previous_html = findHtmlString(j);\n}\n\n// ---------- 4) Determine mode ----------\nlet mode = cameFromIfFalse ? 'revise' : 'create';\n// Safety: if revise requested but no previous_html available (e.g. first run), fall back to create\nif (mode === 'revise' && !previous_html) mode = 'create';\n\n// ---------- 5) Prompt builder ----------\n// Original prompt template for CREATE mode.\n// Placeholders like {{ $json.job_title }} are substituted.\nconst CREATE_PROMPT_TEMPLATE = `\nYour task: \nUsing the structured input below, write a **complete, polished job description in English** in the style of a modern, mission-driven employer (similar to innovative EdTech / tech-forward companies).\n\n### Overall style and tone\n\n- Professional, clear, and confident.\n- Positive and mission-driven (without becoming cheesy or overhyped).\n- Inclusive and welcoming; avoid jargon where not needed.\n- Use active voice, strong verbs, and concise sentences.\n- Write for experienced professionals (mid to senior), but keep it approachable.\n- No emojis, no hashtags.\n\n### Structure & content\n\nFollow this structure (using headings in ALL CAPS):\n\n1. **Mission hook & role introduction (1\u20132 short paragraphs)** \n - Start with a short, inspiring mission statement using or adapting:\n - {{ $json.mission_statement }}\n - Immediately introduce the role with job title, department, employment type, and location context, similar to: \n - \u201cStrengthen our {{ $json.department }} Team {{ $json.employment_type }} as a {{ $json.job_title }} {{ $json.job_subtitle }} at our locations in {{ $json.locations }}{{ $json.is_remote_friendly }}.\u201d\n - If available, briefly mention the location/remote policy in a natural way:\n - Use {{ $json.location_policy_text }} appropriately.\n - Keep this section crisp and inviting.\n\n2. **Short role overview (1 paragraph)** \n - Summarize what the role focuses on, in 2\u20134 sentences:\n - Use the themes from {{ $json.responsibilities }}, {{ $json.tech_stack }}, {{ $json.department }}.\n - Emphasize impact, cross-functional collaboration, and key topics (e.g., digital transformation, AI, automation, etc. if relevant from input).\n - Do not list responsibilities here; this is a narrative overview.\n\n3. **YOUR RESPONSIBILITIES (bullet list)** \n - Add the heading: \\`YOUR RESPONSIBILITIES\\`\n - Convert {{ $json.responsibilities }} into **clear, action-oriented bullet points**.\n - Each bullet:\n - Starts with a strong verb in present tense (e.g., \u201cLead\u2026\u201d, \u201cDesign\u2026\u201d, \u201cImplement\u2026\u201d, \u201cCollaborate\u2026\u201d).\n - Focuses on one main idea; avoid overly long multi-clause bullets.\n - If {{ $json.tech_stack }} is relevant, integrate tools/technologies naturally into the bullets (e.g., \u201cusing tools such as \u2026\u201d).\n - Aim for ~5\u20138 bullets where possible (depending on input length/quality).\n\n4. **YOUR PROFILE (bullet list)** \n - Add the heading: \\`YOUR PROFILE\\`\n - Combine and rephrase:\n - {{ $json.hard_skills }}\n - {{ $json.soft_skills }}\n - {{ $json.languages }}\n - Turn them into candidate-focused bullet points (e.g., \u201cSeveral years of experience in\u2026\u201d, \u201cHands-on expertise with\u2026\u201d, \u201cYou enjoy\u2026\u201d, \u201cYou communicate fluently in\u2026\u201d).\n - Group related items logically (e.g., experience, tools, mindset, languages).\n - Keep it realistic and not excessively long (again ~5\u20138 bullets when possible).\n\n5. **WE OFFER (bullet list)** \n - Add the heading: \\`WE OFFER\\`\n - Use {{ $json.benefits_list }} and turn it into attractive but concrete bullet points.\n - Mix:\n - Work model & flexibility (remote, hybrid, working hours)\n - Learning & development opportunities\n - Culture/values-related benefits\n - Financial/transport/perks (e.g. commute support, bonuses, etc.)\n - Avoid duplicating the culture section; keep this benefits-focused.\n\n6. **Company & culture paragraph** \n - Using {{ $json.culture_statement }} and {{ $json.mission_statement }}, write 1\u20132 short paragraphs that:\n - Briefly describe what the organization does (industry, size or positioning if given).\n - Highlight being tech-forward / data-driven / innovative **only if** implied by the inputs.\n - Emphasize inclusive culture and collaboration.\n - Keep it factual and not overly long.\n\n7. **Call to action & application information** \n - Encourage candidates to apply in a friendly, straightforward sentence or two.\n - If {{ $json.contact_info }} is provided:\n - Mention how to apply or who to contact using this information in a natural way (no raw JSON or labels).\n - If no contact info is available, use a generic call to apply via the careers page.\n\n8. **Diversity & inclusion statement (short, clear)** \n - End with a concise, inclusive statement in line with modern standards, inspired by but not copying typical texts.\n - Emphasize that all qualified applicants are welcome regardless of background, and that the company values diversity and an inclusive culture.\n - Keep it to 2\u20134 sentences, not a long legal block.\n\n### Formatting rules\n\n- **Output must be valid HTML.**\n- Wrap the entire job description in semantic HTML tags (e.g. use <html>, <body>, and appropriate sections or <div> containers).\n- Use headings in ALL CAPS as text inside heading tags (e.g. <h2>YOUR RESPONSIBILITIES</h2>).\n- Use <p> tags for paragraphs and <ul><li>...</li></ul> for bullet lists.\n- Do **not** use Markdown.\n- Do **not** mention the JSON fields, variables, or any technical prompt details in the output.\n- Do **not** quote the input verbatim if it\u2019s awkward; you may lightly edit/standardize for style and grammar.\n- If certain inputs are missing or empty, **gracefully omit** the related parts and still produce a coherent, polished job description.\n- Do **not** add any explanations, intros, or meta-comments before or after the HTML. The output must be the HTML job description only.\n\nInput data to use\n\n- Job title: {{ $json.job_title }}\n- Job subtitle: {{ $json.job_subtitle }}\n- Department / team: {{ $json.department }}\n- Employment type: {{ $json.employment_type }}\n- Locations: {{ $json.locations }}\n- Remote-friendliness: {{ $json.is_remote_friendly }}\n- Location policy text: {{ $json.location_policy_text }}\n- Mission statement: {{ $json.mission_statement }}\n- Responsibilities: {{ $json.responsibilities }}\n- Tech stack: {{ $json.tech_stack }}\n- Hard skills: {{ $json.hard_skills }}\n- Soft skills: {{ $json.soft_skills }}\n- Languages: {{ $json.languages }}\n- Benefits list: {{ $json.benefits_list }}\n- Culture statement: {{ $json.culture_statement }}\n- Contact info / application details: {{ $json.contact_info }}\n`.trim();\n\n// Placeholder substitution: {{ $json.field }} -> actual value from sheet\nconst fillCreateTemplate = (tpl, ctx) =>\n tpl.replace(/\\{\\{\\s*\\$json\\.([a-zA-Z0-9_]+)\\s*\\}\\}/g, (_, key) => S(ctx[key]).trim());\n\n// Revise prompts\nconst REVISE_SYSTEM = `\nYou are an expert HR copywriter. You will revise an existing job description based on feedback.\nRules:\n- Apply the feedback precisely and minimally; do not rewrite from scratch.\n- Preserve structure, section order, headings, and any content not mentioned in the feedback.\n- Keep the tone professional, clear, modern, and inclusive.\n- Output must be valid HTML only (no explanations).\n`.trim();\n\nconst makeReviseUser = (prevHtml, fb) => `\nFeedback:\n${fb || '(no additional feedback provided)'}\n\nPrevious draft HTML:\n${prevHtml}\n\nInstructions:\n- Update only the necessary parts to address the feedback.\n- Keep HTML valid; keep headings in ALL CAPS as they are.\n- Do not insert new sections unless explicitly requested by the feedback.\n`.trim();\n\n// ---------- 6) Assemble system/user messages ----------\nlet system, user;\nif (mode === 'create') {\n system = `You are an expert HR copywriter specialized in creating clear, engaging, and modern job descriptions for tech- and transformation-focused roles.`;\n user = fillCreateTemplate(CREATE_PROMPT_TEMPLATE, base);\n} else {\n system = REVISE_SYSTEM;\n user = makeReviseUser(previous_html, feedback);\n}\n\n// ---------- 7) Return ----------\nreturn [{\n json: {\n mode,\n system,\n user,\n feedback,\n previous_html,\n // Useful for debugging in the n8n UI:\n _debug: {\n cameFromIfTrue,\n cameFromIfFalse,\n agentItemKeys: agentItem?.json ? Object.keys(agentItem.json) : [],\n foundHtml: Boolean(previous_html)\n },\n // Pass sheet data downstream if needed\n ...base\n }\n}];\n"
},
"typeVersion": 2
},
{
"id": "177ea090-e232-479f-bb8b-00e692970807",
"name": "JD-Writer",
"type": "@n8n/n8n-nodes-langchain.agent",
"position": [
2416,
240
],
"parameters": {
"text": "={{ $json.user }}\n\n",
"options": {
"systemMessage": "={{ $json.system }}"
},
"promptType": "define",
"hasOutputParser": true
},
"typeVersion": 3.1
},
{
"id": "eac6fe8f-0c99-434a-942c-e10ffacb3868",
"name": "Send JD to Teams for approval",
"type": "n8n-nodes-base.microsoftTeams",
"position": [
2768,
240
],
"parameters": {
"chatId": {
"__rl": true,
"mode": "list",
"value": "YOUR_MS_TEAMS_CHAT_ID",
"cachedResultUrl": "https://teams.microsoft.com/l/chat/YOUR_CHAT_URL",
"cachedResultName": "Your_Teams_Chat"
},
"message": "={{ $json.output }}",
"options": {},
"resource": "chatMessage",
"operation": "sendAndWait",
"formFields": {
"values": [
{
"fieldType": "dropdown",
"fieldLabel": "Approved?",
"defaultValue": "yes",
"fieldOptions": {
"values": [
{
"option": "yes"
},
{
"option": "no"
}
]
},
"requiredField": true
},
{
"fieldLabel": "feedback",
"placeholder": "what should i change or improve?"
}
]
},
"responseType": "customForm"
},
"typeVersion": 2
},
{
"id": "fb85dea3-ae4a-42e6-9c55-ad89c0af4fa8",
"name": "Convert approved JD to PDF",
"type": "n8n-nodes-htmlcsstopdf.htmlcsstopdf",
"position": [
3200,
272
],
"parameters": {
"html_content": "={{ $('JD-Writer').item.json.output }}",
"output_format": "file"
},
"typeVersion": 1
},
{
"id": "4177edba-cb3e-485d-89c2-9a5a2b29e08e",
"name": "Upload PDF to Google Drive",
"type": "n8n-nodes-base.googleDrive",
"position": [
3408,
272
],
"parameters": {
"name": "={{ $('Append row in sheet').item.json.job_title }}",
"driveId": {
"__rl": true,
"mode": "list",
"value": "My Drive"
},
"options": {},
"folderId": {
"__rl": true,
"mode": "id",
"value": "={{ $('Create folder').item.json.id }}"
}
},
"typeVersion": 3
},
{
"id": "830be834-6ba7-4170-ae21-2bd2462078fd",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
-64,
0
],
"parameters": {
"width": 520,
"height": 900,
"content": "## Auto-generate job descriptions from briefing notes\n\nTurn a **Google Docs role briefing transcript** (e.g. from a Hiring Manager / Recruiter conversation) into a polished, structured **job description** in HTML and PDF -- fully automated with AI and a human-in-the-loop approval via Microsoft Teams.\n\n## How it works\n\n1. A new Google Doc appears in a watched Drive folder (the briefing transcript).\n2. The doc is moved into a timestamped subfolder for organization.\n3. An AI Agent (OpenAI) reads the transcript and extracts structured job data (title, responsibilities, skills, benefits, etc.) into JSON.\n4. The extracted data is logged in a Google Sheets tracker.\n5. A second AI Agent (JD-Writer) generates a full HTML job description from the structured data.\n6. The draft is sent to **Microsoft Teams** for review with an approve/reject form.\n7. If approved: the HTML is converted to PDF and uploaded to Google Drive.\n8. If rejected: feedback is looped back and the JD is revised automatically.\n\n## Setup steps\n\n1. **Google Drive & Docs** -- Create OAuth2 credentials. Set the watched folder ID in the Google Drive Trigger node.\n2. **Google Sheets** -- Create a spreadsheet with columns matching the job data schema (job_title, department, responsibilities, etc.). Update the Sheet ID.\n3. **OpenAI** -- Add your API key. Used for both the data extraction agent and the JD-Writer agent.\n4. **Microsoft Teams** -- Create OAuth2 credentials. Set the Teams chat ID in the approval node.\n5. **HTML-to-PDF** -- Install the community node `n8n-nodes-htmlcsstopdf` (self-hosted only). Add the API credential.\n\n> **Community node required:** `n8n-nodes-htmlcsstopdf` -- this template works on **self-hosted n8n only**."
},
"typeVersion": 1
},
{
"id": "9a6d4027-7942-4a83-849e-266549729aef",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
512,
192
],
"parameters": {
"color": 7,
"width": 360,
"height": 130,
"content": "### Step 1: Trigger & File Organization\nGoogle Drive Trigger watches a folder for new Google Docs. When detected, a timestamped subfolder is created and the document is moved into it."
},
"typeVersion": 1
},
{
"id": "bec98195-060c-4ec7-92a5-4a3fadaa4c9a",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
1264,
176
],
"parameters": {
"color": 7,
"width": 380,
"height": 140,
"content": "### Step 2: AI Data Extraction\nThe Google Doc content is read and passed to an OpenAI AI Agent that extracts structured job data (title, department, responsibilities, skills, benefits, etc.) from the German transcript into a JSON schema."
},
"typeVersion": 1
},
{
"id": "3dab8a4e-6d68-4e62-aa13-65d05ffdda56",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
2352,
16
],
"parameters": {
"color": 7,
"width": 400,
"content": "### Step 3: JD Generation & Approval Loop\nExtracted data is logged in Google Sheets, then an Input Builder prepares a prompt for the JD-Writer AI Agent. The generated HTML job description is sent to Microsoft Teams for review. If rejected, feedback is looped back for revision."
},
"typeVersion": 1
},
{
"id": "563eb0c3-c67d-49af-ba03-32d7bb1e8902",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
3184,
80
],
"parameters": {
"color": 7,
"width": 360,
"height": 130,
"content": "### Step 4: PDF Export & Upload\nOnce approved, the HTML job description is converted to PDF via HTML-to-PDF API and uploaded to the Google Drive subfolder alongside the original briefing document."
},
"typeVersion": 1
}
],
"active": false,
"settings": {
"binaryMode": "separate",
"availableInMCP": false,
"executionOrder": "v1"
},
"versionId": "cd128e71-7267-4241-8e79-babf37fa4df6",
"connections": {
"JD-Writer": {
"main": [
[
{
"node": "Send JD to Teams for approval",
"type": "main",
"index": 0
}
]
]
},
"OpenAI Chat Model": {
"ai_languageModel": [
[
{
"node": "Extract job data from transcript",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Google Drive Trigger": {
"main": [
[
{
"node": "Create timestamped subfolder",
"type": "main",
"index": 0
}
]
]
},
"Parse AI JSON output": {
"main": [
[
{
"node": "Log job data in Google Sheets",
"type": "main",
"index": 0
}
]
]
},
"Build JD-Writer prompt": {
"main": [
[
{
"node": "JD-Writer",
"type": "main",
"index": 0
}
]
]
},
"Check if JD is approved": {
"main": [
[
{
"node": "Convert approved JD to PDF",
"type": "main",
"index": 0
}
],
[
{
"node": "Build JD-Writer prompt",
"type": "main",
"index": 0
}
]
]
},
"Read Google Doc content": {
"main": [
[
{
"node": "Extract job data from transcript",
"type": "main",
"index": 0
}
]
]
},
"Convert approved JD to PDF": {
"main": [
[
{
"node": "Upload PDF to Google Drive",
"type": "main",
"index": 0
}
]
]
},
"Create timestamped subfolder": {
"main": [
[
{
"node": "Move briefing doc to subfolder",
"type": "main",
"index": 0
}
]
]
},
"Log job data in Google Sheets": {
"main": [
[
{
"node": "Build JD-Writer prompt",
"type": "main",
"index": 0
}
]
]
},
"OpenAI Chat Model (JD-Writer)": {
"ai_languageModel": [
[
{
"node": "JD-Writer",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Send JD to Teams for approval": {
"main": [
[
{
"node": "Check if JD is approved",
"type": "main",
"index": 0
}
]
]
},
"Move briefing doc to subfolder": {
"main": [
[
{
"node": "Read Google Doc content",
"type": "main",
"index": 0
}
]
]
},
"Extract job data from transcript": {
"main": [
[
{
"node": "Parse AI JSON output",
"type": "main",
"index": 0
}
]
]
}
}
}
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
Recruiters, HR teams, and hiring managers who conduct role briefing conversations and want to convert their meeting notes into polished, structured job descriptions automatically -- without manual copywriting.
Source: https://n8n.io/workflows/14065/ — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
This workflow automatically converts unstructured internal documentation into clear, actionable Standard Operating Procedures (SOPs).
This comprehensive n8n workflow automates the entire Meta (Facebook/Instagram) advertising process, from asset analysis to ad creation. It combines AI-powered content analysis with automated ad deploy
Transcript Evalu8r V2 is a robust browser-based transcript analysis tool powered by Deepgram’s speech-to-text API and built into an n8n workflow template. This release introduces full in-browser audio
Transcript Evalu8r is an AI-powered transcript analysis workflow that automates the processing, visualization, and evaluation of transcribed conversations. This n8n workflow template is designed to he
This workflow is for job seekers who want to automate their entire application pipeline — from discovering job postings to generating personalized cover letters, CVs, and organizing everything in Goog