Connect Gmail to Google Sheets: Sample Workflow for Beginners
The Context: You live in your Inbox, but your business lives in a Spreadsheet. Every day, you do the “Alt-Tab Dance”.
- Open Email.
- Copy “Order Amount”.
- Alt-Tab to Excel.
- Paste “Order Amount”.
- Repeat 50 times. This is not work. This is robot work.
The Solution: We are going to build a pipeline. We will turn your Inbox into a “Data Source” and your Google Sheet into a “Database”. In the middle, n8n will act as the pump, moving data automatically, formatting it, and saving it forever.
The Goal: By the end of this guide, you will have a workflow that watches for “Invoices” and auto-logs them to a “Financial Tracker” sheet.
Prerequisites & Credentials (The Hardest Part)
The Context: Security First
Before n8n can read your emails or write to your private financial sheet, you must give it permission.
Google is (rightfully) paranoid about security. This means setting up credentials is often the stumbling block for beginners.
You have two choices: OAuth2 (The “Sign in with Google” button) or Service Account (A server-side key).
For this tutorial, we will use OAuth2 because it is easier for personal Gmail accounts (@gmail.com).
The Build: setting up OAuth2
1. Google Cloud Console
- Go to
console.cloud.google.com. - Create a New Project (Name it “n8n Automation”).
- Go to APIs & Services -> Library.
- Enable Gmail API and Google Sheets API.
2. Credentials Screen
- Go to Credentials -> Create Credentials -> OAuth Client ID.
- Application Type: Web Application.
- Authorized Redirect URI: Copy the URL from your n8n Credential window explanation (It usually ends in
/rest/oauth2-credential/callback).
3. n8n Connection
- Copy the Client ID and Client Secret from Google.
- Paste them into the n8n Credential (“Google OAuth2”).
- Click Sign in with Google.
- Accept the permissions (Read Mail, Edit Sheets).
The “Pro Tip”: Service Accounts
If you are on Google Workspace (Business), use a Service Account.
It generates a generic email (bot@project.iam.gserviceaccount.com).
You then simply “Share” your Google Sheet with that bot email, just like you would a coworker. This bypasses the need for complex OAuth re-authentication.
Common Pitfalls
- The “Unverified App” Warning: Since you haven’t detailed your app to Google, you will see a scary “Google hasn’t verified this app” screen. Click Advanced -> Proceed (Unsafe). It is safe because you are the developer.
- Scope Errors: If you forget to enable the “Drive API” or “Sheets API” in the library, n8n will fail silently.
Step 1: The Trigger (Catching the Email)
The Context: Filtering the Noise
Your inbox gets 100 emails a day. You only care about the 3 that are “Invoices”. If we trigger on every email, we waste CPU cycles and clutter our spreadsheet. We need a Filter.
The Build: Gmail Trigger
1. Add the Node
- Search for Gmail Trigger (or “Email Read”).
- Event: On New Email.
2. The Filter Logic
- Property: Label? OR Sender? OR Subject?
- Best Practice: Use Labels.
- Create a label in Gmail called
Automation_Hitlist. - Set up a Gmail Filter: “If Subject contains ‘Invoice’, apply label
Automation_Hitlist”.
- Create a label in Gmail called
- In n8n, set the Trigger to watch only the
Automation_Hitlistlabel. - Why: This lets you manually drag “missed” emails into that label to trigger the bot retroactively.
3. Polling Interval
- Set it to Every Minute only if urgent.
- Set it to Every Hour if it’s just for bookkeeping.
The “Pro Tip”: The “Search Query”
If you don’t use labels, use the Gmail “Search Query” syntax directly in n8n.
Query: subject:invoice has:attachment -from:me.
This is powerful because it uses Google’s native search engine to filter before the data even hits n8n.
Common Pitfalls
- Triggering on Sent Mail: By default, “All Mail” includes your Sent folder. If n8n replies to an email, and then triggers on that reply, you create an infinite loop. Always filter out
from:me.
Step 2: Parsing the Data (The extraction)
The Context: Unstructured to Structured
The email arrives as a blob of HTML or Text.
“Hi, here is the invoice for $500.00. Thanks, Bob.”
Sheets needs structured columns: [Bob], [$500.00].
We need to extract the value.
The Build: Text Manipulation
1. The Simple Extraction (Subject)
- Sometimes the data is in the subject: “Invoice #1234 from Acme”.
- Use a Set node.
- Expression:
{{ $json.subject.split('#')[1].split(' ')[0] }}.- Logic: Split by
#, take the second part (1234 from Acme), split by space, take the first part (1234).
- Logic: Split by
2. The Advanced Extraction (AI)
- Text parsing is fragile.
- Use the AI Extract node (Basic LLM).
- Prompt: “Extract the Total Amount and Vendor Name from this email body.”
- Output Schema:
{ "amount": "Number", "vendor": "String" }. - This is the modern way to handle messy inputs.
The “Pro Tip”: HTML vs Plain Text
Gmail sends both body.html and body.text.
Always try to parse body.text. HTML is messy with <div> tags and CSS classes that break Regex.
Only use HTML if you need to extract a link hidden in a button.
Common Pitfalls
- Currency Symbols: extracting
$500results in a String (“$500”). Google Sheets Math formulas need a Number (500). Use.replace('$', '')orparseInt()to convert it before sending.
Step 3: Preparing the Sheet (The Database)
The Context: Landing Zone
Automation fails if the destination changes. If you delete Column A, the robot continues to write to Column A, corrupting your data. We need a “Rigid” landing zone.
The Build: Sheet Setup
1. Create the Sheet
- Name:
Automation_Log. - Header Row (Row 1):
- A:
Date - B:
Sender - C:
Subject - D:
Amount - E:
Link_to_Email
- A:
2. Freeze the Header
- View -> Freeze -> 1 Row.
- This helps you (the human) but doesn’t affect the bot.
3. Format Columns
- Select Column A (Date). Format -> Number -> Date Time.
- Select Column D (Amount). Format -> Number -> Currency.
- Why: If you send “2026-01-01” as text, Sheets might treat it as a calculation
2026 minus 1 minus 1. Pre-formatting prevents this.
The “Pro Tip”: The “Status” Column
Add a Column F called Status.
When n8n writes the row, write “Pending”.
Then have a second automation that reads “Pending” rows, processes them (e.g., pays them), and updates the status to “Paid”.
This creates a “State Machine” inside your sheet.
Common Pitfalls
- Merged Cells: Never, ever merge cells in a database sheet. It breaks the API’s ability to count rows.
- Human Edits: Do not manually type in the rows where the bot is writing. You will cause a conflict. Treat the bottom of the sheet as “Bot Territory”.
Step 4: The Append Action (Writing the Row)
The Context: Writing Data
We have the Email (Step 1) and the Extracted Data (Step 2). Now we need to push it to the cloud.
The Build: Google Sheets Node
1. The Node
- Add Google Sheets.
- Operation: Append Row.
- Note: Do not use “Update Row”. We want to add to the history, not overwrite it.
2. The Mapping
- Sheet ID: Select from the dropdown (or paste the long ID from the URL).
- Range: Leave blank (Appends to bottom) or specify
A:E. - Data Mode: Map Each Column Below.
- Date:
{{ $now }}(Timestamp of run) or{{ $json.date }}(Email timestamp). - Sender:
{{ $json.from.text }}. - Amount:
{{ $json.ai_extraction.amount }}.
- Date:
3. Execution
- Hit “Execute”.
- Go to your Google Sheet tab.
- Did the row appear?
- If yes, you have a pipeline.
The “Pro Tip”: User Entered vs Raw
In the Node Settings, there is an option called Value Input Option.
- RAW: Enters data exactly as typed.
'=SUM(1+1)will be text. - USER_ENTERED: Simulates a human typing.
=SUM(1+1)becomes2. - Advice: Use USER_ENTERED. It allows you to inject formulas if needed and lets Google auto-detect dates.
Common Pitfalls
- Range Not Found: If you renamed “Sheet1” to “Data”, you must update n8n.
- Quota Limits: Google Sheets API has a limit of 60 writes per minute. If you try to dump 1,000 emails instantly, it will error. Use the Split in Batches node to slow it down.
Extensions: Notifications & Logic
The Context: Closing the Loop
The workflow works. But is it smart? What if the Invoice is for $10,000? Do you want that hidden in a sheet? No. You want a siren.
The Build: Adding Logic
1. The Gatekeeper (If Node)
- Add an If node after the Sheet Append.
- Condition:
Amount > 1000.
2. The Alert (Slack/Email)
- True Path: Connect to Slack.
- Message: “🚨 High Value Invoice Detected: ${{Amount}} from {{Sender}}.”
- False Path: Do nothing (or log to a ‘Low Priority’ sheet).
The “Pro Tip”: Deep Linking
In your Sheet (and your Slack alert), include a link back to the original email.
Google Email Link Format: https://mail.google.com/mail/u/0/#inbox/{{threadId}}.
This allows you to click one button and be taken instantly to the context.
Common Pitfalls
- Silent Failures: If the “Append Row” fails, you won’t know.
- Fix: Connect the “Error Output” (red dot) of the Sheets node to a Slack Alert node saying “Database Write Failed”.
Advanced: Handling Duplicates (The Idempotency Key)
The Context: The Double-Entry Problem
A standard flaw in beginner automations is Trigger-Happiness. If you update the email label, or if the trigger fires twice on the same email (e.g., a Reply), your bot will blindly append a second row. Your clean database becomes a mess of duplicates. You need a Gatekeeper.
The Build: The Lookup Method
1. The Concept: Unique IDs
- Every Gmail message has a unique
messageId. - We will use this ID as our “Key”.
2. The Check (Google Sheets Lookup)
- Before the “Append” node, add a Google Sheets node.
- Operation: Get Many Rows.
- Filter:
messageIdequals{{ $json.messageId }}.
3. The Gate (If Node)
- Add an If node.
- Condition: If the “Get Many Rows” node returns any items (Count > 0), it means the email is already in the sheet.
- True Path: Do Nothing (Stop).
- False Path: Proceed to Append Row.
The “Pro Tip”: Column Indexing
If you have 10,000 rows, scanning the sheet for an ID is slow. Instead of a “Lookup” node, keep a separate “Cache” (like Redis or a simple n8n static list) of the last 100 processed IDs for instant checking.
Common Pitfalls
- Assuming Subject is Unique: Never dedup based on “Subject”. Two clients might send emails with the subject “Invoice”. Always use
messageId.
Advanced: Saving Attachments (The File Locker)
The Context: Metadata is Insufficient
Knowing that “Invoice.pdf” exists is one thing. Having the file is another. You don’t want to dig through Gmail to find the PDF during an audit. You want the PDF in a Google Drive folder, and a link to that PDF in your Spreadsheet.
The Build: The Upload Flow
1. The Extraction
- In the Gmail Trigger, enable the option Download Attachments.
- This changes the data packet. It now includes a huge “Binary” object containing the file data.
2. The Upload (Google Drive)
- Add a Google Drive node.
- Operation: Upload File.
- File Content: Use the Binary Property name (usually
attachment_0). - Parent Folder: Paste the ID of your “Invoices 2026” folder.
- Output: The node will return a
webViewLink(The URL to view the file).
3. The Link (Update Sheet)
- Now, in your Append Row node, map the
webViewLinkto Column E (Link Structure). - Result: Your spreadsheet row now has a clickable blue link that opens the PDF directly.
The “Pro Tip”: Dynamic Folders
Don’t dump everything in one folder.
- Use a Google Drive node (Search) to look for a folder named
{{ $json.sender_name }}. - If it doesn’t exist, Create it.
- Upload the file inside that specific client folder. This creates a perfectly organized filesystem without you lifting a finger.
Common Pitfalls
- Large Files: Cloud Functions (which n8n sometimes runs on) have memory limits. If someone emails you a 50MB video, the workflow might crash OOM (Out of Memory). Filter attachments by size (
If size < 10MB). - MIME Types: Check that the attachment is actually a PDF (
application/pdf) and not a signature image (image/png).
Conclusion
You have just built a system that replaces the “Alt-Tab Dance”.
- Input: Gmail.
- Process: Filter -> Extract -> Format.
- Output: Google Sheets + Slack Alert.
This logic serves as the blueprint for every data entry task.
- Typeform -> Notion.
- Stripe -> Airtable.
- Webhook -> SQL. It is all the same pipe.
Your Homework: Modify this workflow to track Newsletters. Filter for “Substack”, extract the “Title” and “URL”, and save it to a “Reading List” sheet.
Ready to get advanced? Now that you have data, learn how to audit it with our guide on Buying Back Time.



