.do

Building Services

Step-by-step guide to implementing services from scratch

Learn how to build services from scratch with step-by-step examples and best practices.

Quick Start: Your First Service

Let's build a simple email summarization service that takes long emails and creates concise summaries. For a complete walkthrough, see Quick Start Guide →

Step 1: Define the Service

Start with a clear service definition. Learn more about Service Definition →

import $, { ai } from 'sdk.do'

const emailSummaryService = await $.Service.create({
  // Basic information
  name: 'Email Summarizer',
  description: 'Condense long emails into brief, actionable summaries',
  version: '1.0.0',

  // Classification
  type: $.ServiceType.ContentGeneration,
  category: $.ServiceCategory.Productivity,

  // What it needs
  input: {
    required: ['emailContent'],
    optional: ['maxLength', 'style'],
  },

  // What it provides
  output: {
    summary: 'string',
    keyPoints: 'array',
    actionItems: 'array',
    sentiment: 'string',
  },

  // Service guarantees
  sla: {
    responseTime: '10 seconds',
    accuracy: 0.9,
  },

  // Pricing
  pricing: {
    model: 'per-summary',
    rate: 0.1,
  },
})

Step 2: Implement the Logic

Create the service handler:

import { on, send } from 'sdk.do'

on.ServiceRequest.created(async (request) => {
  // Only handle our service
  if (request.serviceId !== emailSummaryService.id) return

  try {
    const { emailContent, maxLength, style } = request.inputs

    // Generate summary using AI
    const result = await ai.generate({
      model: 'gpt-5',
      systemPrompt: 'You are an expert at summarizing emails concisely.',
      prompt: `Summarize this email in ${maxLength || 150} words or less:

${emailContent}

Provide:
1. A brief summary
2. Key points (bullet list)
3. Action items (if any)
4. Overall sentiment`,
      temperature: 0.3, // Lower temperature for consistency
    })

    // Parse the AI response
    const parsed = parseEmailSummary(result.content)

    // Deliver the result
    send.ServiceResult.deliver({
      requestId: request.id,
      outputs: {
        summary: parsed.summary,
        keyPoints: parsed.keyPoints,
        actionItems: parsed.actionItems,
        sentiment: parsed.sentiment,
      },
      metadata: {
        originalLength: emailContent.length,
        summaryLength: parsed.summary.length,
        compressionRatio: parsed.summary.length / emailContent.length,
      },
    })

    // Charge for the service
    send.Payment.charge({
      customerId: request.customerId,
      amount: emailSummaryService.pricing.rate,
      description: 'Email Summary Service',
    })
  } catch (error) {
    // Handle errors gracefully
    send.ServiceRequest.fail({
      requestId: request.id,
      error: error.message,
      retryable: error.retryable || false,
    })
  }
})

// Helper function to parse AI output
function parseEmailSummary(content: string) {
  // Extract structured data from AI response
  const lines = content.split('\n').filter((l) => l.trim())

  return {
    summary: extractSummary(lines),
    keyPoints: extractKeyPoints(lines),
    actionItems: extractActionItems(lines),
    sentiment: extractSentiment(lines),
  }
}

Step 3: Test the Service

Create comprehensive tests. Learn more about Quality Assurance →

import { test, expect } from 'vitest'

test('summarizes email correctly', async () => {
  const testEmail = `
    Dear Team,

    I wanted to follow up on our discussion from yesterday's meeting about the Q4 roadmap.
    As agreed, we need to prioritize the following items:

    1. Complete the API redesign by end of October
    2. Launch the mobile app beta in November
    3. Prepare marketing materials for December launch

    Please review the attached document and send me your feedback by Friday.
    We'll need to make some tough decisions about scope if we want to hit these dates.

    Also, don't forget about the team offsite next month - I'll send calendar invites soon.

    Best regards,
    John
  `

  const result = await send.ServiceRequest.create({
    serviceId: emailSummaryService.id,
    inputs: {
      emailContent: testEmail,
      maxLength: 100,
    },
  })

  // Wait for result
  const output = await waitForResult(result.id)

  // Verify outputs
  expect(output.summary).toBeDefined()
  expect(output.summary.length).toBeLessThanOrEqual(100)
  expect(output.keyPoints).toHaveLength(3)
  expect(output.actionItems).toContain('Review document by Friday')
  expect(output.sentiment).toBe('professional')
})

test('handles error cases', async () => {
  const result = await send.ServiceRequest.create({
    serviceId: emailSummaryService.id,
    inputs: {
      emailContent: '', // Empty email
    },
  })

  const output = await waitForResult(result.id)
  expect(output.error).toBeDefined()
})

