Sunday, April 5

Email is where most business logic goes to die. You’ve got support requests mixed with sales leads, partner updates buried under newsletters, and urgent ops alerts sitting unread next to someone’s invoice. If you’re handling more than 50 emails a day, manual triage is already costing you hours you don’t have. An n8n Claude workflow can fix this — not theoretically, but in production, running today, classifying and routing real messages without a human in the loop.

This article walks through a complete implementation: connecting a Gmail or IMAP inbox to n8n, calling Claude via the Anthropic API to classify each email, and routing the result to Slack, a CRM, a ticketing system, or wherever your team actually works. I’ll cover the API setup, the prompt engineering that actually works for classification, and the error handling you’ll need before this touches a real inbox.

What You’re Actually Building

The workflow has four stages:

  1. Trigger — poll or webhook on a Gmail/IMAP mailbox
  2. Prepare — extract sender, subject, and body; strip HTML; truncate safely
  3. Classify — send structured payload to Claude, get back a category + confidence + suggested action
  4. Route — branch on category to Slack, HubSpot, Zendesk, or whatever sink you’re using

This isn’t a toy demo. By the end you’ll have something you can drop into a real business and it will work — with one caveat I’ll address in the error handling section: Claude occasionally returns malformed JSON when the email body is weird. You need to handle that.

Prerequisites and API Setup

n8n Instance

Run n8n self-hosted (Docker is easiest) or use n8n Cloud. For production email triage, self-hosted gives you better control over credentials and execution logs. The Docker one-liner:

docker run -it --rm \
  --name n8n \
  -p 5678:5678 \
  -v ~/.n8n:/home/node/.n8n \
  n8nio/n8n

Pin the version tag in production — n8nio/n8n:1.40.0 or whatever the current stable release is. latest has broken workflows before on minor releases.

Anthropic API Key

Get your key from console.anthropic.com. In n8n, go to Credentials → New → Header Auth. Set the header name to x-api-key and paste your key as the value. You’ll reference this credential in the HTTP Request node that calls Claude.

Model choice matters here. For email classification, Claude Haiku 3.5 is the right call — it’s fast, cheap (roughly $0.0008 per 1K input tokens, $0.004 per 1K output at current pricing), and accurate enough for category classification tasks. Save Claude Sonnet or Opus for cases where you need nuanced drafting or complex reasoning. Triage doesn’t need that.

Building the n8n Workflow Step by Step

Step 1: Gmail Trigger Node

Add a Gmail Trigger node. Set it to poll every minute (or use push via Gmail’s watch API if you need sub-minute latency). Configure it to watch your inbox with the filter is:unread. You don’t want to re-process emails you’ve already handled.

Key settings to enable:

  • Mark as read after trigger fires — prevents re-processing on next poll
  • Download attachments — off for now; you can add attachment handling later
  • Simplify output — on; it strips the raw MIME mess into usable fields

The output gives you from, subject, text, html, date, and id. You want text over html — Claude doesn’t need the markup and it burns tokens.

Step 2: Prepare the Payload (Function Node)

Before calling Claude, clean the input. Email bodies can be enormous. Claude’s context window can handle it, but you’ll pay for every token and most of the signal is in the first 500 words anyway.

// n8n Function node — runs in the workflow before the Claude HTTP call
const email = $input.first().json;

// Prefer plain text; fall back to stripping HTML tags
let body = email.text || email.html?.replace(/<[^>]+>/g, ' ') || '';

// Truncate to ~2000 chars — enough context, not wasteful
body = body.trim().slice(0, 2000);

// Strip repeated whitespace that inflates token count
body = body.replace(/\s+/g, ' ');

return [{
  json: {
    email_id: email.id,
    from: email.from,
    subject: email.subject || '(no subject)',
    body: body,
    date: email.date
  }
}];

Step 3: Claude Classification (HTTP Request Node)

Add an HTTP Request node. Configure it as follows:

  • Method: POST
  • URL: https://api.anthropic.com/v1/messages
  • Authentication: your Header Auth credential
  • Headers: anthropic-version: 2023-06-01, content-type: application/json

The body (set as JSON expression):

{
  "model": "claude-haiku-4-5",
  "max_tokens": 256,
  "messages": [
    {
      "role": "user",
      "content": "You are an email classification assistant. Classify the following email into exactly one category and return valid JSON only — no markdown, no explanation.\n\nCategories:\n- support: customer asking for help or reporting a bug\n- sales: inbound lead, pricing question, or partnership inquiry\n- billing: invoice, payment issue, or subscription question\n- ops: internal team message, vendor, or operational matter\n- spam: unsolicited marketing or irrelevant\n- other: doesn't fit above\n\nReturn this exact JSON structure:\n{\"category\": \"<category>\", \"confidence\": <0.0-1.0>, \"reason\": \"<one sentence>\", \"priority\": \"high|medium|low\"}\n\nEmail:\nFrom: {{ $json.from }}\nSubject: {{ $json.subject }}\nBody: {{ $json.body }}"
    }
  ]
}

A few things to note about this prompt. The instruction “no markdown, no explanation” is load-bearing — without it, Claude will wrap the JSON in a code fence roughly 20% of the time, which breaks your downstream JSON parser. The explicit schema also helps: Claude is far less likely to hallucinate extra fields when you show it exactly what you want back.

Step 4: Parse the Response (Function Node)

