Skip to content

AI Customer Support Agent

Build an intelligent customer support agent that automatically responds to emails using AI.

What We're Building

A production-ready AI support agent that:

  • Receives customer emails via webhooks
  • Generates contextual responses using GPT-4
  • Maintains conversation threads
  • Escalates complex issues to humans
  • Tracks support metrics

Prerequisites

Before you begin, you'll need:

  • Myxara API key
  • OpenAI API key
  • A server to receive webhooks (or ngrok for local testing)
  • Node.js 18+ or Bun installed

Quick Start

Install dependencies:

bash
npm install @myxara/sdk-js openai express
bash
bun add @myxara/sdk-js openai express

Complete Implementation

typescript
import express from 'express'
import crypto from 'crypto'
import { MyxaraClient } from '@myxara/sdk-js'
import OpenAI from 'openai'

// Initialize clients
const myxara = new MyxaraClient({
  apiKey: process.env.MYXARA_API_KEY!
})

const openai = new OpenAI({
  apiKey: process.env.OPENAI_API_KEY!
})

const app = express()
app.use(express.json())

// Configuration
const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET!
const ESCALATION_EMAIL = process.env.ESCALATION_EMAIL || 'human-support@company.com'

// System prompt for the AI agent
const SYSTEM_PROMPT = `You are a helpful customer support agent for a SaaS product.

Guidelines:
- Be concise and friendly
- Provide clear, actionable solutions
- If you don't know something, admit it and say you'll escalate to a human
- Always maintain a professional tone
- Keep responses under 200 words

If the issue requires human intervention (billing, account deletion, etc.), say:
"I'll escalate this to our support team. They'll reach out within 24 hours."
`

// Verify webhook signature
function verifySignature(signature: string, payload: any): boolean {
  const expected = crypto
    .createHmac('sha256', WEBHOOK_SECRET)
    .update(JSON.stringify(payload))
    .digest('hex')

  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected)
  )
}

// Check if issue needs human escalation
function needsEscalation(message: string): boolean {
  const escalationKeywords = [
    'billing',
    'refund',
    'cancel',
    'delete account',
    'talk to human',
    'speak to person',
    'urgent',
    'legal'
  ]

  const lowerMessage = message.toLowerCase()
  return escalationKeywords.some(keyword => lowerMessage.includes(keyword))
}

// Generate AI response
async function generateResponse(customerMessage: string): Promise<string> {
  const completion = await openai.chat.completions.create({
    model: 'gpt-4',
    messages: [
      { role: 'system', content: SYSTEM_PROMPT },
      { role: 'user', content: customerMessage }
    ],
    max_tokens: 300,
    temperature: 0.7
  })

  return completion.choices[0].message.content || 'I apologize, but I encountered an error. Please try again.'
}

// Handle incoming messages
async function handleIncomingMessage(message: any) {
  console.log(`New message from ${message.from}`)
  console.log(`Subject: ${message.subject}`)

  // Check if needs escalation
  const shouldEscalate = needsEscalation(message.text)

  if (shouldEscalate) {
    // Escalate to human
    console.log('Escalating to human support')

    await myxara.inboxes.messages(message.inbox_id).send({
      to: message.from,
      subject: `Re: ${message.subject}`,
      text: `Thank you for contacting us. I've escalated your request to our support team, and they'll reach out within 24 hours.

Best regards,
AI Support Agent`,
      html: `
        <p>Thank you for contacting us.</p>
        <p>I've escalated your request to our support team, and they'll reach out within 24 hours.</p>
        <p>Best regards,<br>AI Support Agent</p>
      `,
      in_reply_to: message.id,
      metadata: {
        escalated: true,
        escalation_reason: 'keyword_match'
      }
    })

    // Notify human team
    await myxara.inboxes.messages(message.inbox_id).send({
      to: ESCALATION_EMAIL,
      subject: `[ESCALATED] ${message.subject}`,
      text: `Customer: ${message.from}\n\nOriginal message:\n${message.text}`,
      html: `
        <h2>Escalated Support Request</h2>
        <p><strong>Customer:</strong> ${message.from}</p>
        <p><strong>Subject:</strong> ${message.subject}</p>
        <h3>Original Message:</h3>
        <p>${message.html || message.text}</p>
      `,
      metadata: {
        escalation: true,
        customer_email: message.from
      }
    })

    return
  }

  // Generate AI response
  const aiResponse = await generateResponse(message.text)

  console.log(`AI Response: ${aiResponse}`)

  // Send AI-generated reply
  await myxara.inboxes.messages(message.inbox_id).send({
    to: message.from,
    subject: `Re: ${message.subject}`,
    text: aiResponse,
    html: `
      <p>${aiResponse.replace(/\n/g, '<br>')}</p>
      <br>
      <p><small>This response was generated by our AI support agent. If you need further assistance, please reply to this email.</small></p>
    `,
    in_reply_to: message.id,
    metadata: {
      ai_generated: true,
      model: 'gpt-4'
    }
  })

  console.log('Response sent successfully')
}

