.do
ScaleServices-as-Software

Building Services

How to create and develop Services-as-Software

Learn how to build Services-as-Software on the .do platform, from initial setup to production deployment.

flowchart TD Start[Create Service] --> Define[Define Service Config] Define --> Events[Add Event Handlers] Events --> AI[Integrate AI] AI --> Test[Write Tests] Test --> Local[Test Locally] Local --> Pass{Tests Pass?} Pass -->|No| Fix[Fix Issues] Fix --> Test Pass -->|Yes| Deploy[Deploy to Staging] Deploy --> Verify[Verify Staging] Verify --> Prod[Deploy to Production] Prod --> Monitor[Monitor & Iterate]

Quick Start

Create a new service in minutes:

# Create service from template
$ do create service customer-support

# Navigate to service directory
$ cd customer-support

# Start development server
$ do dev

Your service structure:

customer-support/
├── service.ts          # Service definition
├── handlers/           # Event handlers
│   ├── ticket.ts
│   └── message.ts
├── lib/                # Shared utilities
│   ├── ai.ts
│   └── email.ts
├── tests/              # Test files
│   └── service.test.ts
├── package.json
└── do.config.ts        # Service configuration

Service Definition

Define your service in service.ts:

import { service } from 'sdk.do'

export default service({
  name: 'Customer Support Service',
  description: 'AI-powered customer support automation',
  version: '1.0.0',

  // Pricing configuration
  pricing: {
    model: 'usage',
    tiers: [
      { up_to: 1000, price_per_unit: 0.5 },
      { up_to: 10000, price_per_unit: 0.3 },
      { above: 10000, price_per_unit: 0.2 },
    ],
  },

  // Event handlers
  on: {
    '$.Ticket.created': './handlers/ticket',
    '$.Message.received': './handlers/message',
  },

  // API endpoints
  api: {
    'GET /health': () => ({ status: 'ok' }),
    'POST /analyze': './api/analyze',
  },

  // Configuration
  config: {
    aiModel: 'gpt-5',
    responseTime: 'under-2-minutes',
    escalationThreshold: 0.7,
  },
})

Event Handlers

Create event handlers in handlers/:

// handlers/ticket.ts
import { $ } from 'sdk.do'

export default async function handleTicketCreated(event) {
  const ticket = event.data

  // Categorize using AI
  const category = await $.ai.generate({
    model: 'gpt-5',
    schema: $.TicketCategory,
    prompt: `Categorize this support ticket`,
    context: { ticket },
  })

  // Generate suggested response
  const response = await $.ai.generate({
    model: 'claude-sonnet-4.5',
    prompt: `Generate a helpful response for this ticket`,
    context: {
      ticket,
      category,
      knowledge: await $.Knowledge.search(ticket.subject),
    },
  })

  // Update ticket
  await ticket.update({
    category: category.name,
    priority: category.priority,
    suggestedResponse: response,
    status: '$.AwaitingReview',
  })

  // Notify support team
  await $.send('$.Support.ticket-categorized', {
    ticket,
    category,
  })
}

AI Integration

flowchart LR Input[User Input] --> Context[Gather Context] Context --> Model{AI Model} Model -->|GPT-5| Gen1[Content Generation] Model -->|Claude| Gen2[Sentiment Analysis] Model -->|Llama-4| Gen3[Data Extraction] Gen1 & Gen2 & Gen3 --> Process[Process Results] Process --> Validate{Valid?} Validate -->|Yes| Output[Return Results] Validate -->|No| Retry[Retry/Fallback] Retry --> Model

Integrate AI capabilities into services:

Content Generation

// Generate personalized responses
const response = await $.ai.generate({
  model: 'gpt-5',
  prompt: 'Write a professional response to this customer inquiry',
  context: {
    inquiry: ticket.message,
    customerHistory: await $.Customer.history(ticket.customerId),
    tone: 'friendly and helpful',
  },
})

Sentiment Analysis

// Analyze customer sentiment
const sentiment = await $.ai.generate({
  model: 'gpt-5',
  schema: {
    sentiment: ['positive', 'neutral', 'negative', 'urgent'],
    confidence: 'number',
    concerns: 'string[]',
  },
  prompt: 'Analyze the sentiment and concerns in this message',
  context: { message: ticket.message },
})

