Back to Blog
AI Automation10 min readJune 18, 2026

Building AI JobCopilot: A Chrome Extension That Automates Job Applications with n8n and LLMs

How I built AI JobCopilot, a Chrome extension that extracts LinkedIn job descriptions, triggers an n8n workflow, and uses LLMs to generate tailored recruiter emails and ATS-optimized resumes automatically.

Chrome ExtensionN8NLLMsGmail APITypeScriptJob AutomationPrompt Engineering

Why I Built This

Applying to jobs on LinkedIn follows the same tedious loop every time: open a listing, read the description, figure out which keywords matter, tweak your resume, write a slightly different email to the recruiter, and repeat for the next fifty postings. I'm a CS student who has spent real time building RPA bots and n8n workflows during my internships, so the loop started to look exactly like the kind of repetitive, rules-plus-judgment task that automation is good at. That's the itch that turned into AI JobCopilot — a Chrome Extension (TypeScript) paired with an n8n workflow that extracts a job description, generates a tailored recruiter email and an ATS-optimized LaTeX resume, sends the email through Gmail, logs everything to Google Sheets, and pings me on Telegram with the status.

This post walks through how the pieces fit together.

Architecture at a Glance

LinkedIn Job Page
      │  (content script scrapes DOM)
      ▼
Chrome Extension (TypeScript)
      │  (POST job description + metadata)
      ▼
n8n Webhook Trigger
      │
      ├─▶ LLM Node: generate recruiter email
      ├─▶ LLM Node: generate ATS-optimized LaTeX resume
      ├─▶ Gmail API Node: send email
      ├─▶ Google Sheets Node: log application
      └─▶ Telegram Node: notify status

The extension's only job is extraction and triggering. All the "thinking" — tailoring content, deciding tone, mapping skills to the job description — happens in the n8n workflow, which keeps the extension lightweight and lets me iterate on prompts without shipping a new extension version.

Step 1: Scraping the Job Description with a Content Script

The extension injects a content script into LinkedIn job pages. It grabs the job title, company name, and full description text from the DOM, then packages it into a JSON payload:

// content-script.ts
function extractJobData(): JobPayload {
  const title = document.querySelector('.job-details-jobs-unified-top-card__job-title')?.textContent?.trim() ?? '';
  const company = document.querySelector('.job-details-jobs-unified-top-card__company-name')?.textContent?.trim() ?? '';
  const description = document.querySelector('.jobs-description__content')?.textContent?.trim() ?? '';

  return {
    title,
    company,
    description,
    url: window.location.href,
    scrapedAt: new Date().toISOString(),
  };
}

chrome.runtime.onMessage.addListener((msg, _sender, sendResponse) => {
  if (msg.type === 'EXTRACT_JOB') {
    sendResponse(extractJobData());
  }
});

The popup UI triggers this extraction when I click "Apply with Copilot," then forwards the payload to the background service worker, which makes the actual network call to n8n. Keeping the network call in the background script (rather than the content script) avoids CORS headaches with LinkedIn's CSP.

// background.ts
async function sendToWorkflow(payload: JobPayload) {
  const res = await fetch(process.env.N8N_WEBHOOK_URL!, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(payload),
  });
  return res.json();
}

Step 2: The n8n Workflow — Where the Automation Lives

Once the webhook fires, n8n takes over. The workflow is structured as trigger → LLM generation → delivery → logging → notification, which is the same pattern I keep reusing across the automation work I do for my internships — it just happens to fit this personal project too.

A trimmed view of the workflow's node chain:

{
  "nodes": [
    { "name": "Job Intake Webhook", "type": "n8n-nodes-base.webhook" },
    { "name": "Generate Recruiter Email", "type": "n8n-nodes-base.httpRequest",
      "parameters": { "url": "https://api.groq.com/openai/v1/chat/completions" } },
    { "name": "Generate LaTeX Resume", "type": "n8n-nodes-base.httpRequest" },
    { "name": "Send Gmail", "type": "n8n-nodes-base.gmail" },
    { "name": "Log to Google Sheets", "type": "n8n-nodes-base.googleSheets" },
    { "name": "Notify Telegram", "type": "n8n-nodes-base.telegram" }
  ]
}

The Recruiter Email Prompt

The prompt is deliberately constrained so the output is close to send-ready without extra editing:

System: You write concise, professional cold emails to recruiters on behalf
of a candidate. Never invent experience the candidate doesn't have. Keep
emails under 150 words, no generic filler ("I am writing to express...").

User: Job title: {{title}}
Company: {{company}}
Job description: {{description}}
Candidate background: {{candidate_profile}}

Write a recruiter email that references 2-3 specific requirements from the
job description and connects them to the candidate's actual background.

Grounding the prompt with a fixed candidate_profile block (my actual skills and project history) is what keeps the LLM from hallucinating experience — it can only rearrange and emphasize, not invent.

The LaTeX Resume Prompt

The resume generation node reuses a base LaTeX template and asks the LLM to reorder and reweight bullet points (not fabricate new ones) based on which skills the job description emphasizes:

System: You are given a candidate's existing LaTeX resume bullets and a job
description. Reorder and lightly rephrase existing bullets to emphasize
relevant keywords for ATS parsing. Do not add skills or experience that
aren't in the source bullets.

The output LaTeX gets compiled in a follow-up step (I use a hosted LaTeX compilation API) before being attached to the email.

Step 3: Sending and Logging

Once both generations complete, n8n's Gmail node sends the email with the compiled resume attached. The same execution writes a row to a Google Sheet — job title, company, date applied, and a link to the generated resume — so I have an application log I don't have to maintain by hand. This is the same instinct behind PDD (Process Definition Document) writing I do for RPA bots at work: if the automation runs without a record, you can't debug it or trust it later.

Step 4: Telegram as the Status Channel

The last node posts a message to a Telegram bot I built specifically for this project:

✅ Application sent
Job: Senior Frontend Engineer @ Acme Corp
Resume: acme-corp-resume.pdf
Sheet row: #47

Telegram also works in the other direction — I can paste a job description directly into the bot chat instead of going through the extension, and the same n8n workflow picks it up via a second Telegram-trigger entry point. That flexibility matters more than it sounds: some job postings live outside LinkedIn (referrals, Slack channels, direct emails), and having a second intake path means the automation isn't locked to one source.

What I'd Do Differently

If I rebuilt this today, I'd add a manual approval step between resume generation and email send — a Telegram inline button to approve or reject before the Gmail node fires. Right now I trust the LLM output enough to send automatically, but for anything the LLM might misjudge (tone, keyword stuffing), a human-in-the-loop checkpoint is cheap insurance. That's a lesson I've picked up from RPA work directly: even reliable automation benefits from a checkpoint before an irreversible action.

Closing Thoughts

AI JobCopilot isn't a SaaS product — it's a personal automation project that solves a problem I actually have, built with the same tools (n8n, LLM prompt pipelines, TypeScript) I use professionally. That's the pattern I'd recommend to any CS student learning automation: build the workflow that fixes your own repetitive task first, because you'll actually use it, test it, and fix its bugs.

If you want to talk about the extension, the n8n setup, or automation projects in general, reach out at rishabnishad22@gmail.com, on WhatsApp, or through my contact page.

Written by

Rishab Nishad

AI & Automation Engineer, currently RPA Developer Intern at Avent IQ. Building RPA bots, AI/LLM automation workflows, and full-stack web applications.

Related Articles