Recruiter friend was losing half her day to manually typing LinkedIn profiles into a sheet – built her a workflow that ends the retyping

:waving_hand: Hey n8n Community,

You might remember Lisa, she’s Mike’s recruiter and the protagonist of the CV Slack Assistant workflow I posted a couple of weeks back. We had a short meeting yesterday so she could give me feedback, and the headline was the best thing you can hear as a builder: both her workflows are smooth, no breaks, and she actually uses them every day.

Then she mentioned a friend, let’s call her Anna, who works in recruiting too but mostly does active sourcing on LinkedIn. Lisa thought her workflow was a candidate for automation. She wasn’t wrong.

:mouse_trap: The trap Anna’s in

Anna’s process is actually really disciplined:

  1. Look for interesting profiles on LinkedIn
  2. Gather them in a Google Sheet (name, role, company, location, skills, recent roles…)
  3. Filter for actual fit
  4. Only then reach out to the candidates who match

The good news about doing it that way: she barely wastes time messaging the wrong people.

The bad news: by the time she’s written her first message of the day, she’s already spent half the day manually typing data from LinkedIn profiles into a sheet.

So I asked Lisa to introduce me, and built Anna a workflow that ends the typing part.

:link: What I built

Anna drops either a LinkedIn screenshot or LinkedIn’s official “Save to PDF” export into a dedicated Slack channel. The workflow extracts the profile into a structured candidate row in her Google Sheet, name, current title and company, location, years of experience, top skills, last 3 roles, education, certifications, one-line summary, plus a source tag so sourced candidates are distinguishable from CV applicants in the same sheet (yes, the schema matches Lisa’s CV onboarding output, so it’s one database, two intake paths).

It also deduplicates: if Anna drops the same person twice, or someone Lisa already onboarded as a CV applicant, the bot replies in-thread with a “this candidate is already in the database, sourced on [date]” warning and a button straight to the sheet. New candidates get the green “added” message with the same button.

:puzzle_piece: The pieces

  • Slack Trigger fires on file uploads in the watched channel
  • files.info lookup, Slack’s file_shared event payload is minimal and doesn’t include the download URL directly
  • easybits Extractor (verified community node), 10-field schema, multimodal so it handles PDFs and screenshots in the same node
  • Code node for normalization and source tagging
  • Read sheet → dedup Code node → IF for routing
  • Direct HTTP POST to Slack’s chat.postMessage for the in-thread confirmation with a button to the sheet

Runs on the easybits free plan (50 extractions/month, 10 fields max, covers Anna’s volume fine).

:turtle: One honest caveat

LinkedIn “Save to PDF” gives you the full profile (all roles, all skills). Screenshots truncate at the “Show all” fold. Both work in the workflow, but if accurate years-of-experience or the full skill list matters, PDF wins. Also worth knowing: LinkedIn Recruiter allows ~200 PDF exports per seat per month, standard accounts ~100. Covers almost everyone, but if you’re sourcing at huge volume you’d hit a ceiling.

:wrench: Workflow JSON

:speech_balloon: Feedback welcome

Would love to see this stress-tested by other active sourcers, does the schema map to how you actually capture candidates, or are there fields you’d add or drop?

And more broadly: what’s the dumbest manual step in your current sourcing process? Curious where the next workflow should go.

Best,
Felix

1 me gusta

Hey Felix,

This is a brilliantly practical build. As someone who spends a lot of time orchestrating complex AI pipelines and workflow automations for clients in n8n, I really appreciate the elegant handling of API quirks you’ve showcased here. You’ve solved a massive real-world bottleneck with a very clean architecture.

A few technical thoughts on the build:

  • Bypassing the Slack Node: Using the HTTP Request node to directly POST to chat.postMessage to preserve Block Kit formatting is a classic, battle-tested workaround. It’s great to see that documented here for newer builders who might otherwise pull their hair out over the native node’s UI limitations.

  • Hydrating File Metadata: Spot on with the files.info lookup. Slack’s event payloads are notoriously sparse, and grabbing that url_private_download dynamically is exactly how this should be handled for production stability.

  • Deduplication Logic: Using a composite key of name + current_company is a great lightweight solution for immediate needs. One potential edge case as the database scales: candidates change companies, or have similar names. If the easybits extractor can reliably pull the unique LinkedIn Profile URL (especially from the PDF), using that as your absolute primary key would make the deduplication foolproof across much longer timelines.

To answer your questions at the end:

1. Are there fields you’d add or drop? I would definitely add the LinkedIn URL as mentioned above. I’d also recommend injecting a default Status column (e.g., “Sourced - Review Pending”). This instantly elevates the Google Sheet from a static data dump into a dynamic Kanban board that can trigger downstream actions.

2. Where should the next workflow go? The most logical next step is closing the outreach loop. You could set up a simple polling trigger (or a webhook if moving to an actual ATS database) so that when Anna reviews the sheet and changes a candidate’s status to “Approved,” it triggers an AI node to draft a highly personalized first-touch Slack or email message based on the extracted Top Skills and Recent Roles. It could drop that draft directly back into Slack for her to approve and send, saving her the writing time as well as the data-entry time!

Awesome work ending the manual data entry trap for her!

1 me gusta

Hey @Swapnil_Mandloi, thank you so much for the kind words and for taking the time to leave such a thoughtful comment.

I really love how active and supportive the n8n community is, and reading comments like yours honestly makes me proud. I started writing these posts because I kept running into the same challenges when I began my automation journey. Back then, I would have loved to find practical write-ups from experienced builders that could have saved me hours of trial and error. Now, almost two years later, I’m in the position to give back, and that’s exactly what I’m trying to do.

Your point about using the LinkedIn profile URL is absolutely valid. Funny enough, I actually had it included in the extraction schema originally, so I can confirm that the Extractor handles it reliably. I only removed it because I wanted to keep the workflow compatible with the free Extractor plan, which is limited to 10 data fields per pipeline. After reading your comment, though, I think it’s worth bringing it back. Using the LinkedIn profile URL as a unique identifier also makes perfect sense, especially since candidates may change companies over time while their profile remains the same.

And thanks for the suggestion about the status field as well! That was a really nice idea, and I’ve already implemented it for Anna since it was such an easy win. While I was at it, we also discussed her current outreach process so I can better understand how those messages should be structured once we automate that part of the workflow.

I’ll definitely keep you updated as the workflow evolves!

1 me gusta