Step 4: Deploy the Service

Make your service available. Learn more about Deployment →

// Deploy to production
await $.Service.deploy({
  serviceId: emailSummaryService.id,
  environment: 'production',
  configuration: {
    maxConcurrency: 50,
    timeout: 30000, // 30 seconds
    retryPolicy: {
      maxRetries: 3,
      backoff: 'exponential',
    },
  },
})

// Make service discoverable
await $.Service.publish({
  serviceId: emailSummaryService.id,
  visibility: 'public',
  categories: ['productivity', 'communication'],
  tags: ['email', 'summary', 'ai'],
})

Building a More Complex Service

Let's build a comprehensive content marketing service that combines multiple capabilities.

Service Architecture

import $, { ai, db, on, send } from 'sdk.do'

const contentMarketingService = await $.Service.create({
  name: 'Content Marketing Suite',
  type: $.ServiceType.Composite,
  description: 'Complete content marketing from research to publication',

  // Define workflow stages
  workflow: [
    {
      stage: 'research',
      name: 'Topic Research',
      required: true,
      estimated: '2 minutes',
    },
    {
      stage: 'outline',
      name: 'Content Outline',
      required: true,
      estimated: '1 minute',
      dependsOn: ['research'],
    },
    {
      stage: 'content',
      name: 'Content Creation',
      required: true,
      estimated: '5 minutes',
      dependsOn: ['outline'],
    },
    {
      stage: 'seo',
      name: 'SEO Optimization',
      required: true,
      estimated: '2 minutes',
      dependsOn: ['content'],
    },
    {
      stage: 'images',
      name: 'Image Generation',
      required: false,
      estimated: '3 minutes',
      dependsOn: ['content'],
    },
    {
      stage: 'social',
      name: 'Social Media Posts',
      required: false,
      estimated: '2 minutes',
      dependsOn: ['content'],
    },
  ],

  // Input requirements
  input: {
    required: ['topic', 'targetKeywords', 'targetAudience'],
    optional: ['tone', 'length', 'includeImages', 'generateSocial'],
  },

  // Pricing
  pricing: {
    model: 'bundled',
    baseRate: 100.0,
    options: {
      images: 25.0,
      socialPosts: 15.0,
    },
  },
})

Implementing the Workflow

on.ServiceRequest.created(async (request) => {
  if (request.serviceId !== contentMarketingService.id) return

  const results = {}
  const inputs = request.inputs

  try {
    // Stage 1: Research
    await updateProgress(request.id, 'research', 'in-progress')

    const research = await ai.research({
      model: 'gpt-5',
      topic: inputs.topic,
      keywords: inputs.targetKeywords,
      sources: ['web', 'news', 'academic'],
      depth: 'comprehensive',
    })

    results.research = research
    await updateProgress(request.id, 'research', 'completed')

    // Stage 2: Create Outline
    await updateProgress(request.id, 'outline', 'in-progress')

    const outline = await ai.generate({
      model: 'gpt-5',
      type: 'content-outline',
      context: {
        topic: inputs.topic,
        research: research.summary,
        keywords: inputs.targetKeywords,
        audience: inputs.targetAudience,
      },
    })

    results.outline = outline
    await updateProgress(request.id, 'outline', 'completed')

    // Stage 3: Generate Content
    await updateProgress(request.id, 'content', 'in-progress')

    const content = await ai.generate({
      model: 'gpt-5',
      type: 'blog-post',
      context: {
        outline: outline.content,
        research: research.findings,
        tone: inputs.tone || 'professional',
        length: inputs.length || 1500,
        keywords: inputs.targetKeywords,
      },
      quality: {
        minReadability: 60,
        keywordDensity: { min: 0.01, max: 0.03 },
      },
    })

    results.content = content
    await updateProgress(request.id, 'content', 'completed')

    // Stage 4: SEO Optimization
    await updateProgress(request.id, 'seo', 'in-progress')

    const seo = await ai.optimize({
      model: 'gpt-5',
      content: content.text,
      keywords: inputs.targetKeywords,
      optimizations: ['meta-description', 'title-tag', 'header-structure', 'internal-linking', 'image-alt-text'],
    })

    results.seo = seo
    await updateProgress(request.id, 'seo', 'completed')

    // Stage 5: Generate Images (optional)
    if (inputs.includeImages) {
      await updateProgress(request.id, 'images', 'in-progress')

      const images = await ai.generateImages({
        model: 'dalle-3',
        prompts: await ai.createImagePrompts({
          content: content.text,
          count: 3,
          style: 'professional',
        }),
      })

      results.images = images
      await updateProgress(request.id, 'images', 'completed')
    }

    // Stage 6: Create Social Posts (optional)
    if (inputs.generateSocial) {
      await updateProgress(request.id, 'social', 'in-progress')

      const socialPosts = await ai.generate({
        model: 'gpt-5',
        type: 'social-media-posts',
        context: {
          content: content.summary,
          platforms: ['twitter', 'linkedin', 'facebook'],
          tone: inputs.tone,
        },
      })

      results.socialPosts = socialPosts
      await updateProgress(request.id, 'social', 'completed')
    }

    // Deliver complete package
    send.ServiceResult.deliver({
      requestId: request.id,
      outputs: results,
      metadata: {
        stages: contentMarketingService.workflow.map((s) => s.stage),
        totalTime: calculateTotalTime(results),
        quality: assessQuality(results),
      },
    })

    // Calculate final billing
    let cost = contentMarketingService.pricing.baseRate
    if (inputs.includeImages) cost += contentMarketingService.pricing.options.images
    if (inputs.generateSocial) cost += contentMarketingService.pricing.options.socialPosts

    send.Payment.charge({
      customerId: request.customerId,
      amount: cost,
      description: 'Content Marketing Suite',
      breakdown: {
        base: contentMarketingService.pricing.baseRate,
        images: inputs.includeImages ? contentMarketingService.pricing.options.images : 0,
        social: inputs.generateSocial ? contentMarketingService.pricing.options.socialPosts : 0,
      },
    })
  } catch (error) {
    send.ServiceRequest.fail({
      requestId: request.id,
      error: error.message,
      stage: getCurrentStage(results),
      partialResults: results,
    })
  }
})

