Why You Need Automated Email Classification
Sifting through a crowded inbox to distinguish between urgent requests, actionable tasks, and FYI messages takes more cognitive energy than you might think. By combining Gemini API's Function Calling with structured output, you can build an intelligent system that automatically classifies incoming emails, assigns priorities, and extracts action items.
This is a sample of the kind of practical system-building content available to Gemini Lab members. If you find this useful, consider joining our membership for more in-depth technical guides.
System Architecture Design
The email classification system consists of three stages:
Stage 1: Classification — Determine the email's category and priority level
Stage 2: Summarization — Extract key points in three sentences or fewer
Stage 3: Action Extraction — Generate action items when a response or follow-up is needed
// types/email.ts
export interface EmailInput {
id: string;
from: string;
to: string;
subject: string;
body: string;
receivedAt: string;
}
export type EmailCategory =
| "urgent" // Requires immediate attention
| "action" // Needs response (not urgent)
| "info" // FYI / informational
| "newsletter" // Newsletters / marketing
| "spam"; // Spam / unwanted
export type Priority = "high" | "medium" | "low";
export interface ClassifiedEmail extends EmailInput {
category: EmailCategory;
priority: Priority;
summary: string;
actionItems: string[];
confidence: number; // Classification confidence (0-1)
}Classifying Emails with Function Calling
Gemini's Function Calling lets you define a strict output schema for email classification. This ensures type-safe data handling in downstream processing.
import { GoogleGenerativeAI } from "@google/generative-ai";
const genAI = new GoogleGenerativeAI("YOUR_GEMINI_API_KEY");
// Email classification tool definition
const classifyEmailTool = {
functionDeclarations: [{
name: "classify_email",
description: "Analyze an incoming email and return its category, priority, summary, and action items",
parameters: {
type: "object",
properties: {
category: {
type: "string",
enum: ["urgent", "action", "info", "newsletter", "spam"],
description: "Email category",
},
priority: {
type: "string",
enum: ["high", "medium", "low"],
description: "Priority level",
},
summary: {
type: "string",
description: "Summary of the email body (3 sentences max)",
},
action_items: {
type: "array",
items: { type: "string" },
description: "List of required actions (empty array if none needed)",
},
confidence: {
type: "number",
description: "Classification confidence score (0.0-1.0)",
},
},
required: ["category", "priority", "summary", "action_items", "confidence"],
},
}],
};
async function classifyEmail(email: EmailInput): Promise<ClassifiedEmail> {
const model = genAI.getGenerativeModel({
model: "gemini-2.5-flash",
tools: [classifyEmailTool],
});
const prompt = `Analyze the following email:
From: ${email.from}
Subject: ${email.subject}
Body:
${email.body}
Classify this email using the classify_email function.`;
const result = await model.generateContent(prompt);
const response = result.response;
// Extract the Function Call result
const functionCall = response.candidates?.[0]?.content?.parts?.find(
(part) => part.functionCall
)?.functionCall;
if (!functionCall || functionCall.name !== "classify_email") {
throw new Error("Expected classify_email function call");
}
const args = functionCall.args as {
category: EmailCategory;
priority: Priority;
summary: string;
action_items: string[];
confidence: number;
};
return {
...email,
category: args.category,
priority: args.priority,
summary: args.summary,
actionItems: args.action_items,
confidence: args.confidence,
};
}By defining the schema in functionDeclarations, Gemini returns data that conforms to this structure. The enum constraints on categories ensure no unexpected values slip through.
Batch Processing for High-Volume Inboxes
When you have hundreds of emails to process, handling them one by one is too slow. Implement batch processing with concurrency control and rate limiting.
// utils/batch-classifier.ts
// Semaphore for concurrency limiting
class Semaphore {
private queue: (() => void)[] = [];
private running = 0;
constructor(private max: number) {}
async acquire(): Promise<void> {
if (this.running < this.max) {
this.running++;
return;
}
return new Promise<void>((resolve) => {
this.queue.push(resolve);
});
}
release(): void {
this.running--;
const next = this.queue.shift();
if (next) {
this.running++;
next();
}
}
}
async function batchClassify(
emails: EmailInput[],
concurrency = 5
): Promise<ClassifiedEmail[]> {
const semaphore = new Semaphore(concurrency);
const results: ClassifiedEmail[] = [];
const errors: Array<{ email: EmailInput; error: string }> = [];
const tasks = emails.map(async (email) => {
await semaphore.acquire();
try {
const classified = await classifyEmail(email);
results.push(classified);
} catch (error) {
errors.push({
email,
error: error instanceof Error ? error.message : "Unknown error",
});
} finally {
semaphore.release();
}
});
await Promise.all(tasks);
if (errors.length > 0) {
console.warn(`${errors.length}/${emails.length} emails failed classification`);
}
// Sort by priority (high → medium → low)
const priorityOrder = { high: 0, medium: 1, low: 2 };
results.sort((a, b) => priorityOrder[a.priority] - priorityOrder[b.priority]);
return results;
}Customizing Classification Rules
Add a configuration layer to adapt classification behavior to different business needs and personal workflows.
// config/classification-rules.ts
interface ClassificationRules {
urgentSenders: string[]; // Always treat as urgent
infoOnlyDomains: string[]; // Always treat as info
spamKeywords: string[]; // Keywords that trigger spam classification
customPromptRules: string; // Additional rules in natural language
}
const defaultRules: ClassificationRules = {
urgentSenders: [
"boss@company.com",
"ceo@company.com",
],
infoOnlyDomains: [
"noreply@github.com",
"notifications@slack.com",
],
spamKeywords: [
"unsubscribe",
"limited time offer",
],
customPromptRules: `
- Client billing emails should always be "urgent" + "high"
- Internal weekly reports should be "info" + "low"
- Recruiting-related emails should be "action" + "medium"
`,
};
function buildClassificationPrompt(
email: EmailInput,
rules: ClassificationRules
): string {
// Pre-rule checks
if (rules.urgentSenders.includes(email.from)) {
return `This email is from an important sender (${email.from}). Classify it as "urgent".\n\n${email.body}`;
}
const domain = email.from.split("@")[1];
if (domain && rules.infoOnlyDomains.includes(domain)) {
return `This email is from a notification domain (${domain}). Classify it as "info".\n\n${email.body}`;
}
return `Follow these additional rules when classifying:
${rules.customPromptRules}
From: ${email.from}
Subject: ${email.subject}
Body: ${email.body}`;
}Generating a Classification Dashboard
Create a human-readable report from the classification results.
function generateReport(emails: ClassifiedEmail[]): string {
const byCategory = emails.reduce((acc, email) => {
acc[email.category] = (acc[email.category] || 0) + 1;
return acc;
}, {} as Record<string, number>);
const urgent = emails.filter(e => e.category === "urgent");
const actions = emails.filter(e => e.category === "action");
let report = `Email Classification Report\n`;
report += `============================\n`;
report += `Total: ${emails.length} emails\n\n`;
Object.entries(byCategory).forEach(([cat, count]) => {
const icon = { urgent: "RED", action: "YELLOW", info: "BLUE", newsletter: "NEWS", spam: "SPAM" }[cat] || "-";
report += `[${icon}] ${cat}: ${count}\n`;
});
if (urgent.length > 0) {
report += `\n[URGENT] Requires immediate attention:\n`;
urgent.forEach(e => {
report += ` - [${e.from}] ${e.subject}\n ${e.summary}\n`;
});
}
if (actions.length > 0) {
report += `\n[ACTION] Needs response:\n`;
actions.forEach(e => {
report += ` - [${e.from}] ${e.subject}\n`;
e.actionItems.forEach(item => {
report += ` -> ${item}\n`;
});
});
}
return report;
}
// Usage
const classified = await batchClassify(inboxEmails);
console.log(generateReport(classified));
// Expected output:
// Email Classification Report
// ============================
// Total: 50 emails
//
// [RED] urgent: 3
// [YELLOW] action: 12
// [BLUE] info: 20
// [NEWS] newsletter: 10
// [SPAM] spam: 5
//
// [URGENT] Requires immediate attention:
// - [client@example.com] Change in delivery date
// Client requesting the delivery date be moved up by 3 days. Response needed by Friday.Looking back
By leveraging Gemini API's Function Calling, you can efficiently build a system that automatically classifies, summarizes, and extracts action items from incoming emails. Strict schema definitions ensure type-safe data flow, while the custom rules layer lets you flexibly adapt the system to your business needs.
For Gemini API basics, see our "[Gemini API Quickstart]((/articles/gemini-api/gemini-api-quickstart)". For Function Calling details, check out "[Complete Guide to Function Calling]((/articles/gemini-advanced/function-calling-guide)". If you're interested in streaming responses, "[Implementing Streaming Responses and Multi-Turn Chat]((/articles/gemini-api/streaming-and-chat)" is also a great resource.