Setting up an automated Gmail newsletter summarizer with N8N and Claude will save you hours of inbox triage every week. I built one after my newsletter subscriptions got out of hand — 20+ emails a day I actually wanted to read but never had time to. Here’s the full setup, including the parts that broke and what fixed them.
What You’re Actually Building
Before getting into steps: this isn’t a simple “connect Gmail, get summary” flow. N8N will watch your Gmail inbox for newsletter emails, pass the content to Claude via the Anthropic API, and deliver a clean summary — either back to your inbox as a digest, to a Slack channel, or to a Notion database, depending on how you wire it.
The Claude part handles the actual summarization. N8N handles the plumbing. You need both working correctly for this to run reliably.
What You Need Before Starting
- An N8N instance (self-hosted or N8N Cloud)
- A Gmail account with API access enabled
- An Anthropic API key (from console.anthropic.com)
- Basic familiarity with N8N’s canvas — you don’t need to know how to code, but you should know what a node is
The Workflow Architecture
Here’s the basic flow before building it:
Gmail Trigger → Filter Node → Code Node (clean HTML) → Claude (Anthropic) Node → Output NodeEach stage matters. Skip the filter node and Claude will summarize every email including shipping notifications and OTP codes. Skip the HTML cleaning step and you’ll send Claude a wall of <div> tags instead of actual content.
Step-by-Step Build
Step 1: Set Up the Gmail Trigger Node
Add a Gmail Trigger node. Set it to poll your inbox every 15 or 30 minutes — real-time polling burns through API quota fast and isn’t necessary for newsletters.
Configure it to trigger on new emails, then add a label filter. The cleanest approach: create a Gmail label called “Newsletters” and apply it manually or via Gmail filters to your subscription emails. In the trigger node, set the label to “Newsletters.”
If you don’t want to use labels, you can filter by sender domain later in the flow, but labels keep things cleaner and reduce unnecessary processing.
One thing to watch: The Gmail Trigger returns the email body as raw HTML by default. Don’t try to feed that directly to Claude.
Step 2: Add a Filter Node
Add an IF node after the Gmail trigger. Set a condition: {{ $json.payload.headers.find(h => h.name === 'List-Unsubscribe') !== null }}
This checks for the List-Unsubscribe header, which is present in virtually every legitimate newsletter and absent from personal emails. It’s a cheap, reliable way to separate newsletter traffic from regular mail without needing complex sender lists.
And yes, some newsletters skip this header — particularly smaller personal newsletters. Your mileage may vary, but it catches the majority.
Step 3: Clean the Email Body
This is the step most tutorials skip, and it’s why their Claude outputs come back garbled or token-heavy.
Add a Code node (JavaScript). Use this to strip HTML tags, remove tracking pixels, collapse whitespace, and pull just the readable text:
javascript
const he = require('he');
const rawHtml = $input.item.json.payload.body?.data ||
$input.item.json.payload.parts?.[0]?.body?.data || '';
const decoded = Buffer.from(rawHtml, 'base64url').toString('utf-8');
const text = decoded
.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, '')
.replace(/<script[^>]*>[\s\S]*?<\/script>/gi, '')
.replace(/<[^>]+>/g, ' ')
.replace(/\s{2,}/g, '\n')
.replace(/https?:\/\/[^\s]+/g, '')
.trim();
const cleaned = he.decode(text).substring(0, 8000);
return [{ json: { cleanedBody: cleaned, subject: $input.item.json.payload.headers.find(h => h.name === 'Subject')?.value || 'No Subject' } }];The substring(0, 8000) cap is important. Claude can handle large contexts, but newsletter content beyond 8,000 characters is usually footer boilerplate and repeated CTAs — not worth summarizing and not worth burning tokens on.
Step 4: Add the Anthropic (Claude) Node
N8N has a native Anthropic node. Add it, connect your API key, and set the model to claude-sonnet-4-6.
Set the prompt. Don’t use a vague instruction like “summarize this email.” Be specific:
You are processing newsletter content. Extract and summarize the following in plain text:
1. Main topic or theme of this newsletter
2. Key points (maximum 5 bullet points)
3. Any links or resources mentioned (title only, no URLs)
4. One-sentence bottom line
Newsletter subject: {{ $json.subject }}
Content:
{{ $json.cleanedBody }}
Keep the summary under 200 words. Use plain text only — no markdown.The “no markdown” instruction matters if you’re delivering the output via email — markdown formatting in email bodies looks like raw symbols in most clients.
Step 5: Set Up Your Output
You have a few options here depending on what you actually want:
Option A — Email Digest: Add a Gmail node (send) to email the summary to yourself. Set the subject to Newsletter Digest: {{ $json.subject }}. This keeps everything in Gmail.
Option B — Slack: Add a Slack node and post to a private channel or DM. Good if you’re already in Slack all day.
Option C — Notion Database: Add a Notion node, create a database with columns for Date, Subject, Summary, and Source. The summaries become searchable, which is useful if you’re using newsletters for research or staying current on a topic.
I use Notion for this. Having a searchable archive of newsletter summaries ended up being more useful than I expected — I’ve gone back to find things I half-remembered reading months ago.
Step 6: Error Handling
Add an Error Trigger node connected to your flow. Route errors to a Slack message or email so you know when something breaks without having to check N8N manually.
Also: in the Claude node, set a timeout. If the Anthropic API doesn’t respond within 30 seconds, something’s wrong and you don’t want the workflow hanging.
What Actually Broke During My Setup
The Gmail body extraction took longer than it should have. Gmail’s API returns the email body base64-encoded, and the field path isn’t consistent — sometimes it’s in payload.body.data, sometimes it’s in payload.parts[0].body.data, and for multipart emails with both plain text and HTML versions, you sometimes get payload.parts[1].body.data for the HTML part.
I spent an embarrassing amount of time getting empty Claude outputs before I realized the Code node was hitting the wrong path and passing a blank string to the API. Claude will happily summarize an empty string and return something like “This newsletter appears to have no content.” — which isn’t an error N8N catches.
The fix was adding a conditional check in the Code node before passing to Claude: if cleanedBody.length < 100, skip the Claude call and log the email subject for manual review.
Prompt Engineering for Better Summaries
The default Claude outputs are fine, but a few tweaks make them noticeably better:
Add newsletter type context if you can. If you’re summarizing tech newsletters differently from finance or health newsletters, create separate workflow branches with tailored prompts. A tech newsletter summary should surface tools and releases. A finance newsletter summary should surface market movements and numbers.
Tell Claude what to ignore. Most newsletters are 30% actual content and 70% sponsor messages, CTAs, and unsubscribe links. Add to your prompt: “Ignore sponsor messages, advertisements, and calls to action. Focus only on editorial content.”
Ask for a one-sentence subject line. Instead of using the original email subject (which is often click-bait optimized), ask Claude to generate a neutral descriptive subject. More useful when scanning a digest.
Advanced: Batching Into a Daily Digest
Running the workflow per-email is fine, but if you get a lot of newsletters, you’ll end up with a lot of individual summaries. A cleaner setup:
- Instead of sending output immediately, write each summary to a Google Sheets row or N8N’s built-in data store
- Set a separate Schedule Trigger workflow to run at 8am daily
- That workflow reads all summaries from the past 24 hours, formats them into a single email or Notion page, and delivers one digest
This requires two separate N8N workflows and a shared data store between them, but it’s worth it if you’re processing more than five or six newsletters a day.
Common Failures and What They Mean
| Symptom | Likely Cause | Fix |
|---|---|---|
| Claude returns “no content found” | Email body path wrong in Code node | Log $input.item.json and check actual structure |
| Workflow triggers on non-newsletter emails | Missing or wrong label filter | Add List-Unsubscribe header check |
| HTML tags appearing in summary | HTML not stripped before Claude | Check Code node — he library may not be installed |
| API rate limit errors | Polling too frequently | Increase trigger interval to 30+ minutes |
| Summary cuts off mid-sentence | Token limit too low | Increase max_tokens in Claude node to 1024 |
FAQ
Does this work with N8N Cloud or only self-hosted?
Both. The Anthropic node and Gmail node are available in N8N Cloud. Self-hosted gives you more control over polling frequency and custom npm packages in Code nodes.
Can I use this with other email providers besides Gmail?
Yes — N8N has IMAP nodes that work with any email provider. The setup is slightly more manual since you lose the label-based filtering, but the rest of the workflow is identical.
Will Claude summarize every email or just newsletters?
Only what you send it. The filter step controls this — if you’re using Gmail labels, only labeled emails hit the Claude node.
How much does this cost to run?
At roughly 8,000 input tokens and 300 output tokens per newsletter, and using claude-sonnet-4-6, you’re looking at fractions of a cent per email. Fifty newsletters a day would cost pennies. The Gmail API is free within standard quota limits.
What if an email is in a language other than English?
Claude handles multilingual content well. Add “Respond in English regardless of the input language” to your prompt if you want consistent English output.
My Code node keeps failing on some emails. Why?
Multipart MIME emails are the usual culprit — particularly newsletters with both plain text and HTML versions. Log the raw $input.item.json structure for a failing email and check which parts[] index has the HTML content. It’s not always index 0.
Can I filter by topic and only summarize emails about specific subjects?
Yes. Add a second Claude call before the summarization step that classifies the email topic, then use an IF node to route only the topics you care about to the full summary flow. Costs slightly more but gives you topic-aware filtering without managing sender lists.
Editor’s Opinion
took me longer to get the gmail body extraction right than to build the rest of the workflow combined. the inconsistent payload structure is genuinely annoying and N8N doesn’t surface it as an error — it just passes a blank string along and everything looks like it worked. once that’s solid though, the whole thing runs cleanly. claude does a good job with newsletter content, better than i expected for generic prompts. the topic-specific prompts make a real difference if you want more than just bullet points. worth building if your inbox has gotten out of control.
