⏱️ 18 min read · 📥 2 free importable templates included
Last year I built a client an invoice-reminder automation. It worked for exactly seven days, then died silently — no error, no log, just an inbox that stopped responding. The cause wasn't the workflow. It was a Google Cloud setting with a seven-day fuse that no guide mentioned. That setting is section 2 of this article, and it's the difference between an automation that survives the week and one you rebuild every Monday.
Connecting Gmail to n8n takes three steps: (1) create a Google Cloud project and enable the Gmail API; (2) create OAuth credentials with the scopes you actually need, and — critically — publish the app to escape the 7-day token trap; (3) add the credential in n8n and build with the Gmail Trigger (read emails) and the Gmail node (send, reply, draft, label).
The two things that decide whether your automation lives or dies quietly: the OAuth setup in section 3 and the filter queries in section 5. Both get full treatment below, plus two tested, importable templates in section 9 — read the "Before You Activate" checklist first, especially if you're building an auto-responder.
1. What You Can Automate
Gmail is one of the most useful integrations in n8n, because email touches support, sales, invoices, notifications, and internal operations — almost every business process eventually routes through an inbox. The map, in the order you'll actually need it:
| Automation | Trigger | Action | Who needs it |
|---|---|---|---|
| Auto-responder | New email from X | Reply with a template | Everyone with a busy inbox |
| Invoice / order alerts | Email with attachment | Parse + save + notify | Freelancers, shops |
| Inbox triage | New email | Label, star, forward, archive | Teams drowning in mail |
| Lead capture | New email from your site | Append to Sheets/CRM | Marketers — pairs with our Lead Pipeline guide |
| Daily unread count | Schedule | Tally unread mail into a message | Anyone checking mail 20×/day |
| AI email assistant | New email | Agent drafts a reply for approval | Support teams — section 7 |
2. The 7-Day Token Trap (Fix This First)
Here's the bug nobody warns you about, and the one that cost my client a silent week: Google Cloud projects default to "Testing" mode, and in Testing mode, Gmail refresh tokens expire after roughly 7 days. No error, no warning — your automation simply stops connecting a week in.
The fix is usually permanent, but read the fine print below before you assume it's bulletproof:
Publish the app
Google Cloud Console → APIs & Services → OAuth consent screen → Publishing status → PUBLISH APP. Status changes from "Testing" to "In production".
Understand what "no verification needed" actually means
Google will ask about a formal verification process (privacy policy, security review, etc.). For a private app used only by your own Google account or a small list of test users, full verification is typically not required — but this can change if you request sensitive/restricted scopes later or distribute the app publicly. If Google ever forces verification on you, that's a signal your setup grew beyond "personal automation."
Reconnect the credential in n8n
Back in n8n, reconnect the credential (Sign in with Google → you'll see an "unverified app" warning → Advanced → Continue). The new refresh token issued after publishing is designed to persist until you revoke it — not 7 days.
3. Google Cloud OAuth Setup (The Part Everyone Gets Wrong)
The full path, in order. Skip a step and you'll meet one of the errors in section 11:
Create a project and enable the API
console.cloud.google.com → create a project (any name) → APIs & Services → Library → search "Gmail API" → Enable.
Configure the consent screen with the scopes you need
OAuth consent screen → External → fill App name + your email → add scopes based on the table below → add yourself as a test user → save → publish (section 2).
Create the OAuth credentials
Credentials → Create credentials → OAuth client ID → Web application. Copy the Client ID and Client Secret — they go into n8n next.
Follow the principle of least privilege — only request the scopes your workflows actually use:
| What you want to build | Scope to add |
|---|---|
| Read/trigger on incoming emails only | gmail.readonly |
| Send, reply, or draft emails | gmail.send |
| Apply labels, mark as read, archive | gmail.modify |
Building everything in this guide (trigger + auto-reply + labeling) needs all three. If you only need the daily-digest template, gmail.readonly alone is enough — don't request gmail.modify or gmail.send if nothing in your workflow uses them.
4. Connect n8n (Credential)
In n8n: Credentials → Add credential → Gmail OAuth2. Paste the Client ID and Client Secret. n8n will display an OAuth redirect/callback URL directly inside this credential screen — copy that exact URL into your Google Cloud OAuth client's "Authorized redirect URIs" field. This is the setting that actually controls whether the OAuth handshake succeeds; it is easy to confuse with other URL settings, so here's the difference:
| Setting | What it controls | Where it lives |
|---|---|---|
WEBHOOK_URL | Public base URL n8n uses to build webhook and OAuth callback links | Environment variable on your n8n instance |
N8N_EDITOR_BASE_URL | URL used for links inside the n8n editor UI itself | Environment variable, mostly relevant behind a reverse proxy |
| OAuth redirect URI | The exact callback n8n sends to Google — must match your Google Cloud client config | Shown live in the Gmail OAuth2 credential screen in n8n |
If you're self-hosting behind a reverse proxy with HTTPS, make sure WEBHOOK_URL reflects your real public HTTPS domain — our Docker guide covers the full reverse-proxy setup.
localhost too. But the moment you move to a server, update WEBHOOK_URL and re-copy the new redirect URI into Google Cloud, or the redirect dies (the classic localhost error our Webhook Localhost guide fixes).One credential, two nodes: the Gmail Trigger (incoming) and the Gmail node (outgoing) both select it. Connect once and both work.
5. Reading: The Trigger & Filter Queries
The Gmail Trigger works by polling — it checks your inbox on an interval you configure, it does not push instantly like a webhook. Its most useful setting is the q filter: a Gmail search query that decides which emails even reach your workflow. The operators worth memorizing:
| Operator | Matches | Example use |
|---|---|---|
from:[email protected] | A specific sender | Alert on invoices from your accountant |
subject:invoice | Keyword in subject | Catch payment emails |
is:unread | Only unread mail | Avoid re-processing on every poll |
has:attachment | Emails with files | File-collection automations |
label:updates | Inside a label | Scope the trigger to one folder |
newer_than:1d | Age window (approximate) | Daily batches — not a guaranteed exact 24h boundary |
Combine them like Gmail search: from:[email protected] has:attachment is:unread. The trigger's output is the full email: $json.subject, $json.textPlain (body), $json.id (the message ID — what the Reply operation needs), and binary attachments when you enable download.
simple option and between accounts/n8n versions — inspecting the real output for thirty seconds beats an hour of guessing why $json.something is empty.6. Sending: Text, HTML, Drafts & Attachments
The Gmail node handles the outgoing side. These four operations cover most workflows you'll build:
- Send — delivers immediately. Fields: To, Subject, Message.
- Reply — answers the email that triggered the workflow (needs the email's
id). - Draft — creates the email in your drafts without sending. The approval-friendly choice: pair it with our Human-in-the-Loop guide and nothing gets sent until a human reads it.
- Mark as Read / Add Labels — inbox housekeeping after processing, and the key to preventing reply loops (section 8).
Three details that elevate an automation from "works" to "looks professional":
- HTML emails: choose the HTML type and use basic tags (
<b>,<br>, inline styles). Skip complex CSS — Gmail strips it. - Attachments: in the node's Options, add attachments from binary data of previous nodes or from URLs.
- Reply-to: set a different reply address than the sender when the reply should land elsewhere.
7. The AI Email Assistant
The modern upgrade: attach the Gmail tool to an AI Agent, and the agent reads, drafts, and sends as part of its reasoning — you write the policy, the agent writes the mail. Two rules from running this in production:
- Never send without approval. The agent's Gmail tool belongs behind an approval checkpoint — our HITL guide wires the review step in one minute. An agent that can email customers unsupervised is a PR incident waiting.
- Give it context, not just instructions. The agent drafts better when it sees the customer's history — memory from our AI Agent Memory guide plus your RAG knowledge base from the RAG tutorial.
The pragmatic setup most people actually need: Trigger → Agent drafts a reply → Gmail node creates a Draft → you review and hit send. Zero risk, full speed.
8. Before You Activate
Any workflow that sends or replies automatically deserves a five-minute safety check before you flip it on:
- Test with a throwaway account first. Send yourself emails from a second Gmail address and watch the workflow run manually before activating it against real contacts.
- Never let an auto-responder reply to itself. Exclude your own sending address and generic
no-reply@senders from the trigger query. - Mark every processed email. Apply a label (e.g.
n8n-processed) right after acting on an email, and exclude that label from future polls — otherwise the same email can be replied to on every single poll cycle. - Don't reply to mailing lists or bulk senders. Add exclusions for known newsletter/marketing senders if your inbox receives them.
- Check your daily send quota (section 10) before pointing an automation at a busy inbox.
9. Free: 2 Importable Templates
Both templates use real n8n node structures. Import, pick your credential, adjust the filters, test manually, then activate.
Template 1 — Smart Auto-Responder (with loop protection)
Watches for a specific sender and replies with a tailored message. Three design choices make it safer than a basic version: it uses the Reply operation (answers the original message ID directly — no address-parsing to break), the q filter excludes anything already labeled as processed, and a final node applies that label so the same email is never answered twice.
n8n-processed manually in your Gmail account, then use the Gmail node's Label → Get Many operation once to find its ID, and paste that ID into the "Add Label" node below.{
"name": "Gmail Smart Auto-Responder",
"nodes": [
{
"parameters": {
"pollTimes": {
"item": [
{
"mode": "everyMinute"
}
]
},
"filters": {
"includeSpamTrash": false,
"includeDrafts": false,
"q": "from:[email protected] is:unread -label:n8n-processed"
},
"options": {}
},
"id": "trigger",
"name": "Gmail Trigger",
"type": "n8n-nodes-base.gmailTrigger",
"typeVersion": 1.3,
"position": [250, 300],
"credentials": {
"gmailOAuth2": {
"id": "YOUR_GMAIL_OAUTH_ID"
}
}
},
{
"parameters": {
"resource": "message",
"operation": "reply",
"messageId": "={{ $json.id }}",
"emailType": "text",
"message": "Hi,\n\nThanks for your message. I will get back to you within one business day.\n\nBest,\nWilliam",
"options": {}
},
"id": "reply",
"name": "Reply to Sender",
"type": "n8n-nodes-base.gmail",
"typeVersion": 1,
"position": [450, 300],
"credentials": {
"gmailOAuth2": {
"id": "YOUR_GMAIL_OAUTH_ID"
}
}
},
{
"parameters": {
"resource": "message",
"operation": "addLabels",
"messageId": "={{ $json.id }}",
"labelIds": ["YOUR_N8N_PROCESSED_LABEL_ID"]
},
"id": "add-label",
"name": "Mark as Processed",
"type": "n8n-nodes-base.gmail",
"typeVersion": 1,
"position": [650, 300],
"credentials": {
"gmailOAuth2": {
"id": "YOUR_GMAIL_OAUTH_ID"
}
}
}
],
"connections": {
"Gmail Trigger": {
"main": [
[
{
"node": "Reply to Sender",
"type": "main",
"index": 0
}
]
]
},
"Reply to Sender": {
"main": [
[
{
"node": "Mark as Processed",
"type": "main",
"index": 0
}
]
]
}
},
"active": false,
"settings": {
"executionOrder": "v1",
"saveManualExecutions": true
}
}
Template 2 — Daily Unread Email Counter (sent to Telegram)
What this actually does: every morning at 8, it counts unread emails from roughly the last day and sends the count as a message to Telegram. It is a counter, not an AI summary — it does not read or summarize email content. If you want real summaries of subject lines and senders, add an AI node between "Get Unread Emails" and "Build Message" (see the note below the code).
{
"name": "Gmail Daily Unread Counter to Telegram",
"nodes": [
{
"parameters": {
"rule": {
"interval": [
{
"field": "cronExpression",
"expression": "0 8 * * *"
}
]
}
},
"id": "schedule",
"name": "Every Day 8:00 AM",
"type": "n8n-nodes-base.scheduleTrigger",
"typeVersion": 1.2,
"position": [250, 300]
},
{
"parameters": {
"resource": "message",
"operation": "getAll",
"returnAll": true,
"additionalFields": {
"q": "is:unread newer_than:1d"
}
},
"id": "get-unread",
"name": "Get Unread Emails",
"type": "n8n-nodes-base.gmail",
"typeVersion": 1,
"position": [450, 300],
"credentials": {
"gmailOAuth2": {
"id": "YOUR_GMAIL_OAUTH_ID"
}
}
},
{
"parameters": {
"jsCode": "const emails = $input.all();\n\n// This counts unread emails — it does NOT summarize content.\n// To add real summaries, insert an AI node before this step\n// and pass subject/sender fields into the prompt.\nconst count = emails.length;\nconst summary = `📬 Morning check: ${count} unread email${count === 1 ? '' : 's'} from roughly the last 24 hours.`;\n\nreturn [{ json: { count, summary } }];"
},
"id": "summarize",
"name": "Build Message",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [650, 300]
},
{
"parameters": {
"chatId": "YOUR_TELEGRAM_CHAT_ID",
"text": "={{ $json.summary }}"
},
"id": "telegram",
"name": "Telegram Digest",
"type": "n8n-nodes-base.telegram",
"typeVersion": 1.2,
"position": [850, 300],
"credentials": {
"telegramApi": {
"id": "YOUR_TELEGRAM_CREDENTIAL_ID"
}
}
}
],
"connections": {
"Every Day 8:00 AM": {
"main": [
[
{
"node": "Get Unread Emails",
"type": "main",
"index": 0
}
]
]
},
"Get Unread Emails": {
"main": [
[
{
"node": "Build Message",
"type": "main",
"index": 0
}
]
]
},
"Build Message": {
"main": [
[
{
"node": "Telegram Digest",
"type": "main",
"index": 0
}
]
]
}
},
"active": false,
"settings": {
"executionOrder": "v1",
"saveManualExecutions": true
}
}
- One credential for both templates —
YOUR_GMAIL_OAUTH_ID, the same credential from section 4. - Verify your output shape once: run the trigger manually (Execute workflow) and inspect the JSON before wiring downstream nodes — field names vary between the "simple" mode and raw payloads, and between n8n versions.
- Want a real AI summary instead of a count? Insert an AI node (OpenAI/Anthropic) between "Get Unread Emails" and "Build Message" that receives subjects and senders and returns 2–3 bullet points — be mindful of sending email content to a third-party API if it contains sensitive data.
- Extend the digest to Gmail instead of Telegram: swap the Telegram node for a Gmail Send node — same structure, one node change.
- More ready-to-import blueprints live in our n8n Workflow Templates Library.
10. Rate Limits & Quotas
The numbers that decide whether your automation is polite or blocked:
| Limit | Value | What it means for you |
|---|---|---|
| Daily send quota | ~100/day (consumer Gmail) · ~500/day (Workspace, plan-dependent) | Batch campaigns hit the ceiling fast — pace them |
| Polling | Every poll costs API quota | Poll every 1–15 min; avoid sub-minute intervals |
| Batch sends | No fixed hard limit, but bursts get throttled | Add a Wait node (1–2 s) between sends in loops |
| Refresh token | Long-lived after publishing · can expire after months of inactivity or revoked consent | Publish (section 2) and reconnect if a credential ever fails |
11. The 10 Most Common Errors & Fixes
| # | Error / Symptom | Cause | Fix |
|---|---|---|---|
| 1 | Credential dies roughly every 7 days, no error | App in Testing mode — refresh token has a short lifespan by design | Publish to In production, then reconnect once (section 2) |
| 2 | invalid_grant / token expired |
Rotated client secret, long inactivity, or the 7-day trap | Reconnect the credential; fix the root cause in section 2 |
| 3 | Trigger never fires | Workflow not activated, or the q filter is too narrow |
Activate, then test with a wide query (is:unread) first |
| 4 | Request had insufficient authentication scopes |
The action needs a scope you didn't add (send vs readonly) | Add the missing scope to the app (section 3), then reconnect |
| 5 | Trigger re-processes the same email repeatedly | No dedup filter — the email stays unread/unlabeled | Add is:unread and a processed-label exclusion (section 9) |
| 6 | Auto-responder creates a reply loop with another bot | No exclusion for the target's own auto-reply or your own address | Exclude no-reply@ and your own sending address in the query (section 8) |
| 7 | Failed to send email in loops |
Hit the daily send quota or burst throttling | Pace with a Wait node; check quota (section 10) |
| 8 | OAuth redirect fails after moving servers | WEBHOOK_URL or the Google Cloud redirect URI still points at the old domain |
Update WEBHOOK_URL, re-copy the new redirect URI into Google Cloud, then reconnect (section 4) |
| 9 | HTML email arrives as raw tags | Sent as text type while containing HTML | Switch the node to the HTML email type |
| 10 | "Google hasn't verified this app" screen | Your app is published but unverified — expected for personal use | Confirm it's your own app, then Advanced → Continue. No verification needed for personal scope levels |
12. Frequently Asked Questions
Create a Google Cloud project, enable the Gmail API, create OAuth credentials, then add a Gmail OAuth2 credential in n8n with the client ID and secret. Connect your account once and both Gmail nodes use the same credential (sections 3–4).
Your Google Cloud app is in Testing mode, where refresh tokens have a short lifespan by design. Publish the app to In production, then reconnect the credential once — personal apps typically don't need Google verification, you just accept the unverified-app warning after confirming it's your own app (section 2).
The usual causes: the workflow is not activated, the polling interval is too long, the q filter is too narrow, or the token expired. Test with a wide query (is:unread) first, then tighten (error #3 in section 11).
Use Gmail search operators in the q field: from:[email protected] for a sender, subject:invoice for subjects, is:unread, label:updates, or has:attachment. The trigger only fires for emails matching the query (section 5).
Yes. In the Gmail Send operation, open Options and add attachments — they can come from previous nodes as binary data or from URLs (section 6).
Yes. Choose the HTML email type and use HTML tags in the message field. Keep it simple — many email clients strip complex CSS, so basic tags and inline styles are safest (section 6).
Send delivers immediately. Draft creates the email in your Gmail drafts folder without sending — perfect for approval flows, where a person reviews and hits send manually (section 6).
Sending is capped around 100 emails per day for consumer accounts and about 500 for Google Workspace (varies by plan). Poll every 1–15 minutes for most automations and batch sends with a Wait node between messages (section 10).
Exclude your own sending address and no-reply@ senders from the trigger query, and apply a processed label to every email right after you reply to it — then exclude that label from future polls with -label:n8n-processed (section 9).
Yes. Attach the Gmail tool to an AI Agent node and the agent can read, draft, and send emails as part of its reasoning — combine it with an approval checkpoint so the agent never sends without a human review (section 7).
Sources & References
- n8n Documentation — Gmail node
- n8n Documentation — Gmail Trigger node
- Google — OAuth token expiration (testing mode behavior)
- Google — Gmail sending limits
- Google — Gmail search operators
