{
  "id": "xb8FwafQZg1wepQB",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "CV Parser - Email",
  "tags": [],
  "nodes": [
    {
      "id": "f2e88168-af09-48bf-b921-5e0a2ca0bc02",
      "name": "Message a model",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "position": [
        -768,
        -1200
      ],
      "parameters": {
        "modelId": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4.1-nano",
          "cachedResultName": "GPT-4.1-NANO"
        },
        "options": {},
        "messages": {
          "values": [
            {
              "content": "=You're an HR assistant. I will provide you with text from a PDF document. \n\n1. First, determine if it's a CV/resume.\n2. If it is, extract the following fields:\n- full_name\n- email\n- phone\n- location\n- job_title\n- years_experience\n- skills (as an array)\n- education (as an array of strings)\n- languages\n- certifications\n- linkedin\n- availability\n\n3. Return the result as a JSON object with those fields. \nIf it's not a CV, just return: { \"is_cv\": false }\nHere is the document text:\n\n{{ $json.body.ParsedResults[0].ParsedText }}\n"
            }
          ]
        },
        "jsonOutput": true
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.8
    },
    {
      "id": "093e9f3f-22a7-4923-8066-35a31e06e7bd",
      "name": "HTTP Request",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -1216,
        -1200
      ],
      "parameters": {
        "url": "https://api.ocr.space/parse/image",
        "method": "POST",
        "options": {
          "timeout": 40000,
          "response": {
            "response": {
              "neverError": true,
              "fullResponse": true
            }
          }
        },
        "sendBody": true,
        "contentType": "multipart-form-data",
        "sendHeaders": true,
        "bodyParameters": {
          "parameters": [
            {
              "name": "file",
              "parameterType": "formBinaryData",
              "inputDataFieldName": "data"
            }
          ]
        },
        "headerParameters": {
          "parameters": [
            {
              "name": "apikey",
              "value": "={{ $vars.OCR_SPACE_API_KEY }}"
            }
          ]
        }
      },
      "typeVersion": 4.2,
      "alwaysOutputData": true
    },
    {
      "id": "6cd96d95-3475-41fd-8170-735c4f237f97",
      "name": "If1",
      "type": "n8n-nodes-base.if",
      "position": [
        -992,
        -1200
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "c9c8336a-a558-4a5a-a9a3-9f717624cfb8",
              "operator": {
                "type": "number",
                "operation": "gte"
              },
              "leftValue": "={{ $json.statusCode }}",
              "rightValue": 200
            },
            {
              "id": "ef368fac-d040-454b-beda-26bc9b2518f3",
              "operator": {
                "type": "number",
                "operation": "lt"
              },
              "leftValue": "={{ $json.statusCode }}",
              "rightValue": 300
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "5be10d7b-3bd8-4062-90c0-3e93d31e0e89",
      "name": "If2",
      "type": "n8n-nodes-base.if",
      "position": [
        304,
        -1200
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "fdf2f089-2b6e-45bc-97fb-35a2df0ca330",
              "operator": {
                "type": "number",
                "operation": "equals"
              },
              "leftValue": "={{ $items(\"Get many entries\").length }}",
              "rightValue": 1
            },
            {
              "id": "14322e28-3390-4fde-a640-83037d7bee6c",
              "operator": {
                "type": "number",
                "operation": "equals"
              },
              "leftValue": "={{ Object.keys($items(\"Get many entries\")[0].json).length }}",
              "rightValue": 0
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "55c610db-85e7-4d7f-87da-55ef5a343838",
      "name": "Gmail Trigger",
      "type": "n8n-nodes-base.gmailTrigger",
      "position": [
        -1664,
        -1200
      ],
      "parameters": {
        "simple": false,
        "filters": {
          "q": "has:attachment"
        },
        "options": {
          "downloadAttachments": true
        },
        "pollTimes": {
          "item": [
            {
              "mode": "everyMinute"
            }
          ]
        }
      },
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "e2cda470-f29a-44ad-b43c-f3520b7d394f",
      "name": "Code",
      "type": "n8n-nodes-base.code",
      "position": [
        -1440,
        -1200
      ],
      "parameters": {
        "jsCode": "const attachmentsArr = Object.values($input.item.binary);\n\nconst output = [];\nfor (const attachment of attachmentsArr) {\n  if (attachment.mimeType === 'application/pdf') {\n    output.push({\n      json: {\n        fileName: attachment.fileName\n      },\n      binary: {\n        data: attachment\n      }\n    });\n  }\n}\n\nreturn output;\n"
      },
      "typeVersion": 2
    },
    {
      "id": "6edaa81b-dbee-46e1-9183-95e2ad6b2e9f",
      "name": "If",
      "type": "n8n-nodes-base.if",
      "position": [
        -368,
        -1200
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "1325d367-f8de-457a-bcfc-9f037a7360b8",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              },
              "leftValue": "={{ $json.message.content.email !== null}}",
              "rightValue": "="
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "9110b3ca-31e9-4158-9739-4ebf545cc9d4",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1664,
        -1952
      ],
      "parameters": {
        "width": 448,
        "height": 688,
        "content": "### \ud83e\udde0 CV Parser - Email to Notion\n\nThis workflow automates the extraction and organization of candidate data from CVs received via email.\n\n### \ud83d\udee0\ufe0f Workflow Overview\n\n1. **Email Trigger**  \n   Monitors a Gmail inbox for new emails with PDF attachments.\n\n2. **PDF Extraction & OCR**  \n   Filters PDF files and extracts their text content using OCR.space.\n\n3. **AI-Powered CV Parsing**  \n   Sends the extracted text to GPT to:\n   - Detect if the file is a CV/resume\n   - Extract structured data like name, email, phone, location, skills, experience, education, and more\n\n4. **Field Mapping**  \n   Aligns the extracted data with the fields in your Notion database.\n\n5. **Duplicate Check**  \n   Looks up existing entries in Notion using the phone number to avoid duplicates.\n\n6. **Record Creation**  \n   Creates a new Notion entry with the parsed candidate data if no duplicate is found.\n"
      },
      "typeVersion": 1
    },
    {
      "id": "dca82c5d-aac9-4461-b243-28ac52904742",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1184,
        -1536
      ],
      "parameters": {
        "color": 5,
        "height": 272,
        "content": "### \ud83d\udccc Notes\n\n- Only processes emails with PDF attachments  \n- Skips non-CV documents automatically  \n- Checks for duplicates by email before creating a new Notion entry\n"
      },
      "typeVersion": 1
    },
    {
      "id": "1545c208-d1c0-41c0-ada2-fdd6172317b4",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1184,
        -1952
      ],
      "parameters": {
        "color": 4,
        "height": 384,
        "content": "### \u2705 Requirements\n\n- Gmail account with OAuth2 connected in n8n  \n- OCR space API Key  \n- OpenAI API Key (must be set either manually or in the system variables) \n- Notion integration connected, with a database containing fields like:  \n  `Name`, `Email`, `Phone`, `Location`, `Experience`, `Education`, `Languages`, `LinkedIn`  \n  *(Fields can be adjusted in the final Notion node.)*\n\n"
      },
      "typeVersion": 1
    },
    {
      "id": "172f7079-1774-4f3e-a006-0a9472f1ab9a",
      "name": "Map Fields",
      "type": "n8n-nodes-base.set",
      "position": [
        -144,
        -1200
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "6366bf8a-2675-405f-9f4d-e40fe3403126",
              "name": "message.content.full_name",
              "type": "string",
              "value": "={{ $json.message.content.full_name }}"
            },
            {
              "id": "612483ea-463e-4384-a207-139a96b3b521",
              "name": "message.content.email",
              "type": "string",
              "value": "={{ $json.message.content.email }}"
            },
            {
              "id": "7f59e751-f5f0-4827-a423-aa76afa3294b",
              "name": "message.content.location",
              "type": "string",
              "value": "={{ $json.message.content.location }}"
            },
            {
              "id": "9b78e65b-3735-4a72-b7fd-85c87fcbf0e7",
              "name": "message.content.phone",
              "type": "string",
              "value": "={{ $json.message.content.phone }}"
            },
            {
              "id": "17350c31-8035-4062-995a-6af30741220f",
              "name": "message.content.years_experience",
              "type": "string",
              "value": "={{ $json.message.content.years_experience || null }}"
            },
            {
              "id": "630b295d-1a5b-45af-9b31-7b8a53e4bb42",
              "name": "message.content.education",
              "type": "array",
              "value": "={{ $json.message.content.education }}"
            },
            {
              "id": "592f7b61-a42a-494d-a6f3-bd05e6e238a7",
              "name": "message.content.languages",
              "type": "array",
              "value": "={{ $json.message.content.languages }}"
            },
            {
              "id": "7f82a821-5c72-4585-937a-ca5e5f58b8d3",
              "name": "message.content.linkedin",
              "type": "string",
              "value": "={{ $json.message.content.linkedin || null }}"
            },
            {
              "id": "7c30e83d-8541-4e47-94d6-62faee08473a",
              "name": "",
              "type": "string",
              "value": ""
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "1a935777-1de1-46f3-8b5c-3e9403b5357b",
      "name": "Create entry",
      "type": "n8n-nodes-base.notion",
      "position": [
        528,
        -1200
      ],
      "parameters": {
        "options": {},
        "resource": "databasePage",
        "databaseId": {
          "__rl": true,
          "mode": "list",
          "value": "239d5e39-f780-8048-9c64-d2a9755ff9a3",
          "cachedResultUrl": "https://www.notion.so/239d5e39f78080489c64d2a9755ff9a3",
          "cachedResultName": "Candidates"
        },
        "propertiesUi": {
          "propertyValues": [
            {
              "key": "Email|email",
              "emailValue": "={{ $('Map Fields').item.json.message.content.email }}"
            },
            {
              "key": "Name|title",
              "title": "={{ $('Map Fields').item.json.message.content.full_name }}"
            },
            {
              "key": "Phone|phone_number",
              "phoneValue": "={{ $('Map Fields').item.json.message.content.phone }}"
            },
            {
              "key": "Location|rich_text",
              "textContent": "={{ $('Map Fields').item.json.message.content.location }}"
            },
            {
              "key": "Years of Experience|rich_text",
              "textContent": "={{ $('Map Fields').item.json.message.content.years_experience }}"
            },
            {
              "key": "Education|rich_text",
              "textContent": "={{ $('Map Fields').item.json.message.content.education.join('/') }}"
            },
            {
              "key": "Languages|rich_text",
              "textContent": "={{ $('Map Fields').item.json.message.content.languages.join(', ') }}"
            },
            {
              "key": "LinkedIn|url",
              "urlValue": "={{ $('Map Fields').item.json.message.content.linkedin }}"
            },
            {
              "key": "Application Date|date",
              "date": "={{ new Date($now).toLocaleString() }}\n",
              "timezone": "Europe/Madrid"
            }
          ]
        }
      },
      "credentials": {
        "notionApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "2781847d-c520-45c4-8e79-8ede72bc980e",
      "name": "Get many entries",
      "type": "n8n-nodes-base.notion",
      "position": [
        80,
        -1200
      ],
      "parameters": {
        "filters": {
          "conditions": [
            {
              "key": "Email|email",
              "condition": "equals",
              "emailValue": "={{ $json.message.content.email }}"
            }
          ]
        },
        "options": {},
        "resource": "databasePage",
        "operation": "getAll",
        "databaseId": {
          "__rl": true,
          "mode": "list",
          "value": "239d5e39-f780-8048-9c64-d2a9755ff9a3",
          "cachedResultUrl": "https://www.notion.so/239d5e39f78080489c64d2a9755ff9a3",
          "cachedResultName": "Candidates"
        },
        "filterType": "manual"
      },
      "credentials": {
        "notionApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.2,
      "alwaysOutputData": true
    }
  ],
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "f333a874-c73d-49c5-b54e-546ddafd8878",
  "connections": {
    "If": {
      "main": [
        [
          {
            "node": "Map Fields",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "If1": {
      "main": [
        [
          {
            "node": "Message a model",
            "type": "main",
            "index": 0
          }
        ],
        []
      ]
    },
    "If2": {
      "main": [
        [
          {
            "node": "Create entry",
            "type": "main",
            "index": 0
          }
        ],
        []
      ]
    },
    "Code": {
      "main": [
        [
          {
            "node": "HTTP Request",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Map Fields": {
      "main": [
        [
          {
            "node": "Get many entries",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create entry": {
      "main": [
        []
      ]
    },
    "HTTP Request": {
      "main": [
        [
          {
            "node": "If1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Gmail Trigger": {
      "main": [
        [
          {
            "node": "Code",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Message a model": {
      "main": [
        [
          {
            "node": "If",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get many entries": {
      "main": [
        [
          {
            "node": "If2",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}