Towering mountain peaks piercing through clouds
Agent Guides

Integrating AI Agents with Your Existing Business Systems

DomAIn Labs Team
January 23, 2025
13 min read

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

  1. Audit your tools: What systems does your agent need to access?
  2. Prioritize integrations: Which are critical vs nice-to-have?
  3. Set up test environments: Sandbox accounts for development
  4. Build one at a time: Start with most important, add more iteratively
  5. 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.

Tags:integrationsAPIsCRMautomationarchitecture

About the Author

DomAIn Labs Team

The DomAIn Labs team consists of AI engineers, strategists, and educators passionate about demystifying AI for small businesses.