workflow recipe
n8n Gmail to Google Sheets Workflow for Clean Email Logging
Use Gmail to collect matching messages, Set to normalize fields, IF to skip noise, and Google Sheets to append clean rows for lightweight email tracking.
- Use when
- n8n workflows, Gmail, Google Sheets, email logging
- First check
- Choose the Gmail query or label that defines which messages should enter the workflow.
- Time to check
- 5-10 minutes
- Next step
- Run the recommended steps, then verify a production execution.
Independent third-party notes. n8n is a trademark of its owner and is referenced only for compatibility and troubleshooting context.
Quick Answer
Use Gmail to collect matching messages, Set to normalize fields, IF to skip noise, and Google Sheets to append clean rows for lightweight email tracking.
Problem Pattern
People rarely need a generic Gmail or Sheets tutorial. They need a reliable way to capture only the right emails, map stable fields, avoid duplicates, and verify that a sheet is receiving production data.
Version awareness
Last reviewed 2026-05-21
Key Facts
- Trigger pattern
- Use a Gmail polling or message lookup step when email is the source system.
- Data shaping
- Normalize sender, subject, date, label, and message URL before writing to a sheet.
- Duplicate control
- Store a stable Gmail message ID or thread ID in the sheet.
- Best outcome
- A sheet that can be filtered, searched, and audited without manual cleanup.
Recommended Steps
- Choose the Gmail query or label that defines which messages should enter the workflow.
- Map only stable fields such as message ID, sender, subject, received time, labels, and a short snippet.
- Add an IF check for required fields or labels so unrelated email does not reach the sheet.
- Append the normalized fields to Google Sheets with clear column names.
- Test with one real message, then with a small batch before enabling the production trigger.
Verification
- A matching email creates exactly one row.
- A non-matching email is skipped.
- The row contains a stable message ID and useful audit fields.
- A retry does not create an unexpected duplicate.
Warnings
- Do not write full email bodies to a public or widely shared sheet unless the data is safe to expose.
- Without a message ID column, it is hard to detect duplicate rows after retries or re-runs.
- OAuth scopes and account access can fail even when the workflow logic is correct.
Best For
- Lightweight inbox tracking
- Support or lead triage logs
- Teams that need a simple spreadsheet audit trail
Not For
- Sensitive mailboxes where message content should not leave the email system
- High-volume ingestion that needs a database and deduplication table
Common Mistakes
- Appending every message without a Gmail label or query filter.
- Writing the entire message body when a snippet and link are enough.
- Forgetting a message ID column for duplicate checks.
- Testing only manually and never verifying the production trigger path.
Examples
Gmail: find messages with label inbound-leads
Set: message_id, from, subject, received_at, snippet
IF: from and subject are present
Google Sheets: append row
Slack optional: notify only for high-priority senders Importable Workflow Starter
gmail-to-google-sheets-starter.jsonStarter workflow for appending selected Gmail messages to Google Sheets. Replace label, sheet, and credential placeholders before activation.
Node order
- Gmail Trigger
- Set row fields
- Google Sheets append row
Credential checklist
- Gmail OAuth credential with read access to the target mailbox/label.
- Google Sheets credential with edit access to the target spreadsheet.
| Source field | Destination field | Notes |
|---|---|---|
| message.id | message_id column | Use as duplicate key. |
| message.from | from column | Normalize display names if needed. |
| message.subject | subject column | Keep the raw subject for search. |
{
"name": "Gmail to Google Sheets starter",
"nodes": [
{
"parameters": {
"pollTimes": {
"item": [
{
"mode": "everyMinute"
}
]
},
"filters": {
"labelIds": [
"REPLACE_WITH_LABEL_ID"
]
}
},
"id": "GmailTrigger",
"name": "Gmail Trigger",
"type": "n8n-nodes-base.gmailTrigger",
"typeVersion": 1,
"position": [
0,
0
],
"credentials": {
"gmailOAuth2": {
"id": "REPLACE_WITH_CREDENTIAL_ID",
"name": "REPLACE_WITH_GMAIL_CREDENTIAL"
}
}
},
{
"parameters": {
"keepOnlySet": true,
"values": {
"string": [
{
"name": "message_id",
"value": "={{$json.id}}"
},
{
"name": "from",
"value": "={{$json.from || $json.headers?.from || \"\"}}"
},
{
"name": "subject",
"value": "={{$json.subject || \"\"}}"
},
{
"name": "received_at",
"value": "={{$json.date || $now}}"
}
]
}
},
"id": "SetRow",
"name": "Set row fields",
"type": "n8n-nodes-base.set",
"typeVersion": 2,
"position": [
240,
0
]
},
{
"parameters": {
"operation": "append",
"documentId": "REPLACE_WITH_SPREADSHEET_ID",
"sheetName": "REPLACE_WITH_SHEET_NAME",
"columns": {
"mappingMode": "autoMapInputData"
}
},
"id": "Sheets",
"name": "Append row",
"type": "n8n-nodes-base.googleSheets",
"typeVersion": 4,
"position": [
480,
0
],
"credentials": {
"googleSheetsOAuth2Api": {
"id": "REPLACE_WITH_CREDENTIAL_ID",
"name": "REPLACE_WITH_GOOGLE_SHEETS_CREDENTIAL"
}
}
}
],
"connections": {
"Gmail Trigger": {
"main": [
[
{
"node": "Set row fields",
"type": "main",
"index": 0
}
]
]
},
"Set row fields": {
"main": [
[
{
"node": "Append row",
"type": "main",
"index": 0
}
]
]
}
},
"settings": {
"executionOrder": "v1"
},
"active": false,
"pinData": {},
"versionId": "replace-after-import"
} {
"id": "msg_example_123",
"from": "[email protected]",
"subject": "Need invoice help",
"date": "2026-05-21T08:00:00Z"
} {
"appended": true,
"spreadsheet": "REPLACE_WITH_SHEET_ID",
"duplicateKey": "msg_example_123"
} Failure paths
- Gmail trigger watches the wrong label.
- Sheet columns do not match the append mapping.
- Repeated trigger events append duplicate rows without message_id dedupe.
Activation checklist
- Import into a non-production workflow first.
- Replace placeholder credential names and destination IDs.
- Run the minimal test payload with non-sensitive data.
- Confirm error paths do not leak secrets.
- Activate only after one successful end-to-end production-shaped test.
Duplicate prevention: Use a stable event ID, message ID, or payload hash in a datastore/sheet column before writing side effects.
{
"label": "WorkflowFixesTest",
"subject": "Smoke test email"
} What to change before import
- Credential names and credential IDs.
- Slack channel, sheet ID, email labels, or other destination identifiers.
- Webhook path and response body.
- Any sample fields that differ from your payload.
FAQ
Should this workflow store the full email body?
Usually no. Store the minimum useful fields first, then add body text only if the sheet is private and the use case requires it.
Why are duplicate rows appearing?
Retries, manual re-runs, or broad Gmail queries can process the same message again. Track message IDs and add a duplicate check when needed.