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
| Property | Type | Description |
|---|---|---|
message | string | Human-readable error message |
name | string | Error 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
| Property | Type | Description |
|---|---|---|
statusCode | number | HTTP status code (400, 401, 404, 409, 500, etc.) |
errorCode | string | Machine-readable error code ("Unauthorized", "Not Found", etc.) |
message | string | Human-readable error message |
responseBody | unknown | Raw response body for debugging |
Common Status Codes
| Status | Error Code | Description |
|---|---|---|
| 400 | Bad Request | Invalid request body or parameters |
| 401 | Unauthorized | Invalid or missing API key |
| 404 | Not Found | Resource doesn't exist |
| 409 | Conflict | Resource already exists |
| 500 | Internal Server Error | Server-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
| Property | Type | Description |
|---|---|---|
message | string | Human-readable error message |
originalError | Error | The 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
| Property | Type | Description |
|---|---|---|
field | string | Name of the invalid field |
message | string | What's wrong with the value |
value | unknown | The invalid value that was provided |
Common Validation Errors
| Field | Error |
|---|---|
id | Agent ID is required |
id | Agent ID must be alphanumeric |
name | Agent name is required |
to | Recipient (to) is required |
to | Invalid email address |
subject | Subject is required |
body | Email body is required (text or html) |
agentId | Agent ID is required |
emailId | Email 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
| Property | Type | Description |
|---|---|---|
statusCode | number | Always 429 |
code | string | "DAILY_SEND_LIMIT_EXCEEDED" or "STORAGE_LIMIT_EXCEEDED" |
limit | number | Maximum allowed value |
current | number | Current value |
resetAt | Date | When the limit resets (for daily limits) |
Rate Limit Codes
| Code | Limit | Description |
|---|---|---|
DAILY_SEND_LIMIT_EXCEEDED | 25/day | Agent has sent too many emails today |
STORAGE_LIMIT_EXCEEDED | 50 MB | Agent'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
});
}
}