// Webhook endpoint
app.post('/webhook', async (req, res) => {
  // Verify signature
  const signature = req.headers['myxara-signature'] as string

  if (!verifySignature(signature, req.body)) {
    console.error('Invalid webhook signature')
    return res.status(401).send('Invalid signature')
  }

  const event = req.body

  console.log(`Received event: ${event.type}`)

  // Handle message received event
  if (event.type === 'message.received') {
    const { message } = event.data

    // Process in background (respond to webhook immediately)
    handleIncomingMessage(message).catch(error => {
      console.error('Error handling message:', error)
    })
  }

  // Always respond quickly to webhook
  res.sendStatus(200)
})

// Health check endpoint
app.get('/health', (req, res) => {
  res.json({ status: 'ok', timestamp: new Date().toISOString() })
})

// Setup function - run once to create inbox and webhook
async function setup() {
  console.log('Setting up AI support agent...')

  // Create support inbox
  const inbox = await myxara.inboxes.create({
    local_part: 'support',
    name: 'AI Support Agent',
    description: 'Automated customer support powered by GPT-4',
    metadata: {
      type: 'customer_support',
      auto_reply: true,
      model: 'gpt-4'
    }
  })

  console.log(`✅ Inbox created: ${inbox.address}`)

  // Create webhook
  const webhook = await myxara.webhooks.create({
    url: process.env.WEBHOOK_URL!,  // e.g., https://your-server.com/webhook
    events: ['message.received'],
    inbox_id: inbox.id,
    description: 'Customer support webhook'
  })

  console.log(`✅ Webhook created: ${webhook.id}`)
  console.log(`Secret: ${webhook.secret}`)
  console.log('\n⚠️ Save this secret to your environment variables as WEBHOOK_SECRET')

  return { inbox, webhook }
}

// Start server
const PORT = process.env.PORT || 3000

app.listen(PORT, () => {
  console.log(`🚀 AI Support Agent running on port ${PORT}`)
  console.log(`Webhook endpoint: http://localhost:${PORT}/webhook`)
})

// Uncomment to run setup:
// setup().catch(console.error)

Environment Variables

Create a .env file:

bash
# Myxara API Key
MYXARA_API_KEY=mx_your_api_key

# OpenAI API Key
OPENAI_API_KEY=sk-your_openai_key

# Webhook Configuration
WEBHOOK_URL=https://your-server.com/webhook
WEBHOOK_SECRET=whsec_your_webhook_secret

# Escalation
ESCALATION_EMAIL=human-support@company.com

# Server
PORT=3000

Running Locally with ngrok

For local development, use ngrok to expose your webhook endpoint:

bash
# Start your server
bun run server.ts

# In another terminal, start ngrok
ngrok http 3000

Use the ngrok URL for your webhook:

bash
WEBHOOK_URL=https://abc123.ngrok.io/webhook

Testing

Send a test email to your support inbox:

typescript
import { MyxaraClient } from '@myxara/sdk-js'

const client = new MyxaraClient({
  apiKey: process.env.MYXARA_API_KEY!
})

