- Proven n8n Cold Email Automation: 7-Step Workflow Guide
Proven n8n Cold Email Automation: 7-Step Workflow Guide
Build a daily automated cold-email workflow with n8n, Google Sheets & Gmail — randomized delays, 20/day limit, and…

📚 Get Practical Development Guides
Join developers getting comprehensive guides, code examples, optimization tips, and time-saving prompts to accelerate their development workflow.
How I Automated Cold Email Outreach with n8n and Google Sheets
I had a CSV with emails, subjects, and copy. I needed to send them out daily — 20 at a time — without babysitting the process. My first instinct was to write a for loop. Then n8n showed me I was thinking about it all wrong.
This guide walks you through building a fully automated cold email workflow using n8n, Google Sheets, and Gmail. By the end, you will have a workflow that runs every morning, picks up the next 20 unsent rows from your sheet, sends each email with a human-like random delay, and marks them as sent — all without writing a single loop.
The Sheet Setup
Before touching n8n, get your Google Sheet ready. You need five columns:
Email, Subject, Copy, sentAt, row_number
The sentAt column is what controls everything. When it is empty, the row is queued. When the workflow sends the email, it sets sentAt to 1. That is your entire state management — dead simple and it works.
The row_number column is added automatically by n8n when it reads the sheet. You will use it later to update the correct row after sending.
Fill in your Email, Subject, and Copy rows. Leave sentAt blank for everything you want sent.
Building the Workflow
Step 1: Schedule Trigger
Add a Schedule Trigger node and set it to run daily at 7am. Make sure the timezone in the node matches your local timezone — n8n defaults to UTC and your 7am will fire at the wrong time if you miss this.
Step 2: Google Sheets — Read Rows
Add a Google Sheets node connected to your spreadsheet. Set the operation to "Get Row(s)" and point it at your sheet. Connect your Google account via OAuth when prompted.
This node returns every row in your sheet as individual items. And here is the thing that changes how you think about n8n entirely.
n8n does not return an array you need to loop over. Every row becomes its own item, and every node downstream runs once per item automatically.
There is no for loop. There is no .map(). There is no iteration logic to write. You build the workflow for a single item and n8n handles the rest. If 200 rows come through, every node fires 200 times. This is the core mental shift.
Step 3: Filter — Skip Already Sent
Add a Filter node and set the condition: sentAt is not equal to 1.
This filters out anything already sent and passes only fresh rows downstream. Combined with n8n's per-item execution, only unsent emails continue through the workflow.
Step 4: Limit — Cap at 20 Per Day
Add a Limit node and set it to 20. Even though your sheet might have hundreds of rows, only the first 20 that pass the filter will continue. This keeps your daily send volume controlled and your domain reputation healthy.
If you have an older Google Workspace account with a solid sending history, 20 is conservative. You can push higher, but start here.
Step 5: Wait — Random Delay Between Sends
This is the detail that makes your outreach look human rather than mechanical. Add a Wait node and in the amount field, open the expression editor and enter:
{{ Math.floor(Math.random() * 61) + 30 }}
This generates a random number between 30 and 90 each time the node runs — meaning each email goes out with a different delay. Since n8n runs this node once per item, every single email in your batch gets its own randomized wait. No two emails leave at the same interval.
Step 6: Gmail — Send the Email
Add a Gmail node, connect your Google account, and map the fields from your sheet:
- To:
{{ $json.Email }} - Subject:
{{ $json.Subject }} - Message:
{{ $json.Copy }}
Set the email type to plain text. Plain text emails land better in inboxes and read like a real person sent them — which is exactly what you want for cold outreach.
One more thing: scroll down to Additional Fields, click Add Field, find Append n8n Attribution and toggle it off. By default n8n adds a "This email was sent automatically with n8n" footer to every email. Turning this off removes it entirely. No environment variables, no Docker config — just a toggle in the node.
Step 7: Google Sheets — Update the Row
Add a second Google Sheets node with the operation set to "Update Row". This marks each email as sent after it goes out.
Map the fields like this:
- row_number:
{{ $('Filter').item.json.row_number }} - sentAt:
1
The row_number reference pulls from the Filter node specifically because that is where the original row data lives before the Wait and Gmail nodes ran. Setting sentAt to 1 flags the row so the Filter node skips it on every future run.
The Complete Flow
Schedule Trigger (7am daily)
→ Google Sheets (read all rows)
→ Filter (sentAt != 1)
→ Limit (20)
→ Wait (random 30–90 seconds)
→ Gmail (send email)
→ Google Sheets (set sentAt = 1)
Activate the workflow. Fill the sheet with leads. Every morning at 7am, the next 20 unsent rows go out with randomized delays and get marked done. When the sheet is empty, nothing sends. When you add more rows, the cycle continues.
What You Built
You started with a CSV and ended up with a self-managing outreach machine. The key insight is that n8n's item-based execution model eliminates the need for loops entirely — you design for one item and the engine scales it across all of them. The random delay makes the sends look human. The sentAt flag gives you full control over what goes out and what does not. Plain text keeps deliverability high.
Fill the sheet, activate the workflow, and let it run.
Let me know in the comments if you have questions, and subscribe for more practical development guides.
Thanks, Matija