{
  "id": "PYI6OI1u3qTlAzm2",
  "meta": {
    "templateCredsSetupCompleted": false
  },
  "name": "daily-job-application-tracker",
  "tags": [],
  "nodes": [
    {
      "id": "7336e7f2-6d28-4a07-a3c1-86be45b5d186",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1904,
        -1568
      ],
      "parameters": {
        "width": 720,
        "height": 384,
        "content": "## Daily Job Application Tracker\n\n### The purpose of this tracker is to automatically maintain a clean, up-to-date record of your job applications without manually checking every email.\nIt helps you:\n- Track every job application in one place, including the company, role, date, and current status.\n- Avoid duplicate entries by keeping one row per application, identified by the company and position.\n- Automatically update application progress when new emails arrive, such as changing a role from Applied to Rejected, Interview, or Offer.\n- Filter out irrelevant emails like OTPs, job alerts, newsletters, and account confirmations so the tracker stays focused.\n- Add human approval before updating existing statuses, so important changes are reviewed before they are written to Google Sheets.\n\nOverall, the tracker acts as a lightweight job-search CRM that turns messy inbox updates into a structured application pipeline."
      },
      "typeVersion": 1
    },
    {
      "id": "8ace736e-0ce8-454f-b9e4-971b6a77e811",
      "name": "Process Email Data",
      "type": "n8n-nodes-base.set",
      "position": [
        -752,
        -432
      ],
      "parameters": {
        "mode": "raw",
        "options": {},
        "jsonOutput": "={{\n{\n  email_id: $json.id,\n  thread_id: $json.threadId,\n  date: $json.date,\n  subject: $json.subject,\n  body: ($json.text || '')\n    .replace(/https?:\\/\\/\\S+/g, '')\n    .replace(/\\s+/g, ' ')\n    .trim()\n    .slice(0, 4000)\n}\n}}"
      },
      "typeVersion": 3.4
    },
    {
      "id": "f73d86ad-b173-442e-bfa0-99d1095dcdaa",
      "name": "Format Data",
      "type": "n8n-nodes-base.set",
      "position": [
        80,
        -416
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "1b191471-5e0a-4948-b9c7-94df4964850d",
              "name": "receivedDate",
              "type": "string",
              "value": "={{ $('Fetch Job Application Emails').item.json.date.split('T')[0] }}"
            },
            {
              "id": "b71092c2-f681-4677-b93b-3eaf06b6a09d",
              "name": "Company",
              "type": "string",
              "value": "={{ JSON.parse($json.text).company }}"
            },
            {
              "id": "2ee9f61d-a1ad-477b-ad50-887bbb70b12d",
              "name": "Position",
              "type": "string",
              "value": "={{ JSON.parse($json.text).position }}"
            },
            {
              "id": "85bb4978-8c94-4b57-8f57-71880fe0df1e",
              "name": "Status",
              "type": "string",
              "value": "={{ JSON.parse($json.text).status }}"
            },
            {
              "id": "daa08683-390a-4b1c-8ec8-a32907b33d3a",
              "name": "ID",
              "type": "string",
              "value": "={{ $('Fetch Job Application Emails').item.json.threadId }}"
            },
            {
              "id": "cb28e3ee-0bc1-4aa1-aec3-22d7eed06ccd",
              "name": "MatchKey",
              "type": "string",
              "value": "={{ JSON.parse($json.text).company }} | {{ JSON.parse($json.text).position }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "placeholder-openai-node",
      "name": "Open AI",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "position": [
        -400,
        -256
      ],
      "parameters": {
        "model": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4o-mini",
          "cachedResultName": "gpt-4o-mini"
        },
        "options": {
          "temperature": 0.1
        },
        "builtInTools": {}
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "3ca1a667-b91f-4e6b-919e-5a3754502134",
      "name": "Merge",
      "type": "n8n-nodes-base.merge",
      "position": [
        1664,
        -16
      ],
      "parameters": {
        "mode": "combine",
        "options": {},
        "combineBy": "combineByPosition"
      },
      "typeVersion": 3.2
    },
    {
      "id": "39e5894f-93ae-4354-b850-2c58107f8fad",
      "name": "Filter Valid Job Updates",
      "type": "n8n-nodes-base.if",
      "position": [
        336,
        0
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 3,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "ba4a9f90-3974-4711-8910-6ab263692acb",
              "operator": {
                "type": "string",
                "operation": "notEquals"
              },
              "leftValue": "={{ $json.Status }}",
              "rightValue": "Other"
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "1fc7b9b3-214e-42c5-a1fc-29423ad0dde1",
      "name": "Daily 9 AM Trigger",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        -1632,
        -432
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "triggerAtHour": 9
            }
          ]
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "c74c59c7-3120-47a5-a679-4ee0e9dd3fd4",
      "name": "Extract Key Information",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "position": [
        -400,
        -432
      ],
      "parameters": {
        "text": "=You are an expert job-application email parser.\n\nExtract job application information from the email data below.\n\nReturn ONLY one valid raw JSON object.\nDo not include markdown, explanations, comments, or code fences.\n\nRequired JSON shape:\n{\n  \"company\": string or null,\n  \"position\": string or null,\n  \"status\": \"Applied\" | \"Rejected\" | \"Interview\" | \"Offer\" | \"Other\"\n}\n\nEmail fields:\nSubject: {{ $('Process Email Data').item.json.subject }}\nDate: {{ $('Process Email Data').item.json.date }}\nBody: {{ $('Process Email Data').item.json.body}}\n\nExtraction rules:\n\n1. Status classification\n\nChoose exactly one Status.\n\nUse \"Offer\" if the email clearly extends or discusses a job offer, offer letter, employment offer, compensation package, start date, or joining instructions.\n\nUse \"Rejected\" if the email says the application will not continue, the company chose other candidates, the role was filled, the candidate was not selected, or similar.\nExamples:\n- \"we will not be moving forward\"\n- \"we will not be proceeding\"\n- \"we won\u2019t be moving forward\"\n- \"we have decided to move forward with other candidates\"\n- \"we have given preference to candidates\"\n- \"not selected\"\n- \"position has been filled\"\n- \"unfortunately\"\n- French equivalents:\n  - \"nous ne poursuivrons pas\"\n  - \"nous avons choisi de poursuivre le processus avec d\u2019autres profils\"\n  - \"candidature non retenue\"\n\nUse \"Interview\" if the email invites the candidate to interview, schedule a call, book a time, meet the recruiter/team, complete a phone screen, video interview, technical interview, case study discussion, or next-round conversation.\nExamples:\n- \"schedule an interview\"\n- \"book a time\"\n- \"schedule a call\"\n- \"meet with\"\n- \"next round\"\n- \"recruiter screen\"\n- \"technical interview\"\n- \"phone interview\"\n- \"video interview\"\n- \"availability\"\n- \"calendly\"\n\nUse \"Applied\" if the email confirms the application was submitted, received, acknowledged, or is under review.\nExamples:\n- \"we received your application\"\n- \"we have received your application\"\n- \"thank you for applying\"\n- \"thanks for applying\"\n- \"thank you for your application\"\n- \"your application has been submitted\"\n- \"we are reviewing your application\"\n- \"we will review your application\"\n- \"application confirmation\"\n- French equivalents:\n  - \"candidature bien re\u00e7ue\"\n  - \"nous avons bien re\u00e7u votre candidature\"\n  - \"nous avons bien re\u00e7u ta candidature\"\n  - \"merci pour votre candidature\"\n  - \"nous l'\u00e9tudions avec attention\"\n\nUse \"Other\" for job-related emails that are not a clear application status.\nExamples:\n- one-time passcodes\n- email verification\n- job alerts\n- newsletters\n- profile updates\n- application portal login links\n- generic recruiting platform emails\n- reminders without an application outcome\n\nPriority rule:\n- If the email contains a rejection, Status must be \"Rejected\", even if it also says \"thank you for applying\" or \"we received your application\".\n- Else if it contains an offer, use \"Offer\".\n- Else if it contains an interview invitation or scheduling request, use \"Interview\".\n- Else if it confirms receipt/submission/review of an application, use \"Applied\".\n- Else use \"Other\".\n\n2. Company extraction\n\nExtract the actual hiring company, not the recruiting platform.\n\nPrefer company names from:\n- Subject\n- From name\n- Body/signature\n- From email domain only if clear\n\nExamples:\n- Subject \"Contexte - Thank you...\" \u2192 company \"Contexte\"\n- From name \"Gaspard Hauff - Contexte\" \u2192 company \"Contexte\"\n- From name \"C12 Quantum Electronics\" \u2192 company \"C12 Quantum Electronics\"\n- Body \"position at C12\" \u2192 company \"C12\"\n- From name \"Dashdoc's HR Team\" \u2192 company \"Dashdoc\"\n- From name \"\u00c9quipe RH de Parallel\" \u2192 company \"Parallel\"\n- From name \"Deloitte Netherlands\" \u2192 company \"Deloitte\"\n- From name \"Coty Recruiting\" \u2192 company \"Coty\"\n- From name \"Nassi - Spiko\" \u2192 company \"Spiko\"\n\nRemove generic recruiter/team words:\n- Hiring Team\n- HR Team\n- Recruiting\n- Recruitment\n- Talent Team\n- Talent Acquisition Team\n- People Team\n- Notifications\n- no-reply\n- notification\n- candidates\n\nNever use these as the company unless the email is actually about a job at that company:\n- Lever\n- Greenhouse\n- Ashby\n- AshbyHQ\n- SmartRecruiters\n- Workday\n- SuccessFactors\n- Teamtailor\n- Welcome to the Jungle\n- Indeed\n- LinkedIn\n- Mailgun\n- SendGrid\n- AmazonSES\n\nIf the company is not confidently identifiable, return null.\n\n3. Position extraction\n\nExtract the job title/role related to this application.\n\nPriority order \u2014 check sources in this order, stop at the first hit:\n1. Subject line \u2014 usually contains the role explicitly. Examples:\n   - \"Contexte - Thank you for applying for the position of Go-to-Market (GTM) Engineer H/F\" \u2192 \"Go-to-Market (GTM) Engineer H/F\"\n   - \"Your application for Senior Data Analyst at Stripe\" \u2192 \"Senior Data Analyst\"\n   - \"Re: Product Manager role\" \u2192 \"Product Manager\"\n\n2. Body text \u2014 look for phrases like:\n   - \"for the [position] position\"\n   - \"for the job of [position] at [company]\"\n   - \"application for [position]\"\n   - \"candidature au poste de [position]\"\n   - \"candidature bien re\u00e7ue pour [position]\"\n   - \"recruiter in charge of [position]\"\n   - \"we will review your application for [position]\"\n   - \"interested in the [position] role\"\n\n3. From-name signature \u2014 sometimes contains the role context.\n\nCleanup rules \u2014 return ONLY the clean role title. Remove:\n- \"position\", \"role\", \"job\", \"job of\"\n- \"application for\", \"candidature au poste de\", \"candidature pour\"\n- \"at [company]\", \"with [company]\", \"@ [company]\"\n- Leading/trailing whitespace, quotes, or punctuation\n\nExamples (input \u2192 output):\n- \"the Forward Deployment Engineer position\" \u2192 \"Forward Deployment Engineer\"\n- \"for the job of Data Analyst at BlaBlaCar\" \u2192 \"Data Analyst\"\n- \"candidature au poste de RevOps Associate\" \u2192 \"RevOps Associate\"\n- \"Client Data & Analytics Associate (f/m/d) position\" \u2192 \"Client Data & Analytics Associate (f/m/d)\"\n\nReturn null ONLY if:\n- The email is a one-time passcode (OTP)\n- The email is an email/account verification\n- The email is a generic platform notification (login, password reset, profile update)\n- No specific role is mentioned anywhere in subject OR body\n\nImportant: if the email mentions ANY job title in the subject or body, extract it. Do NOT return null just because the format is unusual. Talent pool / spontaneous application emails should extract the pool name (e.g. \"Spiko Talent Pool\") instead of null.\n\n4. Output requirements\n\n- Return valid JSON only.\n- Use double quotes for all keys and string values.\n- Use null, not \"null\", for unknown values.\n- Do not add extra keys.\n- The \"Status\" value must never be null.",
        "batching": {
          "batchSize": 1,
          "delayBetweenBatches": 2000
        },
        "promptType": "define"
      },
      "retryOnFail": false,
      "typeVersion": 1.9
    },
    {
      "id": "placeholder-sheets-append-node",
      "name": "Track New Application",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        1008,
        -48
      ],
      "parameters": {
        "columns": {
          "value": {
            "Date": "={{ $('Format Data').item.json.receivedDate }}",
            "Status": "={{ $('Format Data').item.json.Status }}",
            "Company": "={{ $('Format Data').item.json.Company }}",
            "Position": "={{ $('Format Data').item.json.Position }}",
            "Match Key": "={{ $json.MatchKey }}",
            "Thread ID": "={{ $('Fetch Job Application Emails').item.json.threadId }}"
          },
          "schema": [
            {
              "id": "Thread ID",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Thread ID",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Match Key",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Match Key",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Date",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Date",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Company",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Company",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Position",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Position",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Status",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Status",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Modified Date",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Modified Date",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "gid=0",
          "cachedResultUrl": "YOUR_GOOGLE_SHEET_URL",
          "cachedResultName": "Sheet1"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "YOUR_GOOGLE_SHEET_ID",
          "cachedResultUrl": "YOUR_GOOGLE_SHEET_URL",
          "cachedResultName": "Job Application Tracker"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "abcae0ac-f135-460c-83e6-b4b89c9b6ebd",
      "name": "Aggregate Data",
      "type": "n8n-nodes-base.aggregate",
      "position": [
        1008,
        288
      ],
      "parameters": {
        "options": {},
        "aggregate": "aggregateAllItemData",
        "destinationFieldName": "updates"
      },
      "typeVersion": 1
    },
    {
      "id": "placeholder-gmail-approval-node",
      "name": "Email Updates & Wait for Approval",
      "type": "n8n-nodes-base.gmail",
      "position": [
        1344,
        256
      ],
      "parameters": {
        "sendTo": "user@example.com",
        "message": "=Morning! \u2615\n\nHere's what landed in your inbox while you were sleeping \u2014 {{ $json.updates.length }} update{{ $json.updates.length === 1 ? '' : 's' }} on your applications:\n\n{{ $json.updates.map(u => `${u.Status === 'Offer' ? '\ud83c\udf89' : u.Status === 'Interview' ? '\ud83d\udcde' : u.Status === 'Rejected' ? '\u274c' : '\ud83d\udcdd'} ${u.Company || 'Unknown'}${u.Position ? ' \u2014 ' + u.Position : ''} \u2192 ${u.Status || 'unclear'}`).join('\\n')}}\n\nTap \"Update tracker\" to log these changes, or skip if you'd like to review them yourself.\n\nKeep going \ud83d\udcaa",
        "options": {
          "appendAttribution": false
        },
        "subject": "={{ $json.updates.length }} job application update{{ $json.updates.length === 1 ? '' : 's' }} - approve?",
        "operation": "sendAndWait",
        "approvalOptions": {
          "values": {}
        }
      },
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "790b9d8e-0b0e-41ba-a14f-de5dc0f68cb2",
      "name": "Split Data",
      "type": "n8n-nodes-base.splitOut",
      "position": [
        1968,
        -16
      ],
      "parameters": {
        "options": {},
        "fieldToSplitOut": "=updates"
      },
      "typeVersion": 1
    },
    {
      "id": "placeholder-sheets-update-node",
      "name": "Update Application",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        2288,
        -16
      ],
      "parameters": {
        "columns": {
          "value": {
            "Status": "={{ $json.Status }}",
            "Match Key": "={{ $json.MatchKey }}",
            "Thread ID": "={{ $json.ID }}",
            "Modified Date": "={{ new Date().toISOString().split('T')[0] }}"
          },
          "schema": [
            {
              "id": "Thread ID",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Thread ID",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Match Key",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Match Key",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Date",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "Date",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Company",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "Company",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Position",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "Position",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Status",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Status",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Modified Date",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Modified Date",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "row_number",
              "type": "number",
              "display": true,
              "removed": true,
              "readOnly": true,
              "required": false,
              "displayName": "row_number",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [
            "Match Key"
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "update",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "gid=0",
          "cachedResultUrl": "YOUR_GOOGLE_SHEET_URL",
          "cachedResultName": "Sheet1"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "YOUR_GOOGLE_SHEET_ID",
          "cachedResultUrl": "YOUR_GOOGLE_SHEET_URL",
          "cachedResultName": "Job Application Tracker"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "placeholder-gmail-fetch-node",
      "name": "Fetch Job Application Emails",
      "type": "n8n-nodes-base.gmail",
      "position": [
        -1184,
        -432
      ],
      "parameters": {
        "limit": 100,
        "simple": false,
        "filters": {
          "q": "=(from:(linkedin.com OR indeed.com OR welcometothejungle.com OR greenhouse.io OR boards.greenhouse.io OR lever.co OR hire.lever.co OR jobs.lever.co OR ashbyhq.com OR workday.com OR myworkdayjobs.com OR icims.com OR smartrecruiters.com OR taleo.net OR successfactors.com OR breezy.hr OR jobvite.com OR teamtailor.com OR personio.com OR personio.de OR wellfound.com OR recruitee.com OR pinpointhq.com OR ripplematch.com OR notion.so OR figma.com) OR subject:(\"your application\" OR \"thank you for applying\" OR \"thank you for your interest\" OR \"interview\" OR \"regret to inform\" OR \"unfortunately\" OR \"next step\" OR \"take-home\" OR \"schedule a call\" OR \"candidature\" OR \"entretien\")) newer_than:25h",
          "sender": "="
        },
        "options": {},
        "operation": "getAll"
      },
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "a136f19c-0e99-4e92-b6e1-10d42a452e2e",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1792,
        -656
      ],
      "parameters": {
        "color": 4,
        "width": 416,
        "height": 368,
        "content": "Starts the workflow automatically every morning.\n\nPurpose:\n\u2022 Run once per day\n\u2022 Check recent job-related emails\n\u2022 Avoid manual execution\n\nRecommended:\nKeep Gmail query limited to newer_than:25h so each daily run only checks emails received since the previous run."
      },
      "typeVersion": 1
    },
    {
      "id": "05547c78-f006-4bd4-91b8-2225060f05ca",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1344,
        -592
      ],
      "parameters": {
        "width": 416,
        "height": 336,
        "content": "Fetches recent Gmail messages that are likely related to job applications. Uses Gmail search operators to find:\n\u2022 ATS emails, Application confirmations, Rejections, Interview invites, Offer-related emails\n\nImportant:\nThis node may still fetch some irrelevant platform emails, OTPs, or promotional emails. Those are filtered later."
      },
      "typeVersion": 1
    },
    {
      "id": "f6295469-1be0-4818-91f1-2387c5d96b5e",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -912,
        -512
      ],
      "parameters": {
        "width": 400,
        "height": 256,
        "content": "Cleans and normalizes raw Gmail output before sending it to the LLM, keeping only useful fields."
      },
      "typeVersion": 1
    },
    {
      "id": "ce02ff9a-65d3-4295-9d94-6865dd4b5de1",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -496,
        -512
      ],
      "parameters": {
        "color": 3,
        "width": 400,
        "height": 368,
        "content": "LLM node that extracts structured job application data. Define batch size and delays to avoid API rate limiting. "
      },
      "typeVersion": 1
    },
    {
      "id": "134fe1ee-2c99-4b79-8cb8-3e1123d63759",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -80,
        -512
      ],
      "parameters": {
        "width": 400,
        "height": 256,
        "content": "Converts LLM output into the final structure used by Google Sheets. Creates/normalizes key informations.\n\nMatch Key is used to identify one unique job application across multiple emails."
      },
      "typeVersion": 1
    },
    {
      "id": "980249ac-d380-4ccf-8d94-b491913c5718",
      "name": "Sticky Note6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        224,
        -96
      ],
      "parameters": {
        "width": 336,
        "height": 256,
        "content": "Prevents irrelevant emails from appearing in the approval email or Google Sheet."
      },
      "typeVersion": 1
    },
    {
      "id": "placeholder-sheets-other-node",
      "name": "Track Invalid Application Status",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        688,
        256
      ],
      "parameters": {
        "columns": {
          "value": {
            "Date": "={{ $('Format Data').item.json.receivedDate }}",
            "Status": "={{ $('Format Data').item.json.Status }}",
            "Company": "={{ $('Format Data').item.json.Company }}",
            "Position": "={{ $('Format Data').item.json.Position }}",
            "Match Key": "={{ $json.MatchKey }}",
            "Thread ID": "={{ $('Fetch Job Application Emails').item.json.threadId }}"
          },
          "schema": [
            {
              "id": "Thread ID",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Thread ID",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Match Key",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Match Key",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Date",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Date",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Company",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Company",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Position",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Position",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Status",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Status",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Modified Date",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Modified Date",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": 509226883,
          "cachedResultUrl": "YOUR_GOOGLE_SHEET_URL",
          "cachedResultName": "Other app"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "YOUR_GOOGLE_SHEET_ID",
          "cachedResultUrl": "YOUR_GOOGLE_SHEET_URL",
          "cachedResultName": "Job Application Tracker"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "b867a3ff-4a7c-41e9-a036-b97c2942fb2b",
      "name": "Branch Valid Application Status",
      "type": "n8n-nodes-base.if",
      "position": [
        688,
        -32
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 3,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "d00bc4af-5739-4732-b0b0-812e7355a172",
              "operator": {
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{ $json.Status.toLowerCase() }}",
              "rightValue": "applied"
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "7d5acd69-dfa3-4390-9f0a-680f750feadc",
      "name": "Sticky Note7",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        592,
        -112
      ],
      "parameters": {
        "width": 304,
        "height": 256,
        "content": "Decides whether a valid job email should create a new application row or update an existing one."
      },
      "typeVersion": 1
    },
    {
      "id": "0751eb05-1a73-433e-955b-41d5a38c6913",
      "name": "Sticky Note8",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        592,
        160
      ],
      "parameters": {
        "width": 304,
        "height": 272,
        "content": "Logs ignored or unclear emails for debugging/review. Used when an email is job-related but not a valid application status update."
      },
      "typeVersion": 1
    },
    {
      "id": "5d04a9e7-8f46-496d-94fa-13fbe01efe12",
      "name": "Sticky Note9",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        912,
        -160
      ],
      "parameters": {
        "width": 304,
        "height": 288,
        "content": "Adds a new row to the main Google Sheet when the job application does not already exist. Usually used for first-time Applied emails or applications not already in the tracker."
      },
      "typeVersion": 1
    },
    {
      "id": "5361d23b-ade2-4641-bbf7-2e3bb9bf4dce",
      "name": "Sticky Note10",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        912,
        144
      ],
      "parameters": {
        "width": 304,
        "height": 288,
        "content": "Groups multiple application updates into one array before sending the approval email. Instead of sending one approval email per update, this node creates one summary email containing all pending updates.\n"
      },
      "typeVersion": 1
    },
    {
      "id": "bc2257ff-acac-4848-b07a-caa5c7a0120c",
      "name": "Sticky Note11",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1232,
        144
      ],
      "parameters": {
        "width": 304,
        "height": 288,
        "content": "Sends one Gmail approval email summarizing all pending application updates, adding human-in-the-loop control before changing existing application statuses.\n"
      },
      "typeVersion": 1
    },
    {
      "id": "21ef08b2-822f-4c0c-a860-e2fb9690011f",
      "name": "Sticky Note12",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1552,
        -112
      ],
      "parameters": {
        "width": 304,
        "height": 256,
        "content": "The approval node does not preserve the original updates array by itself.\nMerge brings the updates array back after approval so the workflow can continue."
      },
      "typeVersion": 1
    },
    {
      "id": "b30ca2a6-359e-4248-8e70-aacc26517c50",
      "name": "Sticky Note13",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1872,
        -112
      ],
      "parameters": {
        "width": 304,
        "height": 256,
        "content": "Splits the aggregated updates array back into individual items. Google Sheets update node needs individual items to update individual rows."
      },
      "typeVersion": 1
    },
    {
      "id": "f732e1e7-3363-4e40-960f-958daeef0977",
      "name": "Sticky Note14",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2208,
        -112
      ],
      "parameters": {
        "color": 4,
        "width": 304,
        "height": 256,
        "content": "Updates existing rows in the main Google Sheet after human approval. All should update the same row using the same Match Key.\n\n"
      },
      "typeVersion": 1
    },
    {
      "id": "67e5aceb-ff77-43f0-88ed-54fe255bb5de",
      "name": "Sticky Note15",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1904,
        -1152
      ],
      "parameters": {
        "color": 5,
        "width": 528,
        "height": 368,
        "content": "## Workflow Summary\n\n1. Run daily at 9 AM\n2. Fetch recent job emails from Gmail\n3. Clean email data\n4. Extract company, position, and status with LLM\n5. Format output and create Match Key\n6. Filter out invalid/Other emails\n7. Add new applications directly\n8. Aggregate existing status updates\n9. Send one approval email\n10. Merge approval response with original updates\n11. Split updates back into individual items\n12. Update existing Google Sheet rows"
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "binaryMode": "separate",
    "executionOrder": "v1"
  },
  "versionId": "a3f9d595-61db-4309-832c-ca8fa7db7ff1",
  "connections": {
    "Merge": {
      "main": [
        [
          {
            "node": "Split Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Open AI": {
      "ai_languageModel": [
        [
          {
            "node": "Extract Key Information",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Split Data": {
      "main": [
        [
          {
            "node": "Update Application",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format Data": {
      "main": [
        [
          {
            "node": "Filter Valid Job Updates",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Aggregate Data": {
      "main": [
        [
          {
            "node": "Email Updates & Wait for Approval",
            "type": "main",
            "index": 0
          },
          {
            "node": "Merge",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Daily 9 AM Trigger": {
      "main": [
        [
          {
            "node": "Fetch Job Application Emails",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Process Email Data": {
      "main": [
        [
          {
            "node": "Extract Key Information",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Key Information": {
      "main": [
        [
          {
            "node": "Format Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filter Valid Job Updates": {
      "main": [
        [
          {
            "node": "Branch Valid Application Status",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Track Invalid Application Status",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Job Application Emails": {
      "main": [
        [
          {
            "node": "Process Email Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Branch Valid Application Status": {
      "main": [
        [
          {
            "node": "Track New Application",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Aggregate Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Email Updates & Wait for Approval": {
      "main": [
        [
          {
            "node": "Merge",
            "type": "main",
            "index": 1
          }
        ]
      ]
    }
  }
}