Loading...
Loading...

I spent two hours a day on email. Not important email. The sorting, the triaging, the "I'll deal with this later" and then dealing with it later at midnight. The requests that needed a one-line reply buried under four paragraphs of context. The newsletters I subscribed to with good intentions and then never read.
I automated most of that. Here's exactly what I built, what worked, and what didn't.
Full transparency: the system handles about 78% of my email without me reading it. That's not perfect. The remaining 22% still takes time. But the 78% number represents roughly 90 minutes reclaimed every day.
Let me show you the result before diving into the build.
When an email arrives:
support_request, sales_inquiry, newsletter, personal, vendor_invoice, meeting_request, requires_attentionnewsletter: Summarized into a weekly digest. Archived.sales_inquiry: Standard response sent, lead logged in CRM.support_request: Triaged by urgency. Simple questions auto-answered. Complex ones flagged.vendor_invoice: Amount extracted, approval routed. Auto-approved under $200.meeting_request: Calendly link sent automatically if my calendar is free.requires_attention: Summarized and added to my priority queue.This is not science fiction. The technology to build this exists today.
| Component | Tool | Why |
|---|---|---|
| Email access | Gmail API | Most common; clear docs |
| Workflow orchestration | n8n (self-hosted) | Visual flows, easy to debug |
| AI classification | Claude Haiku | Fast, cheap, accurate enough |
| AI response generation | Claude Sonnet | Better quality for replies |
| CRM integration | Notion database | Simple, visual |
| Calendar | Google Calendar API | For meeting detection |
You can swap any of these. The pattern works with Outlook, Zapier, OpenAI, Airtable. The principles are the same.
The classifier is the brain of the system. Everything depends on it being accurate.
// email-classifier.ts
import Anthropic from "@anthropic-ai/sdk";
export type EmailCategory =
| "newsletter"
| "sales_inquiry"
| "support_request"
| "meeting_request"
| "vendor_invoice"
| "personal"
| "requires_attention";
export interface EmailClassification {
category: EmailCategory;
confidence: number; // 0-1
urgency: "low" | "medium" | "high";
summary: string;
suggestedAction: string;
extractedData: Record<string, unknown>;
}
const anthropic = new Anthropic();
export async function classifyEmail(
from: string,
subject: string,
body: string,
myContext: string // Brief description of who you are and your role
): Promise<EmailClassification> {
const response = await anthropic.messages.create({
model: "claude-haiku-4-20250514",
max_tokens: 512,
system: `You are an email classifier. Analyze the email and return a JSON classification.
Categories:
- newsletter: Marketing emails, subscriptions, automated digests
- sales_inquiry: Someone trying to sell something to us
- support_request: Customer or user needing help
- meeting_request: Someone requesting a meeting or call
- vendor_invoice: Bill or invoice that needs processing
- personal: Personal or social communication
- requires_attention: Important email requiring human judgment
Return valid JSON only. No explanation.`,
messages: [
{
role: "user",
content: `My context: ${myContext}
Email from: ${from}
Subject: ${subject}
Body: ${body.slice(0, 2000)}`,
},
],
});
const text = response.content[0].type === "text" ? response.content[0].text : "{}";
try {
return JSON.parse(text);
} catch {
return {
category: "requires_attention",
confidence: 0.3,
urgency: "medium",
summary: "Could not classify email automatically",
suggestedAction: "Manual review required",
extractedData: {},
};
}
}For common email types, build template generators that produce personalized responses:
// response-generator.ts
import Anthropic from "@anthropic-ai/sdk";
import { EmailClassification } from "./email-classifier.js";
const anthropic = new Anthropic();
export async function generateAutoResponse(
emailContent: { from: string; subject: string; body: string },
classification: EmailClassification,
myInfo: {
name: string;
role: string;
calendarLink: string;
supportUrl: string;
}
): Promise<string | null> {
// Only auto-respond to these categories
const autoRespondCategories = ["meeting_request", "sales_inquiry", "support_request"];
if (!autoRespondCategories.includes(classification.category)) {
return null;
}
if (classification.confidence < 0.8) {
// Too uncertain to auto-respond
return null;
}
const templates: Record<string, string> = {
meeting_request: `Generate a polite, brief reply to this meeting request.
- Thank them for reaching out
- Direct them to my calendar: ${myInfo.calendarLink}
- Keep it under 50 words
- Sign as ${myInfo.name}`,
sales_inquiry: `Generate a polite decline to this sales email.
- Be warm but firm
- Don't apologize
- Keep it under 40 words
- Don't offer to follow up
- Sign as ${myInfo.name}`,
support_request: `Generate a helpful first-response to this support request.
- Acknowledge their issue
- Direct them to: ${myInfo.supportUrl}
- If the question seems simple, attempt to answer it briefly
- Keep it under 100 words
- Sign as ${myInfo.name}, ${myInfo.role}`,
};
const prompt = templates[classification.category];
if (!prompt) return null;
const response = await anthropic.messages.create({
model: "claude-sonnet-4-20250514",
max_tokens: 300,
messages: [
{
role: "user",
content: `Original email:\nFrom: ${emailContent.from}\nSubject: ${emailContent.subject}\nBody: ${emailContent.body.slice(0, 1000)}\n\nTask: ${prompt}`,
},
],
});
return response.content[0].type === "text" ? response.content[0].text : null;
}This is where it comes together. n8n connects Gmail to your classifier and back to Gmail for responses.
The workflow in n8n looks like this:
Gmail Trigger (new email)
|
v
Function Node (prepare email data)
|
v
HTTP Request (call your classifier API)
|
v
Switch Node (route by category)
| | | |
newsletter sales support requires_attention
| | | |
Archive Generate Generate Add to priority
response response queue in Notion
| | |
Email Send reply Send reply
digest
You can build this in n8n's visual interface without writing much code. The classifier runs as a local Node.js HTTP endpoint that n8n calls.
To expose your classifier as an API:
// api-server.ts
import express from "express";
import { classifyEmail } from "./email-classifier.js";
const app = express();
app.use(express.json());
app.post("/classify", async (req, res) => {
const { from, subject, body, context } = req.body;
try {
const classification = await classifyEmail(from, subject, body, context);
res.json(classification);
} catch (error) {
res.status(500).json({ error: String(error) });
}
});
app.listen(3001, () => console.log("Email classifier running on port 3001"));Instead of receiving 15 newsletters per day, receive one summary per week.
// digest-generator.ts
import Anthropic from "@anthropic-ai/sdk";
interface NewsletterItem {
from: string;
subject: string;
summary: string;
date: Date;
}
export async function generateWeeklyDigest(
newsletters: NewsletterItem[]
): Promise<string> {
const anthropic = new Anthropic();
const response = await anthropic.messages.create({
model: "claude-sonnet-4-20250514",
max_tokens: 2048,
messages: [
{
role: "user",
content: `Create a concise weekly newsletter digest from these ${newsletters.length} items.
Group by topic/source. Lead with the most valuable information.
Be opinionated about what's worth my time. Skip obvious filler.
Format as HTML for email. Keep total under 500 words.
Newsletters:
${newsletters.map(n => `FROM: ${n.from}\nSUBJECT: ${n.subject}\nSUMMARY: ${n.summary}`).join("\n\n")}`,
},
],
});
return response.content[0].type === "text" ? response.content[0].text : "<p>Error generating digest</p>";
}False positive on auto-responses. Early version sent a "thanks but no thanks" sales decline to a potential enterprise customer. Wrong classification, significant consequence. Fixed by raising the confidence threshold for auto-responses to 0.9 and adding a "known contacts" whitelist that always routes to manual review.
Newsletter detection too aggressive. The AI classified some important project updates from contractors as newsletters and archived them. Fixed by adding domain allowlists for known important senders.
Response quality variance. Auto-generated responses were sometimes slightly off-tone. Fixed by adding a review step for the first 50 auto-responses, using those to refine the prompts, then disabling review for high-confidence categories.
No error handling in the n8n workflow. When the classifier API was down, emails just stopped being processed. Added error handling that falls back to routing everything to the priority queue when the classifier is unavailable.
Some email should never be auto-responded:
Build an allowlist/denylist system and respect it. The time you save on automation is wasted if you accidentally mishandle something important.
| Metric | Before | After |
|---|---|---|
| Time spent on email daily | ~2 hours | ~25 minutes |
| Emails requiring manual response | 60-80/day | 12-18/day |
| Response time for auto-handled emails | Hours | Minutes |
| Missed important emails | Occasional | Zero (so far) |
The mental load reduction is harder to measure but more significant. Email no longer occupies background attention. It's a handled system, not a pile of obligations.

n8n can connect anything to anything. Most people spend days fighting the setup instead of building automations. Here's how to get running fast and stay running.

Stop watching tutorials about tutorials. Here's how to actually build an AI agent that does something useful, from zero, in one sitting.

Cold outreach is broken. Spray and pray is dead. Here's how to build an AI lead generation system that finds the right people and says the right thing.
Stop reading about AI and start building with it. Book a free discovery call and see how AI agents can accelerate your business.