Personalized emails from a Google Sheet
Send a personal email to every row in a sheet and mark each one as sent.
How it works
Trigger: Manual — click Execute workflow when ready
- Read your list (name, email, status).
- Keep rows that haven't been sent yet.
- Send each person a personalized email.
- Mark the row as sent so nobody gets it twice.
Diagram
Trigger App Logic
Build it
Pick your tool — same automation, step by step.
Copy and paste straight into an n8n canvas (Ctrl+V), or download and use Import from File. Then connect your own credentials and replace every YOUR_… placeholder.
Show code (194 lines)
{
"name": "Personalized emails from a Google Sheet (Flowngn)",
"nodes": [
{
"parameters": {
"content": "## Personalized emails from a Google Sheet\nSend a personal email to every row in a sheet and mark each one as sent.\n\n**Setup**\n1. Open each node and connect your own credentials.\n2. Replace every YOUR_… placeholder.\n3. Test once, then activate.\n\nFree template from Flowngn",
"height": 260,
"width": 340
},
"id": "458210f1-1c22-487e-a172-02be03f78739",
"name": "Setup notes",
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
-420,
-60
]
},
{
"parameters": {},
"id": "458210f1-1c22-487e-a172-02be03f78739",
"name": "Run manually",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [
0,
0
]
},
{
"parameters": {
"operation": "read",
"documentId": {
"__rl": true,
"value": "YOUR_SPREADSHEET_ID",
"mode": "id"
},
"sheetName": {
"__rl": true,
"value": "Contacts",
"mode": "name"
},
"options": {}
},
"id": "458210f1-1c22-487e-a172-02be03f78739",
"name": "Read list",
"type": "n8n-nodes-base.googleSheets",
"typeVersion": 4.5,
"position": [
300,
0
]
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "loose"
},
"conditions": [
{
"id": "c1",
"leftValue": "={{ $json.status }}",
"rightValue": "sent",
"operator": {
"type": "string",
"operation": "notEquals"
}
}
],
"combinator": "and"
},
"options": {}
},
"id": "458210f1-1c22-487e-a172-02be03f78739",
"name": "Not sent yet?",
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [
600,
0
]
},
{
"parameters": {
"sendTo": "={{ $json.email }}",
"subject": "=A quick note for you, {{ $json.name }}",
"emailType": "text",
"message": "=Hi {{ $json.name }},\n\nYOUR_MESSAGE\n\nBest,\nYOUR_NAME",
"options": {
"appendAttribution": false
}
},
"id": "458210f1-1c22-487e-a172-02be03f78739",
"name": "Send email",
"type": "n8n-nodes-base.gmail",
"typeVersion": 2.1,
"position": [
900,
0
]
},
{
"parameters": {
"operation": "update",
"documentId": {
"__rl": true,
"value": "YOUR_SPREADSHEET_ID",
"mode": "id"
},
"sheetName": {
"__rl": true,
"value": "Contacts",
"mode": "name"
},
"columns": {
"mappingMode": "defineBelow",
"value": {
"email": "={{ $('Not sent yet?').item.json.email }}",
"status": "sent"
},
"matchingColumns": [
"email"
]
},
"options": {}
},
"id": "458210f1-1c22-487e-a172-02be03f78739",
"name": "Mark as sent",
"type": "n8n-nodes-base.googleSheets",
"typeVersion": 4.5,
"position": [
1200,
0
]
}
],
"connections": {
"Run manually": {
"main": [
[
{
"node": "Read list",
"type": "main",
"index": 0
}
]
]
},
"Read list": {
"main": [
[
{
"node": "Not sent yet?",
"type": "main",
"index": 0
}
]
]
},
"Not sent yet?": {
"main": [
[
{
"node": "Send email",
"type": "main",
"index": 0
}
]
]
},
"Send email": {
"main": [
[
{
"node": "Mark as sent",
"type": "main",
"index": 0
}
]
]
}
},
"pinData": {},
"settings": {
"executionOrder": "v1"
},
"meta": {
"templateCredsSetupCompleted": false
},
"tags": []
}In Make, create a new scenario and add these modules in order:
- Run the scenario manually (Run once)
- Google Sheets › Search Rows
- Filter on the next route: “Not sent yet?”
- If yesGmail › Send an Email
- If yesGoogle Sheets › Update a Row
- Every module run uses Make operations — the free plan includes a monthly quota.
- Module and step names can differ slightly as Make and Zapier update their apps.
In Zapier, create a new Zap and add these steps in order:
- Google Sheets — New or Updated Spreadsheet Row (Zapier has no manual trigger)
- Google Sheets — Lookup Spreadsheet Rows (or Code by Zapier)
- Filter by Zapier: “Not sent yet?”
- If yesGmail — Send Email
- If yesGoogle Sheets — Update Spreadsheet Row
- Zaps with more than two steps need a paid Zapier plan.
- Webhooks, Filters/Paths and Code by Zapier are premium features on some Zapier plans.
- Module and step names can differ slightly as Make and Zapier update their apps.
Tips
- Gmail has daily sending limits — send to small batches.
- Only email people who expect to hear from you.
Related flows
MarketingAdvanced
Competitor price change monitor
Check public product pages daily and get notified when a price changes.
HTTP Request → Google Sheets → Gmail5h/mo
MarketingEasy
Automatic birthday emails
Customers or team members get a warm birthday email on their day.
Google Sheets → Gmail2h/mo
MarketingEasy
Deliver a free guide and add the reader to your list
When someone asks for your free guide, email them the download, add them to Mailchimp and log the source.
Webhook → Gmail → Mailchimp → Google Sheets3h/mo