Skip to content

Error Handling

Handle errors gracefully with typed error classes and comprehensive error information.

Error Types

The SDK throws specific error classes for different failure scenarios:

typescript
import {
  AuthenticationError,
  RateLimitError,
  ValidationError,
  NotFoundError,
  APIError
} from '@myxara/sdk-js'

Basic Error Handling

typescript
try {
  await client.inboxes.create({
    local_part: 'support',
    name: 'Support Agent'
  })
} catch (error) {
  if (error instanceof APIError) {
    console.error('API Error:', error.message)
    console.error('Status:', error.status)
  } else {
    console.error('Unexpected error:', error)
  }
}

Authentication Errors

Thrown when API key is invalid or missing:

typescript
import { AuthenticationError } from '@myxara/sdk-js'

try {
  await client.inboxes.list()
} catch (error) {
  if (error instanceof AuthenticationError) {
    console.error('Authentication failed')
    console.error('Status:', error.status)  // 401
    console.error('Message:', error.message)
    // → "Invalid API key"

    // Check your API key
    console.log('Current key:', process.env.MYXARA_API_KEY)
  }
}

Common Causes

  • API key not set in environment variables
  • API key has been revoked
  • API key doesn't have required scopes
  • Typo in API key

Rate Limit Errors

Thrown when you exceed the rate limit:

typescript
import { RateLimitError } from '@myxara/sdk-js'

try {
  await client.inboxes.list()
} catch (error) {
  if (error instanceof RateLimitError) {
    console.error('Rate limited!')
    console.error('Retry after:', error.retryAfter, 'seconds')
    console.error('Limit resets at:', error.resetAt)

    // Wait and retry
    await new Promise(resolve => setTimeout(resolve, error.retryAfter * 1000))

    // Try again
    const inboxes = await client.inboxes.list()
  }
}

Rate Limit Properties

typescript
interface RateLimitError extends APIError {
  status: 429
  retryAfter: number    // Seconds until you can retry
  resetAt: Date         // When the rate limit resets
  limit: number         // Your rate limit (e.g., 100)
  remaining: number     // How many requests you have left (0)
}

Automatic Retries

The SDK automatically retries rate-limited requests:

typescript
const client = new MyxaraClient({
  apiKey: process.env.MYXARA_API_KEY!,
  maxRetries: 5  // Will retry up to 5 times
})

// This will automatically retry if rate limited
await client.inboxes.list()

Validation Errors

Thrown when request parameters are invalid:

typescript
import { ValidationError } from '@myxara/sdk-js'

try {
  await client.inboxes.create({
    local_part: '',  // ❌ Invalid - empty
    name: 'Support'
  })
} catch (error) {
  if (error instanceof ValidationError) {
    console.error('Validation failed')
    console.error('Status:', error.status)  // 400
    console.error('Errors:', error.errors)
    // → { local_part: ['This field is required'] }

    // Handle specific field errors
    if (error.errors.local_part) {
      console.error('local_part errors:', error.errors.local_part)
    }
  }
}

Validation Error Object

typescript
interface ValidationError extends APIError {
  status: 400
  errors: Record<string, string[]>  // Field-specific errors
}

Common Validation Errors

typescript
// Empty required field
await client.inboxes.create({
  local_part: '',  // ❌
  name: 'Support'
})
// → { local_part: ['This field is required'] }

// Invalid email format
await client.inboxes.messages(inboxId).send({
  to: 'not-an-email',  // ❌
  subject: 'Test',
  text: 'Test'
})
// → { to: ['Invalid email address'] }

// Invalid local_part characters
await client.inboxes.create({
  local_part: 'support@123',  // ❌ Can't contain @
  name: 'Support'
})
// → { local_part: ['Invalid characters in local_part'] }

Not Found Errors

Thrown when a resource doesn't exist:

typescript
import { NotFoundError } from '@myxara/sdk-js'

try {
  await client.inboxes.get('inbox_nonexistent')
} catch (error) {
  if (error instanceof NotFoundError) {
    console.error('Inbox not found')
    console.error('Status:', error.status)  // 404
    console.error('Message:', error.message)
    // → "Inbox not found"
  }
}