// Helper: Update progress
async function updateProgress(requestId: string, stage: string, status: string) {
  await db.ServiceRequest.update({
    where: { id: requestId },
    data: {
      progress: {
        [stage]: {
          status,
          timestamp: new Date(),
        },
      },
    },
  })

  send.ServiceProgress.updated({
    requestId,
    stage,
    status,
  })
}

Service Patterns

Pattern 1: Synchronous Services

For quick, immediate responses:

// Simple validation service
const validationService = await $.Service.create({
  name: 'Email Validator',
  type: $.ServiceType.Validation,
  executionMode: 'synchronous',

  pricing: {
    model: 'per-validation',
    rate: 0.01,
  },
})

on.ServiceRequest.created(async (request) => {
  if (request.serviceId !== validationService.id) return

  // Validate immediately
  const isValid = await validateEmail(request.inputs.email)

  // Return result synchronously
  send.ServiceResult.deliver({
    requestId: request.id,
    outputs: {
      valid: isValid,
      details: isValid ? null : getValidationErrors(request.inputs.email),
    },
  })
})

Pattern 2: Asynchronous Services

For long-running operations:

// Video transcription service
const transcriptionService = await $.Service.create({
  name: 'Video Transcriber',
  type: $.ServiceType.MediaProcessing,
  executionMode: 'asynchronous',

  pricing: {
    model: 'per-minute',
    rate: 0.5,
  },
})

on.ServiceRequest.created(async (request) => {
  if (request.serviceId !== transcriptionService.id) return

  // Start async processing
  send.ServiceExecution.start({
    requestId: request.id,
    estimatedDuration: estimateTranscriptionTime(request.inputs.videoUrl),
  })

  // Process in background
  processVideoTranscription(request).then(async (result) => {
    send.ServiceResult.deliver({
      requestId: request.id,
      outputs: result,
    })
  })

  // Return immediately with job ID
  send.ServiceRequest.acknowledged({
    requestId: request.id,
    message: 'Transcription started, you will be notified when complete',
  })
})

Pattern 3: Streaming Services

For real-time results:

// Real-time translation service
const translationService = await $.Service.create({
  name: 'Live Translator',
  type: $.ServiceType.Translation,
  executionMode: 'streaming',

  pricing: {
    model: 'per-session',
    rate: 5.0,
  },
})

on.ServiceRequest.created(async (request) => {
  if (request.serviceId !== translationService.id) return

  // Create streaming session
  const session = await createStreamingSession(request)

  // Stream translations as they arrive
  for await (const chunk of session.inputStream) {
    const translated = await ai.translate({
      text: chunk,
      from: request.inputs.sourceLanguage,
      to: request.inputs.targetLanguage,
    })

    send.ServiceResult.stream({
      requestId: request.id,
      chunk: translated,
    })
  }

  // Close session
  send.ServiceResult.complete({
    requestId: request.id,
  })
})

