AutomationFlowsAI & RAG › Match Cvs to Job Descriptions with Gemini Analysis & Email Reports

Match Cvs to Job Descriptions with Gemini Analysis & Email Reports

ByMychel Garzon @mychel-garzon on n8n.io

This workflow uses AI to automatically analyze a candidate’s CV against any job posting. It extracts key skills, requirements, and gaps, then generates a clear fit summary, recommendations, and optimization tips. Candidates also receive a structured email report, helping them…

Webhook trigger★★★★☆ complexityAI-powered18 nodesHTTP RequestGoogle Gemini ChatOutput Parser StructuredGmailAgent
AI & RAG Trigger: Webhook Nodes: 18 Complexity: ★★★★☆ AI nodes: yes Added:

This workflow corresponds to n8n.io template #8637 — we link there as the canonical source.

This workflow follows the Agent → Gmail 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 →

Download .json
{
  "id": "TzPKUZdrlcTUenHQ",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "AI CV Optimizer",
  "tags": [],
  "nodes": [
    {
      "id": "08a1bb78-33db-4698-9970-c5b38c25835c",
      "name": " Webhook - CV Optimizer Form",
      "type": "n8n-nodes-base.webhook",
      "position": [
        -224,
        272
      ],
      "parameters": {
        "path": "cv-optimizer",
        "options": {},
        "responseMode": "responseNode",
        "multipleMethods": true
      },
      "typeVersion": 2.1
    },
    {
      "id": "310ffa59-1f26-4ddd-881d-c5819b89d7f1",
      "name": "Webhook Response - HTML Form",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        0,
        176
      ],
      "parameters": {
        "options": {},
        "respondWith": "text",
        "responseBody": "=<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"UTF-8\" />\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n  <title>AI CV Optimizer</title>\n  <style>\n    :root {\n      --primary: #2563eb;\n      --primary-hover: #1e4ed8;\n      --gray-light: #f9fafb;\n      --gray: #ccc;\n      --text-dark: #333;\n      --radius: 10px;\n    }\n    body {\n      font-family: Arial, sans-serif;\n      background: var(--gray-light);\n      margin: 0;\n      padding: 0;\n      display: flex;\n      justify-content: center;\n      align-items: center;\n      min-height: 100vh;\n    }\n    .container {\n      background: white;\n      padding: 2rem;\n      border-radius: var(--radius);\n      box-shadow: 0 6px 16px rgba(0,0,0,0.1);\n      max-width: 420px;\n      width: 100%;\n    }\n    h2 {\n      margin-bottom: 1.5rem;\n      color: var(--text-dark);\n      font-size: 1.5rem;\n      text-align: center;\n    }\n    label {\n      font-weight: 600;\n      display: block;\n      margin: 1rem 0 0.5rem;\n    }\n    input[type=\"file\"], input[type=\"text\"], input[type=\"email\"] {\n      width: 100%;\n      padding: 0.75rem;\n      border: 1px solid var(--gray);\n      border-radius: var(--radius);\n      font-size: 0.95rem;\n      box-sizing: border-box;\n    }\n    input:focus {\n      border-color: var(--primary);\n      outline: none;\n      box-shadow: 0 0 0 3px rgba(37,99,235,0.15);\n    }\n    button {\n      margin-top: 1.5rem;\n      padding: 0.9rem;\n      background: var(--primary);\n      color: white;\n      border: none;\n      border-radius: var(--radius);\n      width: 100%;\n      font-size: 1rem;\n      font-weight: 600;\n      cursor: pointer;\n      transition: background 0.2s ease;\n    }\n    button:hover {\n      background: var(--primary-hover);\n    }\n    button:disabled {\n      background: #9ca3af;\n      cursor: not-allowed;\n    }\n    .status {\n      margin-top: 1rem;\n      font-size: 0.9rem;\n      text-align: center;\n    }\n    .status.success { color: green; }\n    .status.error { color: red; }\n  </style>\n</head>\n<body>\n  <div class=\"container\">\n    <h2>AI CV Optimizer</h2>\n    <form id=\"cvForm\">\n      <label for=\"cv\">Upload your CV (PDF):</label>\n      <input type=\"file\" id=\"cv\" name=\"cv\" accept=\"application/pdf\" required />\n\n      <label for=\"job_url\">Job Posting URL:</label>\n      <input type=\"text\" id=\"job_url\" name=\"job_url\" placeholder=\"https://linkedin.com/job/123\" required />\n\n      <label for=\"email\">Your Email:</label>\n      <input type=\"email\" id=\"email\" name=\"email\" placeholder=\"you@example.com\" required/>\n\n      <button type=\"submit\">Check My CV</button>\n      <div class=\"status\" id=\"status\"></div>\n    </form>\n  </div>\n\n  <script>\n    const form = document.getElementById(\"cvForm\");\n    const statusDiv = document.getElementById(\"status\");\n\n    form.addEventListener(\"submit\", async (e) => {\n      e.preventDefault();\n\n      const formData = new FormData(form);\n      statusDiv.textContent = \"\u23f3 Uploading and analyzing your CV...\";\n      statusDiv.className = \"status\";\n\n      try {\n        const res = await fetch(\"https://n8nworkflow.eu/webhook-test/cv-optimizer\", {\n          method: \"POST\",\n          body: formData\n        });\n\n        if (!res.ok) {\n          throw new Error(\"Server error: \" + res.statusText);\n        }\n\n        const data = await res.json();\n        console.log(\"n8n response:\", data);\n        statusDiv.textContent = \"\u2705 CV submitted successfully! Check your email for results.\";\n        statusDiv.className = \"status success\";\n      } catch (err) {\n        console.error(err);\n        statusDiv.textContent = \"\u274c Error: \" + err.message;\n        statusDiv.className = \"status error\";\n      }\n    });\n  </script>\n</body>\n</html>"
      },
      "typeVersion": 1.4
    },
    {
      "id": "5f221527-5476-49cb-a166-eb3213f5d4a6",
      "name": "Extract CV Text (PDF)",
      "type": "n8n-nodes-base.extractFromFile",
      "position": [
        0,
        384
      ],
      "parameters": {
        "options": {},
        "operation": "pdf",
        "binaryPropertyName": "=cv"
      },
      "typeVersion": 1
    },
    {
      "id": "75eb814c-8d04-4310-bc01-3fcaf1640634",
      "name": "Prepare CV Text",
      "type": "n8n-nodes-base.set",
      "position": [
        224,
        384
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "be5e5936-7940-43be-b905-62b54d9db076",
              "name": "cv_text",
              "type": "string",
              "value": "={{ $json.text }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "da50a5a9-a727-45f9-b07c-e75844f89d45",
      "name": "Fetch Job Posting",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        448,
        304
      ],
      "parameters": {
        "url": "={{ $(' Webhook - CV Optimizer Form').item.json.body.job_url }}",
        "options": {}
      },
      "typeVersion": 4.2
    },
    {
      "id": "405c1c1d-afa1-4b9e-a595-a7eef3772303",
      "name": "Merge CV + Job Data",
      "type": "n8n-nodes-base.merge",
      "position": [
        896,
        368
      ],
      "parameters": {
        "mode": "combine",
        "options": {},
        "combineBy": "combineByPosition"
      },
      "typeVersion": 3.2
    },
    {
      "id": "f7e1ebf8-5e4e-48b8-a384-d88f53d771bb",
      "name": " Gemini Model - Primary",
      "type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
      "position": [
        1120,
        592
      ],
      "parameters": {
        "options": {}
      },
      "credentials": {
        "googlePalmApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "cf43c082-b2ae-42e0-8d3d-83db4a4d9f9d",
      "name": "Parse AI JSON Output",
      "type": "@n8n/n8n-nodes-langchain.outputParserStructured",
      "position": [
        1248,
        592
      ],
      "parameters": {
        "autoFix": true,
        "jsonSchemaExample": "{\n  \"job_title\": \"Frontend Developer (React)\",\n  \"location\": \"Helsinki, Finland\",\n  \"fit_summary\": \"The candidate has solid React, TypeScript, and JavaScript experience, supported by practical work on full-stack applications. They also demonstrate CI/CD knowledge and testing skills (Cypress, Jest), which align well with the role. However, the CV does not explicitly highlight advanced CSS frameworks or accessibility practices that are emphasized in the job description.\",\n  \"recommendation\": \"Consider\",\n  \"fit_score\": 7,\n  \"missing_critical\": [\n    \"Advanced CSS framework experience (e.g., Tailwind, Material UI)\",\n    \"Accessibility (WCAG) best practices\"\n  ],\n  \"cv_optimization\": \"Add a section highlighting hands-on experience with CSS frameworks (Tailwind, Material UI) and accessibility best practices (WCAG). Include concrete project examples that demonstrate user-focused design and frontend performance improvements.\",\n  \"final_recommendation\": \" The candidate is a good potential match but should strengthen their CV by explicitly mentioning CSS framework expertise and accessibility knowledge to move from 'Consider' to a stronger 'Apply'.\"\n}"
      },
      "typeVersion": 1.3
    },
    {
      "id": "51bd8f61-dea2-4763-beb3-d0171ca55660",
      "name": "Gemini Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
      "position": [
        1328,
        800
      ],
      "parameters": {
        "options": {}
      },
      "credentials": {
        "googlePalmApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "a0bbfaab-6717-4a88-9766-97d70dec3f03",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -224,
        -304
      ],
      "parameters": {
        "width": 512,
        "height": 272,
        "content": "## AI CV Optimizer: Match Your CV to Job Descriptions with AI\n\nThis workflow uses AI to automatically analyze a candidate\u2019s CV against any job posting. It extracts key skills, requirements, and gaps, then generates a clear fit summary, recommendations, and optimization tips. Candidates also receive a structured email report, helping them improve their CV and focus on the right roles.\n\nNo more guesswork, the workflow delivers objective. \n### AI-powered career insights in minutes."
      },
      "typeVersion": 1
    },
    {
      "id": "91fb8dbc-2126-47dc-a4ae-a42d8b9c4397",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1152,
        192
      ],
      "parameters": {
        "color": 6,
        "width": 224,
        "content": "### AI - Compare CV with Job\n\nYou can adjust the AI Agent prompt for output schema, scoring, or language.\n"
      },
      "typeVersion": 1
    },
    {
      "id": "0343291b-6da2-4155-9a52-164f4c4ab1d1",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1088,
        736
      ],
      "parameters": {
        "color": 5,
        "width": 208,
        "height": 112,
        "content": "###  Gemini / AI \nCredentials:  \nUse **Google Gemini/PaLM** credential."
      },
      "typeVersion": 1
    },
    {
      "id": "14e53603-87f5-4d4e-92ef-216eb5917964",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1584,
        528
      ],
      "parameters": {
        "color": 5,
        "width": 192,
        "height": 96,
        "content": "### Send report \nSend Email\nCredentials: Use **Gmail OAuth2** credential. (required)."
      },
      "typeVersion": 1
    },
    {
      "id": "c05f35ba-eeb9-4752-80ef-23eefbcca0f2",
      "name": "Send report ",
      "type": "n8n-nodes-base.gmail",
      "position": [
        1616,
        368
      ],
      "parameters": {
        "sendTo": "={{ $(' Webhook - CV Optimizer Form').item.json.body.email }}",
        "message": "=<p>Hi,</p>\n\n<p>We\u2019ve analyzed your CV against the job posting for <b>{{ $json.output.job_title }}</b> in <b>{{ $json.output.location }}</b>.</p>\n\n<p><b>Summary:</b><br>\n{{ $json.output.fit_summary }}</p>\n\n<p><b>Critical gaps identified:</b></p>\n<ul>\n  {{ $json.output.missing_critical.map(gap => `<li>${gap}</li>`).join(\"\") }}\n</ul>\n\n<p><b>FinalFit Score:</b> {{ $json.output.fit_score }} / 10 <b> Recommendation: <b/> {{ $json.output.recommendation }}</p>\n<p>\n<b>Tips for improving CV: </b>{{ $json.output.cv_optimization }}\n</p>\n<p><b>AI Advice:</b><br>\n{{ $json.output.final_recommendation }}\n</p>\n\n<p>Best of luck with your applications,<br>AI CV Optimizer</p>",
        "options": {},
        "subject": "=Your CV Review: {{ $json.output.job_title }} in {{ $json.output.location }}"
      },
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.1
    },
    {
      "id": "8918b88f-f90d-4e68-b28b-84dd68ad854a",
      "name": "Sticky Note11",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -16,
        16
      ],
      "parameters": {
        "color": 5,
        "width": 160,
        "height": 144,
        "content": "### Submission form\n**POST (required)**\n- `Upload CV`\n- `Job Link` \n- `email` "
      },
      "typeVersion": 1
    },
    {
      "id": "03491b28-4df7-4be4-88b1-4128e92fe9b7",
      "name": "AI CV Analyzer",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        1152,
        368
      ],
      "parameters": {
        "text": "=CV:\n{{ $json[\"cv_text\"] ? $json[\"cv_text\"].slice(0, 18000) : \"\" }}\n\nJOB:\n{{ $json[\"job_text\"] ? $json[\"job_text\"].slice(0, 18000) : \"\" }}\n\nTasks:\n1) Extract the \"job_title\" and \"location\".  \n2) Identify \"matched_skills\" and \"missing_critical\" skills.  \n3) Write a short \"advice\" paragraph (max 100 words).  \n4) Write a friendly \"email_body\" addressed to the candidate, summarizing:\n   - Job title & location  \n   - What they already match well  \n   - Areas to improve or learn for better fit  \n   - A motivating closing sentence \n5)5) Write a short final_recommendation paragraph.\n\n6) Provide **two recommendations**:  \n   - \"recommendation\": Apply / Consider / Not a fit (must align with fit_score)  \n   - \"cv_optimization\": Clear advice on how to improve the CV for similar roles.  \n6) The \"fit_score\" must always align with \"recommendation\":  \n   - Apply \u2192 fit_score between 9 and 10  \n   - Consider \u2192 fit_score between 7 and 8  \n   - Not a fit \u2192 fit_score between 1 and 6  \n\n\u26a0\ufe0f IMPORTANT: Return ONLY valid JSON in this schema:\n{\n  \"job_title\": \"string\",\n  \"location\": \"string\",\n  \"fit_score\": 0,\n  \"recommendation\": \"Apply|Consider|Not a fit\",\n\"final_recommendation\": [\"string\"],\n  \"matched_skills\": [\"string\"],\n  \"missing_critical\": [\"string\"],\n  \"advice\": \"string\",\n  \"cv_optimization\": \"string\",\n  \"email_body\": \"string\"\n}",
        "options": {
          "systemMessage": "You are a professional career assistant.  \nYour task is to compare a candidate\u2019s CV with a job description and return a structured JSON output.  \n\n\u26a0\ufe0f RULES:  \n- Follow the schema exactly.  \n- Every field must be included.  \n- All values must be plain text, arrays, or integers \u2014 never nested objects.  \n- `fit_score` must be an integer (1\u201310) aligned with `recommendation`:  \n   - \"Apply\" \u2192 9\u201310  \n   - \"Consider\" \u2192 7\u20138  \n   - \"Not a fit\" \u2192 1\u20136  \n- Do not add responsibilities, requirements, or benefits.  \n- Do not include any text outside the JSON.  \n\nSchema:\n{\n  \"job_title\": \"string\",\n  \"location\": \"string\",\n  \"fit_score\": 0,\n  \"recommendation\": \"Apply|Consider|Not a fit\",\n  \"matched_skills\": [\"string\"],\n  \"missing_critical\": [\"string\"],\n  \"advice\": \"string\",\n  \"cv_optimization\": \"string\",\n  \"email_body\": \"string\",\n\"final_recommendation\": : \"string\",\n\n}"
        },
        "promptType": "define",
        "hasOutputParser": true
      },
      "typeVersion": 2.2
    },
    {
      "id": "798f0b85-bd22-4ad8-a9c3-a3535d955069",
      "name": "Job Text Cleaner",
      "type": "n8n-nodes-base.code",
      "position": [
        672,
        304
      ],
      "parameters": {
        "jsCode": "const raw = $json.data || \"\";\nconst text = raw\n  .replace(/<script[\\s\\S]*?<\\/script>/gi, \"\")\n  .replace(/<style[\\s\\S]*?<\\/style>/gi, \"\")\n  .replace(/<\\/?[^>]+(>|$)/g, \" \")\n  .replace(/\\s+/g, \" \")\n  .trim();\nreturn [{ job_text: text }];"
      },
      "typeVersion": 2
    },
    {
      "id": "eef4754b-adb0-4859-93df-cfc361f8175c",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        384,
        -304
      ],
      "parameters": {
        "color": 2,
        "width": 304,
        "height": 144,
        "content": "## Customization checklist\n\u2705 Update Webhook URL\n\u2705 Configure Google Gemini credentials\n\u2705 Set Gmail OAuth2 credentials\n\u2705 Adjust AI prompt if schema changes"
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "039ce290-aa42-4e4a-a346-65841445a6a6",
  "connections": {
    "Gemini Model": {
      "ai_languageModel": [
        [
          {
            "node": "Parse AI JSON Output",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "AI CV Analyzer": {
      "main": [
        [
          {
            "node": "Send report ",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare CV Text": {
      "main": [
        [
          {
            "node": "Fetch Job Posting",
            "type": "main",
            "index": 0
          },
          {
            "node": "Merge CV + Job Data",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Job Text Cleaner": {
      "main": [
        [
          {
            "node": "Merge CV + Job Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Job Posting": {
      "main": [
        [
          {
            "node": "Job Text Cleaner",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge CV + Job Data": {
      "main": [
        [
          {
            "node": "AI CV Analyzer",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse AI JSON Output": {
      "ai_outputParser": [
        [
          {
            "node": "AI CV Analyzer",
            "type": "ai_outputParser",
            "index": 0
          }
        ]
      ]
    },
    "Extract CV Text (PDF)": {
      "main": [
        [
          {
            "node": "Prepare CV Text",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    " Gemini Model - Primary": {
      "ai_languageModel": [
        [
          {
            "node": "AI CV Analyzer",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    " Webhook - CV Optimizer Form": {
      "main": [
        [
          {
            "node": "Webhook Response - HTML Form",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Extract CV Text (PDF)",
            "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.

Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

About this workflow

This workflow uses AI to automatically analyze a candidate’s CV against any job posting. It extracts key skills, requirements, and gaps, then generates a clear fit summary, recommendations, and optimization tips. Candidates also receive a structured email report, helping them…

Source: https://n8n.io/workflows/8637/ — original creator credit. Request a take-down →

More AI & RAG workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

AI & RAG

leads. Uses supabase, gmail, formTrigger, httpRequest. Webhook trigger; 62 nodes.

Supabase, Gmail, Form Trigger +13
AI & RAG

⏺ 🚀 How it works

Agent, Anthropic Chat, Output Parser Structured +6
AI & RAG

LinkedIn_Job_Hunt_and_Cover_Letter. Uses outputParserStructured, outputParserAutofixing, googleDrive, agent. Scheduled trigger; 85 nodes.

Output Parser Structured, Output Parser Autofixing, Google Drive +6
AI & RAG

LineOA. Uses httpRequest, agent, lmChatGoogleGemini, outputParserStructured. Webhook trigger; 69 nodes.

HTTP Request, Agent, Google Gemini Chat +3
AI & RAG

Resume Screening & Behavioral Interviews with Gemini, Elevenlabs, & Notion ATS copy. Uses outputParserStructured, chainLlm, googleDrive, stickyNote. Webhook trigger; 67 nodes.

Output Parser Structured, Chain Llm, Google Drive +9