LinkedIn + Google Sheets: approved cold email drafts
You start with “just a quick list of leads.” Then you’re neck-deep in tabs: LinkedIn profiles, company pages, Crunchbase, notes, and a half-written email draft you don’t trust.
This is where LinkedIn email automation pays off. A growth marketer needs volume without sounding generic. A sales lead needs quality control. And a small agency owner needs something repeatable that doesn’t fall apart when the team gets busy.
This workflow pulls enrichment data, drafts a personalized cold email, then keeps only the drafts that pass an AI “judge.” You’ll see what it does, what you need, and how to make it fit your outreach process.
How This Automation Works
The full n8n workflow, from trigger to final output:
n8n Workflow Template: LinkedIn + Google Sheets: approved cold email drafts
flowchart LR
subgraph sg0["When clicking ‘Execute workflow’ Flow"]
direction LR
n8@{ icon: "mdi:play-circle", form: "rounded", label: "When clicking ‘Execute workf..", pos: "b", h: 48 }
n11@{ icon: "mdi:cog", form: "rounded", label: "Get row(s)1", pos: "b", h: 48 }
n13["<div style='background:#f5f5f5;padding:10px;border-radius:8px;display:inline-block;border:1px solid #e0e0e0'><img src='https://flowpast.com/wp-content/uploads/n8n-workflow-icons/httprequest.dark.svg' width='40' height='40' /></div><br/>Linkedin_URL"]
n14@{ icon: "mdi:cog", form: "rounded", label: "Wait", pos: "b", h: 48 }
n15@{ icon: "mdi:swap-vertical", form: "rounded", label: "RapidAPI-Key", pos: "b", h: 48 }
n16["<div style='background:#f5f5f5;padding:10px;border-radius:8px;display:inline-block;border:1px solid #e0e0e0'><img src='https://flowpast.com/wp-content/uploads/n8n-workflow-icons/httprequest.dark.svg' width='40' height='40' /></div><br/>results"]
n17@{ icon: "mdi:cog", form: "rounded", label: "Update row(s)1", pos: "b", h: 48 }
n18@{ icon: "mdi:cog", form: "rounded", label: "Wait1", pos: "b", h: 48 }
n19["<div style='background:#f5f5f5;padding:10px;border-radius:8px;display:inline-block;border:1px solid #e0e0e0'><img src='https://flowpast.com/wp-content/uploads/n8n-workflow-icons/httprequest.dark.svg' width='40' height='40' /></div><br/>results1"]
n20@{ icon: "mdi:cog", form: "rounded", label: "Update row(s)2", pos: "b", h: 48 }
n21["<div style='background:#f5f5f5;padding:10px;border-radius:8px;display:inline-block;border:1px solid #e0e0e0'><img src='https://flowpast.com/wp-content/uploads/n8n-workflow-icons/httprequest.dark.svg' width='40' height='40' /></div><br/>Linkedin_URL_COMPANY"]
n22@{ icon: "mdi:cog", form: "rounded", label: "Wait2", pos: "b", h: 48 }
n23["<div style='background:#f5f5f5;padding:10px;border-radius:8px;display:inline-block;border:1px solid #e0e0e0'><img src='https://flowpast.com/wp-content/uploads/n8n-workflow-icons/httprequest.dark.svg' width='40' height='40' /></div><br/>results2"]
n24@{ icon: "mdi:cog", form: "rounded", label: "Update row(s)3", pos: "b", h: 48 }
n25["<div style='background:#f5f5f5;padding:10px;border-radius:8px;display:inline-block;border:1px solid #e0e0e0'><img src='https://flowpast.com/wp-content/uploads/n8n-workflow-icons/httprequest.dark.svg' width='40' height='40' /></div><br/>Crunchbase_URL"]
n26@{ icon: "mdi:cog", form: "rounded", label: "Call 'My workflow'", pos: "b", h: 48 }
n14 --> n16
n18 --> n19
n22 --> n23
n16 --> n17
n19 --> n20
n23 --> n24
n11 --> n13
n11 --> n21
n11 --> n25
n11 --> n26
n13 --> n14
n15 --> n11
n25 --> n22
n21 --> n18
n8 --> n15
end
subgraph sg1["When Executed by Another Workflow Flow"]
direction LR
n0@{ icon: "mdi:robot", form: "rounded", label: "Structured Output Parser", pos: "b", h: 48 }
n1@{ icon: "mdi:robot", form: "rounded", label: "Structured Output Parser1", pos: "b", h: 48 }
n2@{ icon: "mdi:swap-vertical", form: "rounded", label: "Main Loop", pos: "b", h: 48 }
n3@{ icon: "mdi:swap-horizontal", form: "rounded", label: "Approval Route", pos: "b", h: 48 }
n4@{ icon: "mdi:robot", form: "rounded", label: "Agent One", pos: "b", h: 48 }
n5@{ icon: "mdi:swap-vertical", form: "rounded", label: "Email Context", pos: "b", h: 48 }
n6@{ icon: "mdi:robot", form: "rounded", label: "Judge Agent", pos: "b", h: 48 }
n7@{ icon: "mdi:brain", form: "rounded", label: "Google Gemini Chat Model", pos: "b", h: 48 }
n9@{ icon: "mdi:cog", form: "rounded", label: "Get row(s)", pos: "b", h: 48 }
n10@{ icon: "mdi:cog", form: "rounded", label: "Update row(s)", pos: "b", h: 48 }
n12@{ icon: "mdi:play-circle", form: "rounded", label: "When Executed by Another Wor..", pos: "b", h: 48 }
n4 --> n5
n2 --> n4
n9 --> n2
n6 --> n3
n6 --> n2
n5 --> n6
n10 --> n2
n3 --> n10
n3 --> n9
n7 -.-> n6
n7 -.-> n4
n0 -.-> n4
n1 -.-> n6
n12 --> n9
end
%% Styling
classDef trigger fill:#e8f5e9,stroke:#388e3c,stroke-width:2px
classDef ai fill:#e3f2fd,stroke:#1976d2,stroke-width:2px
classDef aiModel fill:#e8eaf6,stroke:#3f51b5,stroke-width:2px
classDef decision fill:#fff8e1,stroke:#f9a825,stroke-width:2px
classDef database fill:#fce4ec,stroke:#c2185b,stroke-width:2px
classDef api fill:#fff3e0,stroke:#e65100,stroke-width:2px
classDef code fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px
classDef disabled stroke-dasharray: 5 5,opacity: 0.5
class n8,n12 trigger
class n0,n1,n4,n6 ai
class n7 aiModel
class n3 decision
class n13,n16,n19,n21,n23,n25 api
classDef customIcon fill:none,stroke:none
class n13,n16,n19,n21,n23,n25 customIcon
The Problem: Cold outreach dies in the research-and-rewrite loop
Personalized cold email is supposed to be “quick.” In reality, research is the tax you pay on every single message. You look up a lead’s LinkedIn to find something real to mention, open the company page to understand what they do, then check Crunchbase to confirm size, funding, or market. After that, you still have to write. And because you’re doing it fast, you second-guess the draft, rewrite it, and forget what you already learned the moment you move to the next lead. It’s exhausting, and errors creep in when you’re trying to move quickly.
It adds up fast. Here’s where it breaks down for most teams.
- Research takes maybe 10 minutes per lead, and it’s scattered across tabs you don’t capture cleanly.
- Copy-paste details get mangled, so names, titles, and company context end up slightly wrong.
- Draft quality swings wildly, which means you either spam people or you spend your evening editing “templates.”
- No consistent approval gate exists, so questionable drafts still slip into sequences when things get busy.
The Solution: Enrich leads, draft emails, and only write back approvals
This n8n workflow starts from your lead list in Google Sheets (using n8n’s Data Table-style “get rows” approach) and finds rows that haven’t been drafted yet. For each lead, it runs enrichment requests through a RapidAPI-powered scraper to pull LinkedIn profile data, LinkedIn company info, and Crunchbase details. It stores the raw JSON back onto the same row, so you can always see what the draft was based on. Then an AI agent (using a Gemini chat model node) writes a short, personalized subject line and email body using your lead fields plus an “ABOUT ME” block that represents your offer and voice. Finally, a second AI agent acts as a judge, approving or rejecting each draft. Only approved drafts get written back to your sheet.
The workflow kicks off on a manual run today, but it’s designed to be scheduled (daily, hourly, whatever fits). Enrichment runs in parallel with wait/polling so the scraper can finish, then the drafting and QA happen as a clean handoff. Your spreadsheet becomes a single source of truth, not a graveyard of half-finished work.
What You Get: Automation vs. Results
| What This Workflow Automates | Results You’ll Get |
|---|---|
|
|
Example: What This Looks Like
Say you draft outreach for 30 new leads each week. Manually, you might spend about 10 minutes researching (LinkedIn + company + Crunchbase) and another 5 minutes writing, so around 15 minutes per lead. That’s roughly 7 hours a week. With this workflow, adding leads to the sheet takes a few minutes, then the automation enriches, drafts, and judges in the background. You still review the approved outputs, but the heavy lifting is gone, so most teams get several hours back every week.
What You’ll Need
- n8n instance (try n8n Cloud free)
- Self-hosting option if you prefer (Hostinger works well)
- Google Sheets to store leads and drafts
- RapidAPI for the enrichment scraper requests
- Google Gemini API key (get it from Google AI Studio)
Skill level: Intermediate. You’ll connect credentials, map sheet columns, and adjust a couple of prompts safely.
Don’t want to set this up yourself? Talk to an automation expert (free 15-minute consultation).
How It Works
Run trigger (manual now, schedule later). You start it manually, or you can swap in a Schedule Trigger so it runs every morning and clears your “pending” leads automatically.
Pull pending rows from Google Sheets. The workflow reads rows where the subject line is blank, then processes them in batches so you don’t overload your enrichment or AI quotas.
Enrich each lead in parallel. It sends HTTP requests for LinkedIn profile, LinkedIn company, and Crunchbase company data, waits for the scraper jobs to finish, then stores the returned JSON back into your sheet row.
Draft, judge, and write back approved emails. One AI agent produces a structured subject/body output. A second agent reviews and returns APPROVED or REVISE. Approved drafts get written to the row; rejected ones are skipped so your sheet stays clean.
You can easily modify the approval rules to be stricter (or more forgiving) based on your needs. See the full implementation guide below for customization options.
Step-by-Step Implementation Guide
Step 1: Configure the Manual Trigger
Set up the entry points that kick off enrichment and email generation.
- Open Manual Launch Trigger and keep it as the primary manual start node.
- Open Triggered by Parent Flow and set Input Source to
passthroughto allow upstream workflows to trigger this one. - Confirm the execution order: Manual Launch Trigger → Set RapidAPI Token and Triggered by Parent Flow → Retrieve Pending Rows.
Tip: Use Manual Launch Trigger for testing, then switch to Triggered by Parent Flow for production orchestration.
Step 2: Connect Data Tables
Configure the data table nodes that read lead rows and store enrichment plus email results.
- In Retrieve Pending Rows, set Operation to
getand verify the Data Table isemail_linkedin_list. Confirm the filter condition: email_subject isisEmpty. - In Fetch Enrichment Rows, set Operation to
getand use the same Data Tableemail_linkedin_listwith the email_subject isisEmptycondition. - In Store Profile Data, set Operation to
update, and map linkedin_profile_scrape to{{ $json.results.toJsonString() }}with the filter email ={{ $('Fetch Enrichment Rows').item.json.email }}. - In Store Company Data and Store Crunchbase Data, keep Operation as
updateand use the same email filter{{ $('Fetch Enrichment Rows').item.json.email }}. - In Write Email Results, set Operation to
updateand map email_body to{{ $('Assemble Email Context').item.json.email_content }}and email_subject to{{ $('Assemble Email Context').item.json.email_subject }}.
⚠️ Common Pitfall: Ensure the Data Table ID for all dataTable nodes points to the same table (email_linkedin_list), or rows won’t match by email.
Step 3: Connect RapidAPI Enrichment Requests
Set the RapidAPI key and configure the parallel enrichment requests, delays, and result fetches.
- In Set RapidAPI Token, set RapidAPI-Key to your key value, replacing
[CONFIGURE_YOUR_API_KEY]. - Confirm Fetch Enrichment Rows receives input from Set RapidAPI Token.
- Fetch Enrichment Rows outputs to both LinkedIn Profile Request and LinkedIn Company Request and Crunchbase Request and Run Sub-Workflow (Configure Required) in parallel.
- In LinkedIn Profile Request, set URL to
https://cold-outreach-enrichment-scraper.p.rapidapi.com/company_urland use Query{{ $json.Linkedin_URL }}with header{{ $('Set RapidAPI Token').item.json['RapidAPI-Key'] }}. - In LinkedIn Company Request, set URL to
https://cold-outreach-enrichment-scraper.p.rapidapi.com/company_urland use Query{{ $json.company_linkedin }}with header{{ $('Set RapidAPI Token').item.json['RapidAPI-Key'] }}. - In Crunchbase Request, set URL to
https://cold-outreach-enrichment-scraper.p.rapidapi.com/url_searchand use Query{{ $json.Crunchbase_URL }}with header{{ $('Set RapidAPI Token').item.json['RapidAPI-Key'] }}. - In Delay Profile Fetch, Delay Company Fetch, and Delay Crunchbase Fetch, keep Unit set to
minutesand adjust the actual delay duration as needed. - In Profile Fetch Results, Company Fetch Results, and Crunchbase Fetch Results, set URL to
https://cold-outreach-enrichment-scraper.p.rapidapi.com/resultsand set task_id to{{ $('LinkedIn Profile Request').item.json.task_id }},{{ $('LinkedIn Company Request').item.json.task_id }}, and{{ $('Crunchbase Request').item.json.task_id }}respectively. - In Run Sub-Workflow (Configure Required), select the sub-workflow in Workflow and decide whether to keep Wait For Sub-Workflow as
false.
⚠️ Common Pitfall: If Set RapidAPI Token is left as [CONFIGURE_YOUR_API_KEY], all HTTP requests will fail with authorization errors.
Step 4: Set Up AI Email Generation
Configure the AI agent and structured parsers to generate personalized outreach content.
- In Personalized Outreach Writer, keep Prompt Type as
defineand ensure the prompt includes the enrichment placeholders like{{ $json.First_name }},{{ $json.Company_Name }}, and{{ $json.linkedin_profile_scrape }}. - Attach Structured Parse Schema to Personalized Outreach Writer so the agent outputs JSON with
email_subjectandemail_content. - In Assemble Email Context, map fields to expressions: email_subject =
{{ $json.output.email_subject }}, email_content ={{ $json.output.email_content }}, and email ={{ $('Batch Iteration').item.json.email }}. - Connect Gemini Chat Engine as the language model for Personalized Outreach Writer.
Credential Required: Connect your googlePalmApi credentials in Gemini Chat Engine. The parsers Structured Parse Schema and Approval Schema Parser do not take credentials directly—add them to Gemini Chat Engine.
Step 5: Configure Review Routing and Batch Flow
Review emails, approve or revise, and manage looping through batches.
- In Quality Review Agent, keep the defined prompt and ensure it receives
{{ $json.email_subject }}and{{ $json.email_content }}as inputs. - Attach Approval Schema Parser to Quality Review Agent to enforce structured responses.
- In Approval Decision, set the condition to check Left Value
{{ $json.output }}containsAPPROVED. - Confirm the flow: Quality Review Agent → Approval Decision; approved emails go to Write Email Results while non-approved output returns to Retrieve Pending Rows for reprocessing.
- Ensure Batch Iteration receives from Retrieve Pending Rows and loops back from Write Email Results for the next row.
⚠️ Common Pitfall: If Approval Decision does not match APPROVED, the workflow will keep cycling. Verify the approval schema and agent output formatting.
Step 6: Configure Output Updates
Write final email subject and body back to the data table once approved.
- In Write Email Results, ensure email_body maps to
{{ $('Assemble Email Context').item.json.email_content }}and email_subject maps to{{ $('Assemble Email Context').item.json.email_subject }}. - Confirm the update filter uses email =
{{ $('Batch Iteration').item.json.email }}so only the correct row is updated.
Tip: Use the built-in table view to verify the email_subject and email_body fields populate after an approved run.
Step 7: Test and Activate Your Workflow
Run a controlled test to validate enrichment, AI output, and data table updates.
- Click Execute Workflow and start with Manual Launch Trigger to run a test on a single row.
- Verify that enrichment data is stored in Store Profile Data, Store Company Data, and Store Crunchbase Data, then confirm AI output in Write Email Results.
- Successful execution shows new values in email_subject and email_body within the
email_linkedin_listtable. - Once validated, activate the workflow and trigger it from the parent workflow using Triggered by Parent Flow.
Common Gotchas
- RapidAPI credentials can expire or need specific permissions. If things break, check your RapidAPI dashboard subscription and the credential value stored in n8n first.
- If you’re using Wait nodes or external rendering, processing times vary. Bump up the wait duration if downstream nodes fail on empty responses.
- Default prompts in AI nodes are generic. Add your brand voice early or you’ll be editing outputs forever.
Frequently Asked Questions
About 45 minutes if your Sheet and API keys are ready.
No. You’ll mostly map fields from Google Sheets and paste in API credentials. The only “technical” part is being careful with column names and prompts.
Yes. n8n has a free self-hosted option and a free trial on n8n Cloud. Cloud plans start at $20/month for higher volume. You’ll also need to factor in RapidAPI enrichment costs and Gemini API usage for the writer + judge.
Two options: n8n Cloud (managed, easiest setup) or self-hosting on a VPS. For self-hosting, Hostinger VPS is affordable and handles n8n well. Self-hosting gives you unlimited executions but requires basic server management.
Yes, and it’s a smart upgrade if you want a human-in-the-loop. Add a Slack message after the draft is generated (right after the outreach writer) and include the subject/body plus a link to the row. Then only route to “Write Email Results” when a Slack interaction (or a webhook callback) returns “approve.” You can keep the AI judge too, so Slack only sees the best drafts.
Usually it’s a missing or expired RapidAPI key in the “Set RapidAPI Token” step. Double-check that the key matches the scraper API you subscribed to and that your plan is active. If it worked once and then stopped, rate limits are a common culprit, especially when you increase batch size. Also confirm the host value points to the correct enrichment scraper.
If you self-host n8n, there’s no execution limit, but throughput depends on your server and the scraper wait times. On n8n Cloud, limits depend on your plan, and the practical cap is usually your RapidAPI and Gemini quotas. In real usage, many teams run this in batches of 10–50 leads per run to keep costs predictable and avoid rate limiting.
Often, yes, because this flow has batching, conditional routing, waiting/polling, and a two-agent QA pattern that gets awkward (and pricey) in simpler tools. n8n also makes it easier to store raw JSON enrichment back onto the row, which is helpful for auditability. The tradeoff is setup time: you will spend a bit longer configuring it up front. If your process is just “new row → send email,” Zapier or Make can be fine. If you want a judged approval gate, n8n is the better fit. Talk to an automation expert if you’re not sure which fits.
Once this is running, your spreadsheet stops being “a list of leads” and turns into a controlled drafting pipeline. You get the repetitive work off your plate, and you keep the quality bar where it belongs.
Need Help Setting This Up?
Our automation experts can build and customize this workflow for your specific needs. Free 15-minute consultation—no commitment required.