Pattern 4: Human-in-the-Loop Services

For services requiring human review. See complete workflow example →

// Content moderation service
const moderationService = await $.Service.create({
  name: 'Content Moderator',
  type: $.ServiceType.ContentModeration,
  executionMode: 'human-in-loop',

  pricing: {
    model: 'tiered',
    ai: 0.05,
    human: 1.0,
  },
})

on.ServiceRequest.created(async (request) => {
  if (request.serviceId !== moderationService.id) return

  // AI first pass
  const aiModeration = await ai.moderate({
    model: 'gpt-5',
    content: request.inputs.content,
    policies: request.inputs.policies,
  })

  // If confidence is high, auto-approve/reject
  if (aiModeration.confidence > 0.95) {
    send.ServiceResult.deliver({
      requestId: request.id,
      outputs: {
        decision: aiModeration.decision,
        reason: aiModeration.reason,
        reviewedBy: 'ai',
      },
    })

    send.Payment.charge({
      customerId: request.customerId,
      amount: moderationService.pricing.ai,
    })
    return
  }

  // Low confidence - send to human
  send.HumanReview.create({
    requestId: request.id,
    content: request.inputs.content,
    aiSuggestion: aiModeration,
    priority: aiModeration.severity,
  })

  send.Payment.charge({
    customerId: request.customerId,
    amount: moderationService.pricing.human,
  })
})

// Human review complete
on.HumanReview.completed(async (review) => {
  send.ServiceResult.deliver({
    requestId: review.requestId,
    outputs: {
      decision: review.decision,
      reason: review.reason,
      reviewedBy: 'human',
      reviewer: review.reviewerId,
    },
  })
})

Best Practices

1. Error Handling

Always handle errors gracefully:

try {
  // Service execution
} catch (error) {
  // Log error
  db.ErrorLog.create({
    serviceId: service.id,
    requestId: request.id,
    error: error.message,
    stack: error.stack,
    timestamp: new Date(),
  })

  // Notify customer
  send.ServiceRequest.fail({
    requestId: request.id,
    error: error.message,
    retryable: isRetryableError(error),
  })

  // Alert team if critical
  if (error.severity === 'critical') {
    send.Alert.create({
      type: 'service-failure',
      serviceId: service.id,
      error: error.message,
    })
  }
}

2. Input Validation

Validate all inputs before processing:

function validateServiceInputs(request, service) {
  const errors = []

  // Check required fields
  for (const field of service.input.required) {
    if (!request.inputs[field]) {
      errors.push(`Missing required field: ${field}`)
    }
  }

  // Validate field types
  for (const [field, value] of Object.entries(request.inputs)) {
    const expectedType = service.input.schema[field]
    if (!validateType(value, expectedType)) {
      errors.push(`Invalid type for ${field}: expected ${expectedType}`)
    }
  }

  // Business rule validation
  if (service.input.rules) {
    for (const rule of service.input.rules) {
      if (!rule.validate(request.inputs)) {
        errors.push(rule.message)
      }
    }
  }

  return {
    valid: errors.length === 0,
    errors,
  }
}

3. Progress Tracking

Keep customers informed:

// Start
send.ServiceProgress.started({
  requestId: request.id,
  estimatedDuration: service.sla.responseTime,
})

// Updates
send.ServiceProgress.updated({
  requestId: request.id,
  stage: 'processing',
  progress: 0.5, // 50%
})

// Complete
send.ServiceProgress.completed({
  requestId: request.id,
  duration: actualDuration,
})

4. Quality Assurance

Verify output quality:

async function verifyQuality(result, service) {
  // Check against SLA
  if (result.duration > service.sla.responseTime) {
    send.Alert.create({
      type: 'sla-breach',
      metric: 'response-time',
    })
  }

  // Validate output
  if (service.output.validation) {
    const validation = await service.output.validation(result.outputs)
    if (!validation.valid) {
      throw new Error(`Quality check failed: ${validation.errors}`)
    }
  }

  // AI quality assessment
  if (service.quality.aiCheck) {
    const assessment = await ai.assess({
      output: result.outputs,
      criteria: service.quality.criteria,
    })

    if (assessment.score < service.quality.threshold) {
      // Regenerate or flag for review
    }
  }
}

Next Steps