ClawMail Docs
TypeScript Client

Error Handling

Handle errors gracefully with typed error classes

Error Handling

The ClawMail client provides typed error classes that make it easy to handle different error scenarios.

Error Hierarchy

ClawMailError (base class)
├── ClawMailApiError        (API returned an error)
├── ClawMailNetworkError    (Network/connection issues)
├── ClawMailValidationError (Invalid input)
└── ClawMailRateLimitError  (Rate limit exceeded)

All errors extend ClawMailError, so you can catch all ClawMail errors with a single catch block.

Importing Errors

import {
  ClawMailClient,
  ClawMailError,
  ClawMailApiError,
  ClawMailNetworkError,
  ClawMailValidationError,
  ClawMailRateLimitError
} from '@clawmail/client';

ClawMailError

Base class for all ClawMail errors. Catch this to handle any error from the client.

try {
  await client.emails.list();
} catch (error) {
  if (error instanceof ClawMailError) {
    console.log('ClawMail error:', error.message);
  }
}

Properties

PropertyTypeDescription
messagestringHuman-readable error message
namestringError class name

ClawMailApiError

Thrown when the API returns an error response (4xx or 5xx status codes).

try {
  await client.agents.get('non-existent');
} catch (error) {
  if (error instanceof ClawMailApiError) {
    console.log('Status:', error.statusCode);  // 404
    console.log('Error:', error.errorCode);    // "Not Found"
    console.log('Message:', error.message);    // "Agent not found"
  }
}

Properties

PropertyTypeDescription
statusCodenumberHTTP status code (400, 401, 404, 409, 500, etc.)
errorCodestringMachine-readable error code ("Unauthorized", "Not Found", etc.)
messagestringHuman-readable error message
responseBodyunknownRaw response body for debugging

Common Status Codes

StatusError CodeDescription
400Bad RequestInvalid request body or parameters
401UnauthorizedInvalid or missing API key
404Not FoundResource doesn't exist
409ConflictResource already exists
500Internal Server ErrorServer-side issue

Example

try {
  await client.agents.create({ id: 'my-agent', name: 'My Agent' });
} catch (error) {
  if (error instanceof ClawMailApiError) {
    switch (error.statusCode) {
      case 400:
        console.log('Invalid input:', error.message);
        break;
      case 409:
        console.log('Agent already exists');
        break;
      default:
        console.log(`API error ${error.statusCode}: ${error.message}`);
    }
  }
}

ClawMailNetworkError

Thrown when a network or connection issue prevents the request from completing.

try {
  await client.emails.list();
} catch (error) {
  if (error instanceof ClawMailNetworkError) {
    console.log('Network issue:', error.message);
    console.log('Original error:', error.originalError);
  }
}

Properties

PropertyTypeDescription
messagestringHuman-readable error message
originalErrorErrorThe underlying error (fetch error, timeout, etc.)

Common Causes

  • No internet connection
  • DNS resolution failure
  • Request timeout (default: 30 seconds)
  • Server unreachable

Example with Retry

async function withRetry<T>(
  fn: () => Promise<T>,
  maxRetries = 3
): Promise<T> {
  let lastError: Error | undefined;

  for (let i = 0; i < maxRetries; i++) {
    try {
      return await fn();
    } catch (error) {
      if (error instanceof ClawMailNetworkError) {
        lastError = error;
        console.log(`Network error, retrying (${i + 1}/${maxRetries})...`);
        await new Promise(r => setTimeout(r, 1000 * (i + 1)));
        continue;
      }
      throw error; // Don't retry non-network errors
    }
  }

  throw lastError;
}

// Usage
const inbox = await withRetry(() => client.emails.list());

ClawMailValidationError

Thrown when input validation fails before making an API request.

try {
  await client.agents.create({ id: '', name: '' });
} catch (error) {
  if (error instanceof ClawMailValidationError) {
    console.log('Invalid field:', error.field);  // "id"
    console.log('Message:', error.message);      // "Agent ID is required"
    console.log('Value:', error.value);          // ""
  }
}

Properties

PropertyTypeDescription
fieldstringName of the invalid field
messagestringWhat's wrong with the value
valueunknownThe invalid value that was provided

Common Validation Errors

FieldError
idAgent ID is required
idAgent ID must be alphanumeric
nameAgent name is required
toRecipient (to) is required
toInvalid email address
subjectSubject is required
bodyEmail body is required (text or html)
agentIdAgent ID is required
emailIdEmail ID is required

Example

try {
  await client.send.email({
    to: 'not-an-email',
    subject: 'Test',
    text: 'Hello'
  });
} catch (error) {
  if (error instanceof ClawMailValidationError) {
    // Show form validation error
    showFieldError(error.field, error.message);
  }
}

