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 expressbash
bun add @myxara/sdk-js openai expressComplete 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=3000Running 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 3000Use the ngrok URL for your webhook:
bash
WEBHOOK_URL=https://abc123.ngrok.io/webhookTesting
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
- Webhooks Example → - Deep dive into webhook handling
- SDK Reference → - Explore all SDK features
- Error Handling → - Production-ready error handling