
Integrating AI Agents with Your Existing Business Systems
Integrating AI Agents with Your Existing Business Systems
An AI agent that can't access your business systems is just an expensive chatbot.
The real power comes when your agent can:
- Look up customer data in your CRM
- Create calendar events
- Send emails automatically
- Update order status in your database
- Trigger workflows in your business tools
This guide shows you how to build these integrations properly—secure, reliable, and maintainable.
The Integration Architecture
Here's what a properly integrated agent looks like:
// Agent orchestrates actions across multiple systems
async function handleCustomerInquiry(message: string, customerId: string) {
// 1. Get customer context from CRM
const customer = await crm.getCustomer(customerId)
// 2. Check order status in database
const orders = await database.getRecentOrders(customerId)
// 3. Generate response with LLM
const response = await agent.generate({
message,
context: { customer, orders }
})
// 4. Take actions based on intent
if (response.intent === 'schedule_callback') {
await calendar.createEvent({
title: `Callback: ${customer.name}`,
attendees: [customer.email],
duration: 30
})
await email.send({
to: customer.email,
template: 'callback_scheduled',
data: { time: response.scheduledTime }
})
}
// 5. Log interaction in CRM
await crm.logInteraction({
customerId,
type: 'ai_agent',
summary: message,
response: response.text,
timestamp: new Date()
})
return response
}
Key components:
- CRM for customer data
- Database for business logic
- Calendar for scheduling
- Email for communication
- Logging for audit trail
Common Integration Patterns
Pattern 1: API Wrappers
Create clean interfaces for each system:
// Generic API client base class
class APIClient {
constructor(
private baseUrl: string,
private apiKey: string,
private rateLimit?: { requests: number; per: number }
) {}
protected async request<T>(
endpoint: string,
method: 'GET' | 'POST' | 'PUT' | 'DELETE',
data?: any
): Promise<T> {
// Rate limiting
await this.checkRateLimit()
// Make request with retry logic
const response = await this.fetchWithRetry(
`${this.baseUrl}${endpoint}`,
{
method,
headers: {
'Authorization': `Bearer ${this.apiKey}`,
'Content-Type': 'application/json'
},
body: data ? JSON.stringify(data) : undefined
}
)
if (!response.ok) {
throw new APIError(response.status, await response.text())
}
return response.json()
}
private async fetchWithRetry(
url: string,
options: RequestInit,
retries = 3
): Promise<Response> {
for (let i = 0; i < retries; i++) {
try {
return await fetch(url, options)
} catch (error) {
if (i === retries - 1) throw error
await this.sleep(Math.pow(2, i) * 1000) // Exponential backoff
}
}
throw new Error('Max retries exceeded')
}
private sleep(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms))
}
}
Pattern 2: CRM Integration
Example with HubSpot:
class HubSpotClient extends APIClient {
constructor(apiKey: string) {
super('https://api.hubapi.com', apiKey, {
requests: 100,
per: 10000 // 100 requests per 10 seconds
})
}
async getContact(email: string) {
return this.request<HubSpotContact>(
`/crm/v3/objects/contacts/${email}?idProperty=email`,
'GET'
)
}
async createContact(data: Partial<HubSpotContact>) {
return this.request<HubSpotContact>(
'/crm/v3/objects/contacts',
'POST',
{ properties: data }
)
}
async updateContact(contactId: string, data: Partial<HubSpotContact>) {
return this.request<HubSpotContact>(
`/crm/v3/objects/contacts/${contactId}`,
'PUT',
{ properties: data }
)
}
async logInteraction(contactId: string, note: string) {
return this.request('/crm/v3/objects/notes', 'POST', {
properties: {
hs_note_body: note,
hs_timestamp: new Date().toISOString()
},
associations: [
{
to: { id: contactId },
types: [{ associationCategory: 'HUBSPOT_DEFINED', associationTypeId: 202 }]
}
]
})
}
async getRecentDeals(contactId: string) {
return this.request<{ results: HubSpotDeal[] }>(
`/crm/v3/objects/contacts/${contactId}/associations/deals`,
'GET'
)
}
}
// Usage in agent
const hubspot = new HubSpotClient(process.env.HUBSPOT_API_KEY!)
const contact = await hubspot.getContact('customer@example.com')
if (!contact) {
// Create new contact
await hubspot.createContact({
email: 'customer@example.com',
firstname: 'John',
lastname: 'Doe',
lifecyclestage: 'lead'
})
}
// Log AI interaction
await hubspot.logInteraction(
contact.id,
`AI Agent: Answered question about pricing. Sentiment: Positive.`
)
Pattern 3: Email Integration
Using SendGrid:
class EmailService {
private client: SendGridClient
constructor(apiKey: string) {
this.client = new SendGridClient(apiKey)
}
async send({
to,
subject,
template,
data,
from = process.env.DEFAULT_FROM_EMAIL
}: {
to: string
subject?: string
template?: string
data?: Record<string, any>
from?: string
}) {
// Use template if provided, otherwise dynamic content
const message = template
? await this.renderTemplate(template, data)
: this.buildMessage(subject!, data)
const response = await this.client.send({
to,
from: from!,
...message
})
// Log email sent
await this.logEmail({
to,
subject: message.subject,
template,
sentAt: new Date(),
status: response.statusCode
})
return response
}
private async renderTemplate(template: string, data?: Record<string, any>) {
// Load template from SendGrid
const templateId = this.getTemplateId(template)
return {
templateId,
dynamicTemplateData: data
}
}
private buildMessage(subject: string, data?: Record<string, any>) {
return {
subject,
html: this.generateHTML(data),
text: this.generatePlainText(data)
}
}
private async logEmail(log: any) {
await db.emailLogs.create({ data: log })
}
}
// Usage in agent
const emailService = new EmailService(process.env.SENDGRID_API_KEY!)
// Send templated email
await emailService.send({
to: customer.email,
template: 'order_confirmation',
data: {
orderNumber: order.id,
total: order.total,
items: order.items,
estimatedDelivery: order.estimatedDelivery
}
})
Pattern 4: Calendar Integration
Using Google Calendar:
import { google } from 'googleapis'
class CalendarService {
private calendar
constructor() {
const auth = new google.auth.GoogleAuth({
credentials: JSON.parse(process.env.GOOGLE_CREDENTIALS!),
scopes: ['https://www.googleapis.com/auth/calendar']
})
this.calendar = google.calendar({ version: 'v3', auth })
}
async createEvent({
summary,
description,
startTime,
endTime,
attendees
}: {
summary: string
description?: string
startTime: Date
endTime: Date
attendees: string[]
}) {
const event = await this.calendar.events.insert({
calendarId: 'primary',
requestBody: {
summary,
description,
start: { dateTime: startTime.toISOString() },
end: { dateTime: endTime.toISOString() },
attendees: attendees.map(email => ({ email })),
reminders: {
useDefault: false,
overrides: [
{ method: 'email', minutes: 24 * 60 },
{ method: 'popup', minutes: 30 }
]
}
},
sendUpdates: 'all'
})
return event.data
}
async findAvailableSlots(
date: Date,
duration: number,
attendees: string[]
): Promise<Date[]> {
// Check free/busy for all attendees
const freeBusy = await this.calendar.freebusy.query({
requestBody: {
timeMin: date.toISOString(),
timeMax: endOfDay(date).toISOString(),
items: attendees.map(email => ({ id: email }))
}
})
// Find slots where everyone is free
const availableSlots = this.calculateAvailableSlots(
freeBusy.data.calendars!,
duration
)
return availableSlots
}
private calculateAvailableSlots(
calendars: any,
duration: number
): Date[] {
// Implementation to find overlapping free time
// Returns array of start times where everyone is available
// ...
return []
}
}
// Usage in agent
const calendar = new CalendarService()
// Agent detects intent to schedule meeting
if (intent === 'schedule_meeting') {
// Find available times
const slots = await calendar.findAvailableSlots(
new Date(),
30, // 30 minute meeting
[customer.email, salesRep.email]
)
// Present options to customer
const response = `I found these available times:\n${slots.map((s, i) =>
`${i + 1}. ${format(s, 'MMM d, h:mm a')}`
).join('\n')}\n\nWhich works best for you?`
// When customer chooses, create event
await calendar.createEvent({
summary: 'Sales Consultation',
description: 'Discussing pricing and implementation',
startTime: slots[chosenIndex],
endTime: addMinutes(slots[chosenIndex], 30),
attendees: [customer.email, salesRep.email]
})
}
Pattern 5: Database Integration
Prisma example:
// schema.prisma
model Customer {
id String @id @default(uuid())
email String @unique
name String
tier String // 'free', 'pro', 'enterprise'
createdAt DateTime @default(now())
orders Order[]
interactions Interaction[]
}
model Order {
id String @id @default(uuid())
customerId String
customer Customer @relation(fields: [customerId], references: [id])
status String // 'pending', 'shipped', 'delivered'
total Float
items Json
createdAt DateTime @default(now())
}
model Interaction {
id String @id @default(uuid())
customerId String
customer Customer @relation(fields: [customerId], references: [id])
type String // 'ai_agent', 'human', 'email'
message String
response String?
sentiment String? // 'positive', 'neutral', 'negative'
createdAt DateTime @default(now())
}
// Database service
class DatabaseService {
private prisma = new PrismaClient()
async getCustomerContext(email: string) {
const customer = await this.prisma.customer.findUnique({
where: { email },
include: {
orders: {
orderBy: { createdAt: 'desc' },
take: 5 // Last 5 orders
},
interactions: {
orderBy: { createdAt: 'desc' },
take: 10 // Last 10 interactions
}
}
})
if (!customer) return null
return {
customer: {
id: customer.id,
name: customer.name,
email: customer.email,
tier: customer.tier,
lifetimeValue: customer.orders.reduce((sum, o) => sum + o.total, 0),
orderCount: customer.orders.length
},
recentOrders: customer.orders,
recentInteractions: customer.interactions
}
}
async logInteraction({
customerId,
type,
message,
response,
sentiment
}: {
customerId: string
type: string
message: string
response?: string
sentiment?: string
}) {
return this.prisma.interaction.create({
data: {
customerId,
type,
message,
response,
sentiment
}
})
}
async updateOrderStatus(orderId: string, status: string) {
return this.prisma.order.update({
where: { id: orderId },
data: { status }
})
}
}
// Usage in agent
const db = new DatabaseService()
const context = await db.getCustomerContext('customer@example.com')
// Generate personalized response based on customer history
const response = await agent.generate({
message: userMessage,
context: {
tier: context.customer.tier,
lifetimeValue: context.customer.lifetimeValue,
hasRecentIssues: context.recentInteractions.some(i => i.sentiment === 'negative')
}
})
// Log this interaction
await db.logInteraction({
customerId: context.customer.id,
type: 'ai_agent',
message: userMessage,
response: response.text,
sentiment: response.sentiment
})
Security Best Practices
1. API Key Management
Never hardcode API keys:
// ❌ BAD
const client = new HubSpotClient('pk_live_abc123...')
// ✅ GOOD - use environment variables
const client = new HubSpotClient(process.env.HUBSPOT_API_KEY!)
// ✅ BETTER - use secrets manager
const client = new HubSpotClient(
await secretsManager.getSecret('hubspot-api-key')
)
2. Least Privilege Access
Only grant permissions your agent needs:
// API key scopes for different functions
const integrations = {
crm: {
apiKey: process.env.CRM_READONLY_KEY, // Read-only for data lookup
scopes: ['crm.objects.contacts.read', 'crm.objects.deals.read']
},
email: {
apiKey: process.env.EMAIL_SEND_KEY, // Send-only
scopes: ['mail.send']
},
calendar: {
credentials: process.env.CALENDAR_LIMITED_CREDS,
scopes: ['calendar.events.readonly', 'calendar.freebusy.readonly']
}
}
3. Input Validation
Sanitize all agent outputs before using in API calls:
function validateEmail(email: string): boolean {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
return emailRegex.test(email)
}
function sanitizeInput(input: string): string {
// Remove potentially dangerous characters
return input
.replace(/[<>]/g, '') // Remove HTML tags
.replace(/[;'"]/g, '') // Remove SQL injection attempts
.trim()
}
// Before creating contact
const sanitizedData = {
email: validateEmail(data.email) ? data.email : null,
name: sanitizeInput(data.name),
company: sanitizeInput(data.company)
}
if (!sanitizedData.email) {
throw new Error('Invalid email address')
}
await crm.createContact(sanitizedData)
4. Rate Limiting
Respect API limits and prevent abuse:
class RateLimiter {
private requests: Map<string, number[]> = new Map()
async checkLimit(
key: string,
maxRequests: number,
windowMs: number
): Promise<void> {
const now = Date.now()
const windowStart = now - windowMs
// Get recent requests for this key
const recent = (this.requests.get(key) || [])
.filter(time => time > windowStart)
if (recent.length >= maxRequests) {
const oldestRequest = Math.min(...recent)
const waitTime = windowStart - oldestRequest + windowMs
throw new RateLimitError(
`Rate limit exceeded. Try again in ${Math.ceil(waitTime / 1000)}s`
)
}
// Add this request
recent.push(now)
this.requests.set(key, recent)
}
}
const limiter = new RateLimiter()
async function callCRM(action: string) {
// 100 requests per minute
await limiter.checkLimit('crm', 100, 60000)
return crm[action]()
}
Error Handling & Resilience
1. Graceful Degradation
Don't let one failed integration break everything:
async function getCustomerContext(email: string) {
const context: any = {}
// Try to get CRM data (non-critical)
try {
context.crmData = await crm.getContact(email)
} catch (error) {
console.error('CRM lookup failed:', error)
context.crmData = null // Continue without CRM data
}
// Try to get order history (critical)
try {
context.orders = await database.getOrders(email)
} catch (error) {
console.error('Database lookup failed:', error)
throw new Error('Cannot proceed without order data')
}
return context
}
2. Retry with Backoff
Transient failures should retry:
async function withRetry<T>(
fn: () => Promise<T>,
options = { maxRetries: 3, backoff: 1000 }
): Promise<T> {
for (let i = 0; i < options.maxRetries; i++) {
try {
return await fn()
} catch (error) {
if (i === options.maxRetries - 1) throw error
// Exponential backoff
const delay = options.backoff * Math.pow(2, i)
console.log(`Retry ${i + 1}/${options.maxRetries} after ${delay}ms`)
await new Promise(resolve => setTimeout(resolve, delay))
}
}
throw new Error('Unreachable')
}
// Usage
const customer = await withRetry(
() => crm.getContact(email),
{ maxRetries: 3, backoff: 1000 }
)
3. Circuit Breaker
Prevent cascading failures:
class CircuitBreaker {
private failures = 0
private lastFailureTime?: number
private state: 'closed' | 'open' | 'half-open' = 'closed'
constructor(
private threshold: number = 5, // Open after 5 failures
private timeout: number = 60000 // Try again after 60s
) {}
async execute<T>(fn: () => Promise<T>): Promise<T> {
if (this.state === 'open') {
// Check if timeout has passed
if (Date.now() - this.lastFailureTime! > this.timeout) {
this.state = 'half-open'
} else {
throw new Error('Circuit breaker is OPEN')
}
}
try {
const result = await fn()
// Success - reset if we were half-open
if (this.state === 'half-open') {
this.state = 'closed'
this.failures = 0
}
return result
} catch (error) {
this.failures++
this.lastFailureTime = Date.now()
if (this.failures >= this.threshold) {
this.state = 'open'
console.error('Circuit breaker opened due to repeated failures')
}
throw error
}
}
}
const crmCircuit = new CircuitBreaker(5, 60000)
// If CRM fails 5 times, stop trying for 60 seconds
const customer = await crmCircuit.execute(() => crm.getContact(email))
Testing Integrations
1. Mock External Services
// __mocks__/crm.ts
export class MockCRMClient {
async getContact(email: string) {
return {
id: 'mock-id',
email,
name: 'Test User',
tier: 'pro'
}
}
async createContact(data: any) {
return { id: 'new-mock-id', ...data }
}
}
// test.ts
jest.mock('./integrations/crm', () => ({
CRMClient: MockCRMClient
}))
describe('Agent with CRM', () => {
it('should lookup customer data', async () => {
const agent = new Agent()
const response = await agent.handleMessage('customer@example.com', 'Hello')
// Verify CRM was called
expect(response.context.customerTier).toBe('pro')
})
})
2. Integration Tests with Real APIs
// Use test accounts for real API testing
describe('CRM Integration (real)', () => {
const crm = new HubSpotClient(process.env.HUBSPOT_TEST_API_KEY!)
afterEach(async () => {
// Clean up test data
await crm.deleteTestContacts()
})
it('should create and retrieve contact', async () => {
const created = await crm.createContact({
email: 'test@example.com',
firstname: 'Test',
lastname: 'User'
})
const retrieved = await crm.getContact('test@example.com')
expect(retrieved.id).toBe(created.id)
expect(retrieved.properties.firstname).toBe('Test')
})
})
Monitoring & Logging
Track integration health:
// Log every API call
async function logAPICall(
service: string,
endpoint: string,
duration: number,
success: boolean,
error?: string
) {
await analytics.track('api_call', {
service,
endpoint,
duration,
success,
error,
timestamp: new Date()
})
// Alert on failures
if (!success) {
await monitoring.alert({
type: 'integration_failure',
service,
endpoint,
error
})
}
}
// Wrap all API calls
async function callAPI<T>(
service: string,
endpoint: string,
fn: () => Promise<T>
): Promise<T> {
const start = Date.now()
try {
const result = await fn()
await logAPICall(service, endpoint, Date.now() - start, true)
return result
} catch (error) {
await logAPICall(service, endpoint, Date.now() - start, false, error.message)
throw error
}
}
The Bottom Line
Integrating AI agents with business systems transforms them from chatbots to actual business automation.
Key Principles:
- Abstract APIs with clean interfaces
- Secure credentials with secrets management
- Handle failures gracefully with retries and circuit breakers
- Validate inputs before external calls
- Monitor everything for debugging and optimization
- Test thoroughly with mocks and real APIs
Common Integrations:
- CRM (HubSpot, Salesforce): Customer context
- Email (SendGrid, Mailgun): Communication
- Calendar (Google, Outlook): Scheduling
- Database (Prisma, PostgreSQL): Business logic
- Payment (Stripe): Transactions
Investment: 1-3 weeks per major integration
Returns: Agents that actually DO things, not just talk
Next Steps
- Audit your tools: What systems does your agent need to access?
- Prioritize integrations: Which are critical vs nice-to-have?
- Set up test environments: Sandbox accounts for development
- Build one at a time: Start with most important, add more iteratively
- Monitor in production: Track failures, optimize performance
Need help integrating your agent? Schedule a consultation to discuss your specific tech stack, or check out our case studies for integration examples.
Remember: Integrations are where agents go from interesting demos to actual business value. Invest the time to do them right.
About the Author
DomAIn Labs Team
The DomAIn Labs team consists of AI engineers, strategists, and educators passionate about demystifying AI for small businesses.