Conflict Errors

Thrown when a resource already exists:

typescript
try {
  // Create inbox
  await client.inboxes.create({
    local_part: 'support',
    name: 'Support Agent'
  })

  // Try to create again with same local_part
  await client.inboxes.create({
    local_part: 'support',  // ❌ Already exists
    name: 'Support Agent 2'
  })
} catch (error) {
  if (error instanceof APIError && error.status === 409) {
    console.error('Inbox with this local_part already exists')
  }
}

Network Errors

Handle network timeouts and connection issues:

typescript
try {
  await client.inboxes.list()
} catch (error) {
  if (error instanceof APIError) {
    console.error('API error:', error.message)
  } else if (error instanceof Error) {
    // Network error (fetch failed, timeout, etc.)
    console.error('Network error:', error.message)

    // Check if timeout
    if (error.message.includes('timeout')) {
      console.error('Request timed out')
    }

    // Check if offline
    if (error.message.includes('network')) {
      console.error('Network connection failed')
    }
  }
}

Custom Timeout

typescript
// Set longer timeout for slow operations
try {
  await client.inboxes.list({}, {
    timeout: 120000  // 2 minutes
  })
} catch (error) {
  console.error('Timed out after 2 minutes')
}

Handling All Error Types

Comprehensive error handling:

typescript
import {
  AuthenticationError,
  RateLimitError,
  ValidationError,
  NotFoundError,
  APIError
} from '@myxara/sdk-js'

async function safeCreateInbox(local_part: string, name: string) {
  try {
    const inbox = await client.inboxes.create({ local_part, name })
    return { success: true, inbox }

  } catch (error) {
    // Authentication error
    if (error instanceof AuthenticationError) {
      return {
        success: false,
        error: 'authentication',
        message: 'Invalid API key. Please check your credentials.'
      }
    }

    // Rate limit error
    if (error instanceof RateLimitError) {
      return {
        success: false,
        error: 'rate_limit',
        message: `Too many requests. Retry after ${error.retryAfter} seconds.`,
        retryAfter: error.retryAfter
      }
    }

    // Validation error
    if (error instanceof ValidationError) {
      return {
        success: false,
        error: 'validation',
        message: 'Invalid parameters',
        errors: error.errors
      }
    }

    // Not found error
    if (error instanceof NotFoundError) {
      return {
        success: false,
        error: 'not_found',
        message: 'Resource not found'
      }
    }

    // Conflict error (409)
    if (error instanceof APIError && error.status === 409) {
      return {
        success: false,
        error: 'conflict',
        message: 'Inbox with this address already exists'
      }
    }

    // Other API errors
    if (error instanceof APIError) {
      return {
        success: false,
        error: 'api_error',
        message: error.message,
        status: error.status
      }
    }

    // Network or unknown errors
    return {
      success: false,
      error: 'unknown',
      message: error instanceof Error ? error.message : 'Unknown error'
    }
  }
}

// Usage
const result = await safeCreateInbox('support', 'Support Agent')

if (result.success) {
  console.log('Inbox created:', result.inbox.address)
} else {
  console.error('Failed to create inbox:', result.message)

  if (result.error === 'validation') {
    console.error('Validation errors:', result.errors)
  } else if (result.error === 'rate_limit') {
    console.log(`Retry after ${result.retryAfter} seconds`)
  }
}

Retry Logic

Implement custom retry logic:

typescript
async function withRetry<T>(
  fn: () => Promise<T>,
  maxAttempts = 3,
  delayMs = 1000
): Promise<T> {
  let lastError: any

  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
    try {
      return await fn()
    } catch (error) {
      lastError = error

      // Don't retry validation or authentication errors
      if (error instanceof ValidationError || error instanceof AuthenticationError) {
        throw error
      }

      // For rate limits, use their retry-after
      if (error instanceof RateLimitError) {
        const delay = error.retryAfter * 1000
        console.log(`Rate limited. Waiting ${error.retryAfter}s...`)
        await new Promise(resolve => setTimeout(resolve, delay))
        continue
      }

      // Don't retry on last attempt
      if (attempt === maxAttempts) {
        break
      }

      // Exponential backoff for other errors
      const delay = delayMs * Math.pow(2, attempt - 1)
      console.log(`Attempt ${attempt} failed. Retrying in ${delay}ms...`)
      await new Promise(resolve => setTimeout(resolve, delay))
    }
  }

  throw lastError
}

