Published on

AI Email Automation — Drafting, Classifying, and Routing Emails at Scale

Authors

Introduction

Email remains the dominant communication channel in business, but managing hundreds of inbound messages daily is exhausting and error-prone. AI email automation accelerates response time, ensures consistent quality, and routes messages to the right teams instantly. Unlike generic email filters, LLMs understand context, intent, and nuance. This guide covers building systems that draft, classify, prioritize, and route emails intelligently while maintaining compliance.

Email Intent Classification

Classify emails into actionable categories:

type EmailIntent = 'support' | 'sales' | 'spam' | 'inquiry' | 'feedback' | 'billing' | 'technical';

interface ClassifiedEmail {
  id: string;
  from: string;
  subject: string;
  body: string;
  intent: EmailIntent;
  confidence: number;
  metadata: Record<string, any>;
}

async function classifyEmail(email: RawEmail): Promise<ClassifiedEmail> {
  const classificationPrompt = `
    Classify this email by intent:

    From: ${email.from}
    Subject: ${email.subject}
    Body: ${email.body}

    Intent categories:
    - support: User has a problem and needs help
    - sales: Potential customer inquiry or partnership
    - spam: Unsolicited marketing, phishing
    - inquiry: General question about product/service
    - feedback: User reporting feature request or bug
    - billing: Payment or subscription issue
    - technical: Infrastructure or integration issue

    Return JSON: { intent, confidence (0-1), reasoning }
  `;

  const result = JSON.parse(await llm.generate(classificationPrompt));
  return {
    id: email.id,
    from: email.from,
    subject: email.subject,
    body: email.body,
    intent: result.intent,
    confidence: result.confidence,
    metadata: { reasoning: result.reasoning }
  };
}

Priority Scoring

Determine which emails need immediate attention:

interface PriorityScore {
  score: number; // 0-100
  urgency: 'critical' | 'high' | 'normal' | 'low';
  reasons: string[];
}

async function scorePriority(email: ClassifiedEmail): Promise<PriorityScore> {
  const priorityPrompt = `
    Score the priority of this email (0-100 scale):

    From: ${email.from}
    Intent: ${email.intent}
    Subject: ${email.subject}
    Body: ${email.body}

    Urgency factors:
    - Repeated contact attempts (high priority)
    - Payment/billing issues (high priority)
    - User mentions time sensitivity
    - VIP/enterprise customer
    - Service outage reported
    - Contains explicit urgency language

    Return JSON: { score, urgency, reasons (array) }
  `;

  const result = JSON.parse(await llm.generate(priorityPrompt));
  return {
    score: result.score,
    urgency: result.urgency,
    reasons: result.reasons
  };
}

Auto-Draft Generation with Context

Generate contextual email drafts:

interface DraftEmail {
  to: string;
  subject: string;
  body: string;
  tone: 'formal' | 'casual' | 'friendly' | 'apologetic';
  isAutoGenerated: boolean;
}

async function generateDraft(
  email: ClassifiedEmail,
  context: { customer?: any; order?: any; account?: any }
): Promise<DraftEmail> {
  const draftPrompt = `
    Generate a professional email response to this incoming email:

    From: ${email.from}
    Original subject: ${email.subject}
    Original message: ${email.body}

    Intent: ${email.intent}
    Customer info: ${JSON.stringify(context.customer)}
    Order info: ${JSON.stringify(context.order)}

    Requirements:
    - Keep response under 200 words
    - Address customer by name if available
    - Acknowledge their specific concern
    - Provide next steps or timeline
    - Include relevant contact info

    Return JSON: { subject, body }
  `;

  const draft = JSON.parse(await llm.generate(draftPrompt));

  return {
    to: email.from,
    subject: draft.subject,
    body: draft.body,
    tone: detectTone(draft.body),
    isAutoGenerated: true
  };
}

Tone Adjustment

Allow operators to adjust response tone:

async function adjustTone(
  originalBody: string,
  targetTone: 'formal' | 'casual' | 'friendly' | 'apologetic'
): Promise<string> {
  const toneGuides = {
    formal: 'Professional language, minimal contractions, structured format',
    casual: 'Conversational, relaxed, use contractions naturally',
    friendly: 'Warm, empathetic, personal touches, exclamation points ok',
    apologetic: 'Acknowledge the problem, take responsibility, offer solution'
  };

  const tonePrompt = `
    Adjust the tone of this email to be more ${targetTone}:

    Original:
    ${originalBody}

    Tone guide: ${toneGuides[targetTone]}

    Maintain the same information and meaning.
    Return only the adjusted email body.
  `;

  return llm.generate(tonePrompt);
}

Template Population

Use templates for repetitive responses:

interface EmailTemplate {
  id: string;
  name: string;
  subject: string;
  body: string; // With {{placeholder}} syntax
  applicableIntents: EmailIntent[];
}

async function populateTemplate(
  template: EmailTemplate,
  email: ClassifiedEmail,
  context: Record<string, any>
): Promise<DraftEmail> {
  let subject = template.subject;
  let body = template.body;

  // Simple variable substitution
  const variables = {
    customer_name: context.customer?.name || 'Customer',
    ticket_id: context.ticketId,
    order_id: context.order?.id,
    product_name: context.product?.name,
    ...context
  };

  for (const [key, value] of Object.entries(variables)) {
    subject = subject.replace(`{{${key}}}`, String(value));
    body = body.replace(`{{${key}}}`, String(value));
  }

  return {
    to: email.from,
    subject,
    body,
    tone: 'formal',
    isAutoGenerated: true
  };
}

Response Suggestion Generation

Offer multiple response options:

interface ResponseOption {
  draft: DraftEmail;
  rationale: string;
  estimatedSendTime: number; // ms to compose
}

async function generateResponseOptions(
  email: ClassifiedEmail,
  context: any,
  count: number = 3
): Promise<ResponseOption[]> {
  const optionsPrompt = `
    Generate ${count} different email responses to this customer:

    Original email: ${email.body}
    Intent: ${email.intent}
    Customer: ${context.customer?.name}

    Vary the responses in:
    1. Tone (formal vs friendly)
    2. Detail level (comprehensive vs concise)
    3. Offers or solutions presented

    Return JSON array with { subject, body, rationale }
  `;

  const options = JSON.parse(await llm.generate(optionsPrompt));

  return options.map((opt: any) => ({
    draft: {
      to: email.from,
      subject: opt.subject,
      body: opt.body,
      tone: 'formal',
      isAutoGenerated: true
    },
    rationale: opt.rationale,
    estimatedSendTime: 0
  }));
}

Email Thread Summarization

Summarize long conversations:

interface ThreadSummary {
  keyIssues: string[];
  customerSentiment: 'positive' | 'neutral' | 'negative';
  previousAttempts: string[];
  recommendedNextStep: string;
}

async function summarizeThread(
  emails: ClassifiedEmail[]
): Promise<ThreadSummary> {
  const threadContext = emails
    .map(e => `[${e.from}] ${e.subject}\n${e.body}`)
    .join('\n\n---\n\n');

  const summaryPrompt = `
    Summarize this email thread in terms of key issues and sentiment:

    ${threadContext}

    Identify:
    1. Main issues discussed
    2. Customer sentiment progression
    3. What''s been tried before
    4. What should happen next

    Return JSON with these fields.
  `;

  return JSON.parse(await llm.generate(summaryPrompt));
}

Routing to Correct Team

Assign emails to appropriate departments:

interface RoutingDecision {
  team: string;
  queue: string;
  priority: number;
  assignee?: string;
  escalationReason?: string;
}