if (sentiment.sentiment === 'urgent') {
  // Escalate immediately
  await $.send('$.Support.escalate', {
    ticket,
    reason: 'urgent-sentiment',
  })
}

Structured Data Extraction

// Extract structured data from unstructured text
const orderInfo = await $.ai.generate({
  model: 'gpt-5',
  schema: $.OrderInquiry,
  prompt: 'Extract order details from this message',
  context: { message: ticket.message },
})

console.log(orderInfo)
// {
//   orderId: 'ORD-12345',
//   issue: 'delayed shipment',
//   requestedAction: 'refund'
// }

Database Integration

Store and query data:

// Create records
const ticket = await $.Ticket.create({
  subject: 'Order not received',
  message: 'I ordered on 10/1 but haven't received my package',
  customerId: 'cust_123',
  status: '$.Open',
})

// Query records
const openTickets = await $.Ticket.findMany({
  where: {
    status: '$.Open',
    assignee: userId,
  },
  orderBy: { priority: 'desc' },
  limit: 10,
})

// Update records
await ticket.update({
  status: '$.Resolved',
  resolvedAt: new Date(),
  resolution: 'Refund processed',
})

// Delete records
await ticket.delete()

Workflows

Orchestrate multi-step processes:

// Define workflow
const workflow = $.workflow('ticket-resolution', async (ticket) => {
  // Step 1: Analyze
  const analysis = await $.ai.generate({
    model: 'gpt-5',
    schema: $.TicketAnalysis,
    prompt: 'Analyze this ticket',
    context: { ticket },
  })

  // Step 2: Route based on analysis
  if (analysis.needsHuman) {
    await $.send('$.Support.assign', {
      ticket,
      priority: analysis.priority,
    })
    return { routed: 'human' }
  }

  // Step 3: Generate AI response
  const response = await $.ai.generate({
    model: 'claude-sonnet-4.5',
    prompt: 'Generate solution',
    context: { ticket, analysis },
  })

  // Step 4: Send response
  await $.Email.send({
    to: ticket.customer.email,
    template: 'ticket-response',
    data: { ticket, response },
  })

  // Step 5: Update ticket
  await ticket.update({
    status: '$.Resolved',
    resolution: response,
    resolvedBy: 'AI',
  })

  return { routed: 'ai', resolved: true }
})

// Execute workflow
const result = await workflow.run(ticket)

API Endpoints

Expose HTTP endpoints:

// api/analyze.ts
export default async function analyzeTicket(req) {
  const { ticketId } = await req.json()

  const ticket = await $.Ticket.findById(ticketId)

  if (!ticket) {
    return new Response('Ticket not found', { status: 404 })
  }

  const analysis = await $.ai.generate({
    model: 'gpt-5',
    schema: $.TicketAnalysis,
    prompt: 'Analyze this ticket',
    context: { ticket },
  })

  return {
    ticketId,
    analysis,
    recommendations: generateRecommendations(analysis),
  }
}

Testing

Write tests for your service:

// tests/service.test.ts
import { describe, it, expect, mock } from '@dotdo/test'
import service from '../service'

describe('Customer Support Service', () => {
  it('categorizes tickets correctly', async () => {
    const ticket = mock.ticket({
      subject: 'Refund request',
      message: 'I want my money back',
    })

    const result = await service.handle('$.Ticket.created', { data: ticket })

    expect(result.category).toBe('billing')
    expect(result.priority).toBe('high')
  })

  it('generates appropriate responses', async () => {
    const ticket = mock.ticket({
      subject: 'How do I reset my password?',
    })

    const response = await service.generateResponse(ticket)

    expect(response).toContain('password reset')
    expect(response).toContain('link')
  })

  it('escalates urgent tickets', async () => {
    const ticket = mock.ticket({
      message: 'URGENT: System is down and we are losing money!',
    })

    const result = await service.handle('$.Ticket.created', { data: ticket })

    expect(result.escalated).toBe(true)
    expect(result.priority).toBe('critical')
  })
})

Run tests:

$ do test
$ do test --watch
$ do test --coverage

Configuration

Configure your service:

// do.config.ts
export default {
  service: {
    name: 'customer-support',
    region: 'auto', // or specific regions: ['us-east', 'eu-west']
    env: {
      AI_MODEL: 'gpt-5',
      MAX_RESPONSE_TIME: '2m',
    },
  },

  database: {
    // Database configuration
    collections: ['Ticket', 'Customer', 'Response'],
  },

  integrations: {
    // Third-party integrations
    stripe: {
      apiKey: process.env.STRIPE_API_KEY,
    },
    sendgrid: {
      apiKey: process.env.SENDGRID_API_KEY,
    },
  },

  monitoring: {
    // Monitoring configuration
    alerts: {
      errorRate: { threshold: 0.05, window: '5m' },
      latency: { threshold: 2000, window: '1m' },
    },
  },
}

Environment Variables

Manage secrets and configuration:

# Set environment variables
$ do env set STRIPE_API_KEY=sk_live_...
$ do env set SENDGRID_API_KEY=SG....

# List environment variables
$ do env list

# Remove environment variables
$ do env remove STRIPE_API_KEY

Access in code:

const apiKey = process.env.STRIPE_API_KEY

Local Development

Develop and test locally:

# Start development server
$ do dev

# Watch for changes
$ do dev --watch

# Test with local events
$ do emit '$.Ticket.created' --data ticket.json

# View logs
$ do logs --follow

# Test API endpoints
$ curl http://localhost:8787/health

Debugging

Debug your service:

// Add debug logging
import { $ } from 'sdk.do'

export default async function handler(event) {
  // Log event
  $.log.debug('Processing event', { event })

  try {
    const result = await processEvent(event)

    // Log result
    $.log.debug('Event processed', { result })

    return result
  } catch (error) {
    // Log error
    $.log.error('Error processing event', { error, event })
    throw error
  }
}

View logs:

$ do logs --level debug
$ do logs --search "Processing event"
$ do logs --tail 100

Code Organization

Organize your service code:

service/
├── service.ts              # Service definition
├── handlers/               # Event handlers
│   ├── ticket/
│   │   ├── created.ts
│   │   ├── updated.ts
│   │   └── resolved.ts
│   └── message/
│       ├── received.ts
│       └── sent.ts
├── api/                    # API endpoints
│   ├── analyze.ts
│   ├── metrics.ts
│   └── health.ts
├── lib/                    # Shared code
│   ├── ai/
│   │   ├── categorize.ts
│   │   ├── respond.ts
│   │   └── sentiment.ts
│   ├── email/
│   │   └── send.ts
│   └── utils/
│       ├── validate.ts
│       └── format.ts
├── workflows/              # Workflows
│   ├── resolution.ts
│   └── escalation.ts
├── tests/                  # Tests
│   ├── handlers/
│   ├── api/
│   └── workflows/
└── do.config.ts           # Configuration

Best Practices

Keep Handlers Focused

Each handler should do one thing well:

// Good: Focused handler
export default async function handleTicketCreated(event) {
  await categorizeTicket(event.data)
  await generateResponse(event.data)
  await notifyTeam(event.data)
}

// Bad: Doing too much
export default async function handleEverything(event) {
  // 500 lines of code...
}

Use TypeScript

Type-safe services are better services:

import { Service, Event } from 'sdk.do'

interface TicketCreatedEvent extends Event {
  data: {
    id: string
    subject: string
    message: string
    customerId: string
  }
}

export default async function handleTicketCreated(event: TicketCreatedEvent) {
  // TypeScript knows the shape of event.data
  const { subject, message, customerId } = event.data
}

Handle Errors Gracefully

Always handle potential failures:

export default async function handler(event) {
  try {
    return await processEvent(event)
  } catch (error) {
    if (error.retryable) {
      // Retry later
      await $.schedule('5 minutes', () => handler(event))
    } else {
      // Log and alert
      await $.log.error('Unrecoverable error', { error, event })
      await $.alert('service-error', { error, event })
    }
  }
}

Test Thoroughly

Write comprehensive tests:

describe('Ticket Handler', () => {
  it('handles normal tickets')
  it('handles urgent tickets')
  it('handles invalid tickets')
  it('handles API failures')
  it('handles rate limits')
  it('handles concurrent requests')
})

Next Steps