Most AI email tools are just fancy wrappers. In this tutorial, you will build one from scratch — an assistant that reads your emails, drafts replies in your voice, summarizes threads, and sends through a real email API.

By the end, you will have a working Node.js application that integrates OpenAI for language tasks and Resend for email delivery.

What You Will Build

A CLI-based email assistant that can:

  • Summarize long email threads into key points
  • Draft context-aware replies matching your tone
  • Auto-classify incoming emails by priority
  • Send emails programmatically via Resend API

Prerequisites

  • Node.js 18 or higher
  • An OpenAI API key
  • A Resend account (free tier works)
  • A verified sending domain in Resend

Step 1: Project Setup

mkdir ai-email-assistant && cd ai-email-assistant
npm init -y
npm install openai resend dotenv zod

Create a .env file:

OPENAI_API_KEY=your-openai-key
RESEND_API_KEY=your-resend-key
FROM_EMAIL=you@yourdomain.com

Step 2: Email Summarizer

The summarizer condenses a long thread into actionable bullet points:

import OpenAI from 'openai'
 
const client = new OpenAI()
 
export async function summarizeEmail(emailContent: string): Promise<string> {
  const response = await client.chat.completions.create({
    model: 'gpt-4o',
    messages: [
      {
        role: 'system',
        content: `You are an expert email summarizer. Extract:
        1. Main topic in one sentence
        2. Key decisions or requests (bullet points)
        3. Required actions and deadlines
        4. Important people mentioned
        Be concise and focus on actionable information.`
      },
      {
        role: 'user',
        content: `Summarize this email thread:\n\n${emailContent}`
      }
    ],
    max_tokens: 500
  })
 
  return response.choices[0].message.content ?? ''
}

Step 3: Reply Drafter

The reply drafter generates context-aware responses matching a specified tone:

interface DraftReplyOptions {
  emailThread: string
  tone: 'professional' | 'friendly' | 'concise'
  keyPoints: string[]
  senderName: string
}
 
export async function draftReply(options: DraftReplyOptions): Promise<string> {
  const { emailThread, tone, keyPoints, senderName } = options
 
  const toneGuide = {
    professional: 'formal, respectful, business-appropriate',
    friendly: 'warm, personable, conversational but still professional',
    concise: 'brief, direct, no fluff — get to the point immediately'
  }
 
  const response = await client.chat.completions.create({
    model: 'gpt-4o',
    messages: [
      {
        role: 'system',
        content: `You draft email replies. Tone: ${toneGuide[tone]}. 
        Always address the sender by name. 
        Structure: greeting → main content → clear next steps → closing.
        Do not use placeholder text.`
      },
      {
        role: 'user',
        content: `Draft a reply to this email thread from ${senderName}.
        
Key points to address:
${keyPoints.map(p => `- ${p}`).join('\n')}
 
Original thread:
${emailThread}`
      }
    ],
    max_tokens: 800
  })
 
  return response.choices[0].message.content ?? ''
}

Step 4: Priority Classifier

Automatically classify emails by urgency to prioritize your inbox:

import { z } from 'zod'
 
const ClassificationSchema = z.object({
  priority: z.enum(['urgent', 'normal', 'low']),
  category: z.enum(['action-required', 'fyi', 'newsletter', 'spam', 'meeting']),
  reasoning: z.string()
})
 
export async function classifyEmail(emailContent: string) {
  const response = await client.chat.completions.create({
    model: 'gpt-4o',
    messages: [
      {
        role: 'system',
        content: 'Classify emails by priority and category. Return JSON only.'
      },
      {
        role: 'user',
        content: `Classify this email and return JSON matching this schema:
        { priority: "urgent"|"normal"|"low", category: "action-required"|"fyi"|"newsletter"|"spam"|"meeting", reasoning: string }
        
        Email: ${emailContent}`
      }
    ],
    response_format: { type: 'json_object' }
  })
 
  const parsed = JSON.parse(response.choices[0].message.content ?? '{}')
  return ClassificationSchema.parse(parsed)
}

Step 5: Email Sender with Resend

Wire up the Resend API to send emails programmatically:

import { Resend } from 'resend'
 
const resend = new Resend(process.env.RESEND_API_KEY)
 
interface SendEmailOptions {
  to: string
  subject: string
  body: string
  replyTo?: string
}
 
export async function sendEmail(options: SendEmailOptions) {
  const { to, subject, body, replyTo } = options
 
  const { data, error } = await resend.emails.send({
    from: process.env.FROM_EMAIL!,
    to,
    subject,
    text: body,
    replyTo
  })
 
  if (error) throw new Error(`Failed to send email: ${error.message}`)
  return data
}

Step 6: Build the CLI Interface

Tie everything together into a usable CLI:

import * as readline from 'readline'
 
const rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout
})
 
const question = (prompt: string): Promise<string> =>
  new Promise(resolve => rl.question(prompt, resolve))
 
async function main() {
  console.log('AI Email Assistant\n')
 
  const action = await question('Choose action (summarize/draft/classify/send): ')
  const emailContent = await question('Paste email content:\n')
 
  switch (action) {
    case 'summarize': {
      const summary = await summarizeEmail(emailContent)
      console.log('\nSummary:\n', summary)
      break
    }
    case 'draft': {
      const tone = await question('Tone (professional/friendly/concise): ') as any
      const points = await question('Key points to address (comma-separated): ')
      const sender = await question('Sender name: ')
      const draft = await draftReply({
        emailThread: emailContent,
        tone,
        keyPoints: points.split(',').map(p => p.trim()),
        senderName: sender
      })
      console.log('\nDraft:\n', draft)
      break
    }
    case 'classify': {
      const classification = await classifyEmail(emailContent)
      console.log('\nClassification:', JSON.stringify(classification, null, 2))
      break
    }
  }
 
  rl.close()
}
 
main().catch(console.error)

Taking It Further

Connect to a real inbox. Use the Gmail API or IMAP to read emails automatically instead of pasting them manually.

Add a web UI. Wrap the functions in a Next.js API route and build a simple dashboard for managing drafts.

Fine-tune the tone. Feed examples of your past emails to the system prompt so the AI matches your writing style more closely.

Rate limit carefully. Each email processed costs API calls. For high volume, batch emails and consider caching classifications.