// Parse Claude's response and handle edge cases
const response = $input.first().json;
const rawContent = response.content?.[0]?.text || '';

let classification;
try {
  // Strip markdown code fences if Claude added them despite instructions
  const cleaned = rawContent.replace(/```json?\n?/g, '').replace(/```/g, '').trim();
  classification = JSON.parse(cleaned);
} catch (e) {
  // Fallback — don't crash the workflow, route to manual review
  classification = {
    category: 'other',
    confidence: 0,
    reason: 'Parse error: ' + rawContent.slice(0, 100),
    priority: 'medium'
  };
}

// Merge classification back with email metadata
return [{
  json: {
    ...$node["Prepare Payload"].json,  // original email fields
    ...classification
  }
}];

The try/catch is not optional. In three months of production use across a client’s support inbox, I saw Claude return malformed JSON approximately 1–2% of the time — always correlated with emails that had unusual encoding or very short bodies. The fallback routes those to a “manual review” bucket rather than crashing the workflow.

Step 5: Route With a Switch Node

Add an n8n Switch node. Set the value to {{ $json.category }} and create output branches for each category: support, sales, billing, ops, spam, other.

Each branch connects to whatever action makes sense:

  • support → Create Zendesk ticket (or Linear issue, or Intercom conversation)
  • sales → Create HubSpot contact + deal, post to #sales-leads Slack channel
  • billing → Post to #billing Slack with full email body and sender
  • ops → Log to Notion database or forward to relevant team member
  • spam → Apply Gmail label “auto-spam”, archive, done
  • other → Post to #email-review Slack for human eyes

Production Error Handling You Actually Need

Three failure modes will bite you in production if you don’t handle them upfront.

Rate Limits

Anthropic’s default rate limits on Haiku are generous but not infinite. If you’re processing a burst of 50 emails at once (Monday morning inbox flood), you may hit the requests-per-minute limit. In n8n, set a Wait node between emails during batch processing, or use n8n’s built-in rate limiting on the execution queue. Don’t let the workflow spam the API in parallel without throttling.

API Timeouts

Set a timeout on your HTTP Request node — 30 seconds is reasonable for Haiku. If Claude doesn’t respond in time, the node should fail gracefully. Wire the error output of the HTTP Request node to a fallback path that routes the email to manual review. An unhandled timeout that kills the whole workflow will cause emails to go missing.

Gmail API Quota

Gmail’s API has a daily quota for read operations. If you’re polling every minute on an active inbox, check your quota usage in Google Cloud Console. For high-volume inboxes (1000+ emails/day), consider switching to Gmail Push Notifications via Pub/Sub instead of polling — it’s more complex to set up but doesn’t burn quota.

Tuning Classification Accuracy

Out of the box with the prompt above, you should see roughly 85–90% accuracy on a typical business inbox. To push higher:

Add examples to the prompt. Few-shot examples dramatically improve consistency. Add 2–3 real examples from your actual inbox with the correct classification labelled. This matters most for edge cases like a billing question written in casual language that looks like support.

Use confidence thresholds. In your routing Switch node, add a condition: if confidence < 0.7, always route to manual review regardless of category. Low-confidence classifications are more likely to be wrong, and it’s better to have a human handle the ambiguous 10% than to route a sales lead to the spam bucket.

Log everything for a week before going fully automated. Run the workflow with notifications only — don’t take action, just log what Claude would have done. Review misclassifications and update your category definitions or prompt accordingly. This one week of shadow mode will catch problems before they annoy real customers.

What This Costs in Practice

At current Haiku pricing: a typical email classification request uses roughly 300–500 input tokens (prompt + email) and 50–100 output tokens (the JSON response). Call it 600 tokens total per email at the high end.

At $0.0008 per 1K input and $0.004 per 1K output: roughly $0.0007 per email classified. For 500 emails/day, that’s $0.35/day — about $10/month. For 5,000 emails/day, you’re at $105/month. That’s the full AI cost; your n8n hosting and Gmail API usage are separate but minimal.

When to Use This Setup

Solo founders and small teams (under 200 emails/day): This workflow pays for itself in the first week. Set it up on n8n Cloud (free tier covers the volume), connect it to a single shared inbox, and route to Slack channels your team already monitors. Total setup time: 2–3 hours including testing.

Growing teams with a shared support inbox: Add a confidence threshold gate and connect the high-confidence support branch directly to Zendesk or Intercom ticket creation. Keep a manual review Slack channel for low-confidence and “other” categories. You’ll eliminate the first-pass triage entirely.

Agencies managing multiple client inboxes: Run separate n8n workflow instances (or separate Gmail triggers with different credentials) per client. The prompt is easy to customize per client — just update the category definitions and routing destinations. The core classification logic stays the same.

The n8n Claude workflow pattern described here is stable, cheap to run, and genuinely useful. It’s not AI for its own sake — it’s AI doing the mechanical part of a real job so that humans only see the emails that need human judgment. Build the shadow-mode version first, measure accuracy against your actual inbox for a week, then flip it to fully automated. That’s how you ship this without breaking trust in your operations.

Editorial note: API pricing, model capabilities, and tool features change frequently — always verify current details on the vendor’s website before building in production. Code examples are tested at time of writing; pin your dependency versions to avoid breaking changes. Some links in this article may be affiliate links — we may earn a commission if you sign up, at no extra cost to you.

Share.
Leave A Reply