// Usage
const inbox = await withRetry(
  () => client.inboxes.create({
    local_part: 'support',
    name: 'Support Agent'
  }),
  maxAttempts: 5,
  delayMs: 1000
)

Logging Errors

Production-ready error logging:

typescript
import { APIError } from '@myxara/sdk-js'

function logError(error: any, context?: Record<string, any>) {
  const timestamp = new Date().toISOString()

  if (error instanceof APIError) {
    console.error(JSON.stringify({
      timestamp,
      type: 'api_error',
      status: error.status,
      message: error.message,
      context,
      // Add request ID if available
      request_id: error.headers?.['x-request-id']
    }))
  } else if (error instanceof Error) {
    console.error(JSON.stringify({
      timestamp,
      type: 'error',
      message: error.message,
      stack: error.stack,
      context
    }))
  } else {
    console.error(JSON.stringify({
      timestamp,
      type: 'unknown',
      error: String(error),
      context
    }))
  }
}

// Usage
try {
  await client.inboxes.create({
    local_part: 'support',
    name: 'Support'
  })
} catch (error) {
  logError(error, {
    operation: 'create_inbox',
    local_part: 'support'
  })
  throw error
}

Complete Example

typescript
import {
  MyxaraClient,
  AuthenticationError,
  RateLimitError,
  ValidationError,
  NotFoundError,
  APIError
} from '@myxara/sdk-js'

const client = new MyxaraClient({
  apiKey: process.env.MYXARA_API_KEY!,
  maxRetries: 3  // Automatic retries
})

async function sendEmail(inboxId: string, to: string, subject: string, text: string) {
  try {
    const message = await client.inboxes.messages(inboxId).send({
      to,
      subject,
      text
    })

    console.log(`✅ Email sent: ${message.id}`)
    return message

  } catch (error) {
    // Handle specific errors
    if (error instanceof AuthenticationError) {
      console.error('❌ Authentication failed - check API key')
      // Maybe refresh API key or alert admin

    } else if (error instanceof RateLimitError) {
      console.error(`⏸️ Rate limited - retry after ${error.retryAfter}s`)
      // Queue for later or implement backoff

    } else if (error instanceof ValidationError) {
      console.error('❌ Invalid email parameters:')
      for (const [field, errors] of Object.entries(error.errors)) {
        console.error(`  ${field}: ${errors.join(', ')}`)
      }
      // Maybe validate inputs before sending

    } else if (error instanceof NotFoundError) {
      console.error('❌ Inbox not found')
      // Maybe create inbox first

    } else if (error instanceof APIError) {
      console.error(`❌ API error (${error.status}): ${error.message}`)
      // Log to error tracking service

    } else {
      console.error('❌ Unexpected error:', error)
      // Log to error tracking service
    }

    throw error
  }
}

// Usage
sendEmail(
  'inbox_abc123',
  'customer@example.com',
  'Welcome!',
  'Thanks for signing up.'
).catch(error => {
  console.error('Failed to send email')
  process.exit(1)
})

TypeScript Types

typescript
import type {
  APIError,
  AuthenticationError,
  RateLimitError,
  ValidationError,
  NotFoundError
} from '@myxara/sdk-js'

function handleError(error: unknown) {
  if (error instanceof ValidationError) {
    // TypeScript knows about error.errors
    const fieldErrors: Record<string, string[]> = error.errors
  } else if (error instanceof RateLimitError) {
    // TypeScript knows about error.retryAfter
    const retryAfter: number = error.retryAfter
    const resetAt: Date = error.resetAt
  } else if (error instanceof APIError) {
    // TypeScript knows about error.status
    const status: number = error.status
  }
}

Next Steps

Released under the MIT License (SDK) & Elastic License 2.0 (Server)