// Send test email
await client.inboxes.messages('inbox_your_support_inbox').send({
  to: 'support@mail.myxara.ai',  // Your support inbox
  subject: 'Test: How do I reset my password?',
  text: 'I forgot my password and need help resetting it.'
})

console.log('Test email sent!')

Advanced Features

Conversation Context

Maintain context across multiple emails:

typescript
// Store conversation history
const conversations = new Map<string, any[]>()

async function generateResponseWithContext(
  customerEmail: string,
  message: string
): Promise<string> {
  // Get conversation history
  const history = conversations.get(customerEmail) || []

  // Build messages array
  const messages = [
    { role: 'system', content: SYSTEM_PROMPT },
    ...history,
    { role: 'user', content: message }
  ]

  const completion = await openai.chat.completions.create({
    model: 'gpt-4',
    messages,
    max_tokens: 300
  })

  const response = completion.choices[0].message.content!

  // Update history
  history.push(
    { role: 'user', content: message },
    { role: 'assistant', content: response }
  )
  conversations.set(customerEmail, history)

  return response
}

Sentiment Analysis

Detect negative sentiment and prioritize:

typescript
async function analyzeSentiment(text: string): Promise<'positive' | 'neutral' | 'negative'> {
  const completion = await openai.chat.completions.create({
    model: 'gpt-4',
    messages: [
      {
        role: 'system',
        content: 'Analyze the sentiment of this customer message. Respond with only: positive, neutral, or negative'
      },
      { role: 'user', content: text }
    ]
  })

  const sentiment = completion.choices[0].message.content?.toLowerCase().trim()
  return (sentiment === 'positive' || sentiment === 'neutral' || sentiment === 'negative')
    ? sentiment
    : 'neutral'
}

// Use in message handler
const sentiment = await analyzeSentiment(message.text)

if (sentiment === 'negative') {
  // Priority escalation
  console.log('⚠️ Negative sentiment detected - priority escalation')
}

Metrics Dashboard

Track support metrics:

typescript
interface SupportMetrics {
  total_messages: number
  ai_responses: number
  escalations: number
  avg_response_time: number
}

const metrics: SupportMetrics = {
  total_messages: 0,
  ai_responses: 0,
  escalations: 0,
  avg_response_time: 0
}

app.get('/metrics', (req, res) => {
  res.json({
    ...metrics,
    escalation_rate: (metrics.escalations / metrics.total_messages * 100).toFixed(2) + '%',
    ai_resolution_rate: (metrics.ai_responses / metrics.total_messages * 100).toFixed(2) + '%'
  })
})

Production Considerations

Rate Limiting

typescript
import rateLimit from 'express-rate-limit'

const limiter = rateLimit({
  windowMs: 15 * 60 * 1000,  // 15 minutes
  max: 100  // Limit each IP to 100 requests per window
})

app.use('/webhook', limiter)

Error Handling

typescript
async function handleIncomingMessage(message: any) {
  try {
    // ... your logic
  } catch (error) {
    console.error('Error processing message:', error)

    // Send fallback response
    await myxara.inboxes.messages(message.inbox_id).send({
      to: message.from,
      subject: `Re: ${message.subject}`,
      text: 'We received your message but encountered an issue. Our team will respond shortly.',
      metadata: { error: true }
    })

    // Alert team
    await myxara.inboxes.messages(message.inbox_id).send({
      to: ESCALATION_EMAIL,
      subject: '[ERROR] Support Agent Failed',
      text: `Failed to process message from ${message.from}\n\nError: ${error}`
    })
  }
}

Logging

typescript
import winston from 'winston'

const logger = winston.createLogger({
  level: 'info',
  format: winston.format.json(),
  transports: [
    new winston.transports.File({ filename: 'error.log', level: 'error' }),
    new winston.transports.File({ filename: 'combined.log' })
  ]
})

logger.info('Message processed', {
  from: message.from,
  subject: message.subject,
  escalated: shouldEscalate
})

Next Steps

Released under the MIT License (SDK) & Elastic License 2.0 (Server)