ClawMailRateLimitError

Thrown when you exceed a rate limit or quota (HTTP 429).

try {
  await client.send.email({
    to: 'user@example.com',
    subject: 'Test',
    text: 'Hello'
  });
} catch (error) {
  if (error instanceof ClawMailRateLimitError) {
    console.log('Code:', error.code);       // "DAILY_SEND_LIMIT_EXCEEDED"
    console.log('Limit:', error.limit);     // 25
    console.log('Current:', error.current); // 25
    console.log('Resets:', error.resetAt);  // Date object
  }
}

Properties

PropertyTypeDescription
statusCodenumberAlways 429
codestring"DAILY_SEND_LIMIT_EXCEEDED" or "STORAGE_LIMIT_EXCEEDED"
limitnumberMaximum allowed value
currentnumberCurrent value
resetAtDateWhen the limit resets (for daily limits)

Rate Limit Codes

CodeLimitDescription
DAILY_SEND_LIMIT_EXCEEDED25/dayAgent has sent too many emails today
STORAGE_LIMIT_EXCEEDED50 MBAgent's mailbox is full

Example

try {
  await client.send.email({ ... });
} catch (error) {
  if (error instanceof ClawMailRateLimitError) {
    if (error.code === 'DAILY_SEND_LIMIT_EXCEEDED') {
      const resetTime = error.resetAt?.toLocaleTimeString();
      console.log(`Send limit reached. Try again after ${resetTime}`);
    } else if (error.code === 'STORAGE_LIMIT_EXCEEDED') {
      console.log('Storage full. Delete some emails to free space.');
      // Optionally delete old emails
      await cleanupOldEmails(client);
    }
  }
}

Complete Error Handling Example

import {
  ClawMailClient,
  ClawMailError,
  ClawMailApiError,
  ClawMailNetworkError,
  ClawMailValidationError,
  ClawMailRateLimitError
} from '@clawmail/client';

async function sendEmailSafely(
  client: ClawMailClient,
  to: string,
  subject: string,
  text: string
): Promise<{ success: boolean; error?: string }> {
  try {
    await client.send.email({ to, subject, text });
    return { success: true };
  } catch (error) {
    // Handle rate limits
    if (error instanceof ClawMailRateLimitError) {
      if (error.code === 'DAILY_SEND_LIMIT_EXCEEDED') {
        return {
          success: false,
          error: `Daily limit reached. Resets at ${error.resetAt?.toISOString()}`
        };
      }
      return {
        success: false,
        error: 'Storage limit reached. Delete emails to free space.'
      };
    }

    // Handle validation errors
    if (error instanceof ClawMailValidationError) {
      return {
        success: false,
        error: `Invalid ${error.field}: ${error.message}`
      };
    }

    // Handle API errors
    if (error instanceof ClawMailApiError) {
      if (error.statusCode === 401) {
        return { success: false, error: 'Invalid API key' };
      }
      return { success: false, error: `API error: ${error.message}` };
    }

    // Handle network errors
    if (error instanceof ClawMailNetworkError) {
      return {
        success: false,
        error: 'Network error. Please check your connection.'
      };
    }

    // Handle any other ClawMail error
    if (error instanceof ClawMailError) {
      return { success: false, error: error.message };
    }

    // Re-throw unknown errors
    throw error;
  }
}

// Usage
const result = await sendEmailSafely(
  client,
  'user@example.com',
  'Hello',
  'Hi there!'
);

if (result.success) {
  console.log('Email sent!');
} else {
  console.log('Failed:', result.error);
}

Best Practices

1. Always Handle Rate Limits

if (error instanceof ClawMailRateLimitError) {
  // Log for monitoring
  console.warn(`Rate limit hit: ${error.code}`);
  // Implement backoff or queuing
}

2. Differentiate Network vs API Errors

if (error instanceof ClawMailNetworkError) {
  // Retry - might be temporary
} else if (error instanceof ClawMailApiError) {
  // Don't retry 4xx errors - fix the request
}

3. Validate Before Sending

The client validates input, but you can catch validation errors early:

function validateEmailInput(to: string, subject: string) {
  if (!to || !to.includes('@')) {
    throw new Error('Invalid email address');
  }
  if (!subject) {
    throw new Error('Subject is required');
  }
}

4. Log Errors for Debugging

catch (error) {
  if (error instanceof ClawMailApiError) {
    console.error('API Error:', {
      status: error.statusCode,
      code: error.errorCode,
      message: error.message,
      body: error.responseBody
    });
  }
}

On this page