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.
  1. Choose the Gmail query or label that defines which messages should enter the workflow.
  2. Map only stable fields such as message ID, sender, subject, received time, labels, and a short snippet.
  3. Add an IF check for required fields or labels so unrelated email does not reach the sheet.
  4. Append the normalized fields to Google Sheets with clear column names.
  5. 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

Minimal logging flow A practical first version before adding enrichment.
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.json

Starter workflow for appending selected Gmail messages to Google Sheets. Replace label, sheet, and credential placeholders before activation.

Download JSON Compare automation tools Replace credentials and IDs before importing.

Node order

  1. Gmail Trigger
  2. Set row fields
  3. 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.
Workflow JSON Replace credential names and destination IDs before activation.
{
  "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"
}
Sample input
{
  "id": "msg_example_123",
  "from": "[email protected]",
  "subject": "Need invoice help",
  "date": "2026-05-21T08:00:00Z"
}
Expected output
{
  "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.

Minimal test payload
{
  "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.

Sources