async function routeEmail(
  email: ClassifiedEmail,
  priority: PriorityScore,
  context: any
): Promise<RoutingDecision> {
  const routingPrompt = `
    Route this email to the correct team:

    Intent: ${email.intent}
    Priority score: ${priority.score}
    Customer type: ${context.customer?.tier}
    Previous tickets: ${context.previousTicketCount}

    Teams available:
    - support-tier1 (fast-track simple issues)
    - support-tier2 (complex technical issues)
    - sales (new opportunities)
    - billing (payments, subscriptions)
    - escalations (VIP, legal concerns)

    Return JSON: { team, queue, priority (1-5), escalationReason if needed }
  `;

  return JSON.parse(await llm.generate(routingPrompt));
}

Confidence-Based Human Review Queue

Route uncertain cases to humans:

async function triageEmail(
  email: ClassifiedEmail,
  classification: ClassifiedEmail,
  draft: DraftEmail,
  routing: RoutingDecision
): Promise<'auto_send' | 'human_review'> {
  const reviewThreshold = 0.88; // Require 88%+ confidence for auto-send

  const confidence = {
    classificationConfidence: classification.confidence,
    draftQuality: await assessDraftQuality(draft),
    routingConfidence: 0.95 // Static routing after extensive rules
  };

  const averageConfidence = Object.values(confidence)
    .reduce((a, b) => a + b) / Object.keys(confidence).length;

  if (averageConfidence &lt; reviewThreshold) {
    return 'human_review';
  }

  return 'auto_send';
}

async function assessDraftQuality(draft: DraftEmail): Promise<number> {
  const qualityChecks = {
    hasGreeting: draft.body.match(/^(Hi|Hello|Dear)/i) ? 1 : 0,
    hasClosing: draft.body.match(/(Best regards|Thanks|Sincerely)/i) ? 1 : 0,
    hasActionItem: draft.body.match(/(will|next|action|steps)/i) ? 1 : 0,
    noPlaceholders: draft.body.includes('{{') ? 0 : 1,
    lengthReasonable: draft.body.length &gt; 50 && draft.body.length &lt; 1000 ? 1 : 0.5
  };

  return Object.values(qualityChecks).reduce((a, b) => a + b) / Object.keys(qualityChecks).length;
}

Unsubscribe and Compliance Handling

Respect user preferences and regulations:

interface EmailCompliance {
  canSend: boolean;
  unsubscribeReason?: string;
  requiresConsent?: boolean;
}

async function checkCompliance(
  email: ClassifiedEmail
): Promise<EmailCompliance> {
  // Check unsubscribe lists, GDPR/CAN-SPAM compliance
  const isUnsubscribed = await unsubscribeDb.exists(email.from);
  const consentGiven = await consentDb.hasConsent(email.from, 'marketing');

  if (isUnsubscribed) {
    return {
      canSend: false,
      unsubscribeReason: 'User is on global unsubscribe list'
    };
  }

  if (email.intent === 'sales' && !consentGiven) {
    return {
      canSend: true,
      requiresConsent: true
    };
  }

  return { canSend: true };
}

Checklist

  • Classify emails into 6-8 intent categories with confidence scoring
  • Score priority 0-100 based on urgency signals and customer value
  • Generate contextual drafts using customer and order data
  • Allow tone adjustment (formal, casual, friendly, apologetic)
  • Build library of templates with placeholder variables
  • Generate 2-3 response options to let operators choose
  • Summarize email threads for context before responding
  • Route to appropriate teams based on intent and priority
  • Require human review if confidence < 88% on any component
  • Check unsubscribe lists and GDPR/CAN-SPAM compliance
  • Log all auto-generated emails and track accuracy
  • A/B test template variations and measure response rates

Conclusion

AI email automation transforms reactive customer service into proactive, responsive support. By combining intent classification, priority scoring, smart drafting, and intelligent routing, you can handle 3-5x more inbound emails without proportional headcount growth. Start with high-confidence auto-responses and simple routing, gradually expanding as your system proves reliable. Always maintain human-in-the-loop for sensitive issues, ambiguous cases, and regulatory compliance.