Error Handling
The VaultSandbox Client SDK provides comprehensive error handling with automatic retries for transient failures and specific error types for different failure scenarios.
Error Hierarchy
Section titled “Error Hierarchy”All SDK errors extend from the base VaultSandboxError class, allowing you to catch all SDK-specific errors with a single catch block.
VaultSandboxError (base class)├── ApiError├── NetworkError├── TimeoutError├── InboxNotFoundError├── EmailNotFoundError├── InboxAlreadyExistsError├── InvalidImportDataError├── DecryptionError├── SignatureVerificationError├── SSEError└── StrategyErrorAutomatic Retries
Section titled “Automatic Retries”The SDK automatically retries failed HTTP requests for transient errors. This helps mitigate temporary network issues or server-side problems.
Default Retry Behavior
Section titled “Default Retry Behavior”By default, requests are retried for these HTTP status codes:
408- Request Timeout429- Too Many Requests (Rate Limiting)500- Internal Server Error502- Bad Gateway503- Service Unavailable504- Gateway Timeout
Configuration
Section titled “Configuration”Configure retry behavior when creating the client:
import { VaultSandboxClient } from '@vaultsandbox/client';
const client = new VaultSandboxClient({ url: 'https://smtp.vaultsandbox.com', apiKey: process.env.VAULTSANDBOX_API_KEY, maxRetries: 5, // Default: 3 retryDelay: 2000, // Default: 1000ms retryOn: [408, 429, 500, 502, 503, 504], // Default status codes});Retry Strategy
Section titled “Retry Strategy”The SDK uses exponential backoff for retries:
- 1st retry:
retryDelayms - 2nd retry:
retryDelay * 2ms - 3rd retry:
retryDelay * 4ms - And so on…
Example
Section titled “Example”// With retryDelay: 1000ms and maxRetries: 3// Retry schedule:// 1st attempt: immediate// 2nd attempt: after 1000ms// 3rd attempt: after 2000ms// 4th attempt: after 4000ms// Total time: up to 7 seconds + request timeError Types
Section titled “Error Types”VaultSandboxError
Section titled “VaultSandboxError”Base class for all SDK errors. Use this to catch any SDK-specific error.
class VaultSandboxError extends Error { name: string; message: string;}Example
Section titled “Example”import { VaultSandboxError } from '@vaultsandbox/client';
try { const inbox = await client.createInbox(); // Use inbox...} catch (error) { if (error instanceof VaultSandboxError) { console.error('VaultSandbox error:', error.message); } else { console.error('Unexpected error:', error); }}ApiError
Section titled “ApiError”Thrown for API-level errors such as invalid requests or permission denied.
class ApiError extends VaultSandboxError { statusCode: number; message: string;}Properties
Section titled “Properties”statusCode: HTTP status code from the APImessage: Error message from the server
Example
Section titled “Example”import { ApiError } from '@vaultsandbox/client';
try { const inbox = await client.createInbox();} catch (error) { if (error instanceof ApiError) { console.error(`API Error (${error.statusCode}): ${error.message}`);
if (error.statusCode === 401) { console.error('Invalid API key'); } else if (error.statusCode === 403) { console.error('Permission denied'); } else if (error.statusCode === 429) { console.error('Rate limit exceeded'); } }}NetworkError
Section titled “NetworkError”Thrown when there is a network-level failure (e.g., cannot connect to server).
class NetworkError extends VaultSandboxError { message: string;}Example
Section titled “Example”import { NetworkError } from '@vaultsandbox/client';
try { const inbox = await client.createInbox();} catch (error) { if (error instanceof NetworkError) { console.error('Network error:', error.message); console.error('Check your internet connection and server URL'); }}TimeoutError
Section titled “TimeoutError”Thrown by methods like waitForEmail() and waitForEmailCount() when the timeout is reached before the condition is met.
class TimeoutError extends VaultSandboxError { message: string;}Example
Section titled “Example”import { TimeoutError } from '@vaultsandbox/client';
try { const email = await inbox.waitForEmail({ timeout: 5000, subject: /Welcome/, });} catch (error) { if (error instanceof TimeoutError) { console.error('Timed out waiting for email'); console.error('Email may not have been sent or took too long to deliver');
// Check what emails did arrive const emails = await inbox.listEmails(); console.log(`Found ${emails.length} emails:`); emails.forEach((e) => console.log(` - ${e.subject}`)); }}InboxNotFoundError
Section titled “InboxNotFoundError”Represents an error when an inbox does not exist. This error class is exported for type checking, but in practice, a missing inbox will throw an ApiError with statusCode: 404.
class InboxNotFoundError extends VaultSandboxError { message: string;}Example
Section titled “Example”import { ApiError } from '@vaultsandbox/client';
try { const emails = await inbox.listEmails();} catch (error) { if (error instanceof ApiError && error.statusCode === 404) { console.error('Inbox no longer exists'); console.error('It may have expired or been deleted'); }}EmailNotFoundError
Section titled “EmailNotFoundError”Represents an error when an email does not exist. This error class is exported for type checking, but in practice, a missing email will throw an ApiError with statusCode: 404.
class EmailNotFoundError extends VaultSandboxError { message: string;}Example
Section titled “Example”import { ApiError } from '@vaultsandbox/client';
try { const email = await inbox.getEmail('non-existent-id');} catch (error) { if (error instanceof ApiError && error.statusCode === 404) { console.error('Email not found'); console.error('It may have been deleted'); }}InboxAlreadyExistsError
Section titled “InboxAlreadyExistsError”Thrown when attempting to import an inbox that already exists in the client.
class InboxAlreadyExistsError extends VaultSandboxError { message: string;}Example
Section titled “Example”import { InboxAlreadyExistsError } from '@vaultsandbox/client';
try { const inbox = await client.importInbox(exportedData);} catch (error) { if (error instanceof InboxAlreadyExistsError) { console.error('Inbox already imported in this client'); console.error('Use a new client instance or delete the existing inbox'); }}InvalidImportDataError
Section titled “InvalidImportDataError”Thrown when imported inbox data fails validation. The SDK validates:
- Version: Export format version must be supported (currently version 1)
- Required fields: All fields must be present and non-empty
- Email format: Must contain exactly one
@character - Key encoding: Keys must be valid base64url
- Key sizes:
secretKeymust decode to 2400 bytes,serverSigPkmust decode to 1952 bytes - Server match: Server public key must match the connected server
class InvalidImportDataError extends VaultSandboxError { message: string;}Example
Section titled “Example”import { InvalidImportDataError } from '@vaultsandbox/client';
try { const corruptedData = JSON.parse(corruptedJson); const inbox = await client.importInbox(corruptedData);} catch (error) { if (error instanceof InvalidImportDataError) { console.error('Invalid import data:', error.message); // Common causes: // - "Unsupported version: X, expected 1" // - "Missing or invalid field: secretKey" // - "Invalid email address: must contain exactly one @ character" // - "Invalid base64url encoding in secret key" // - "Invalid server public key size: expected 1952, got X" }}DecryptionError
Section titled “DecryptionError”Thrown if the client fails to decrypt an email. The SDK validates the encrypted payload before decryption:
- Protocol version: Must match expected version (currently 1)
- Algorithm suite: Must use ML-KEM-768, ML-DSA-65, AES-256-GCM, HKDF-SHA-512
- Component sizes: ct_kem (1088 bytes), nonce (12 bytes), signature (3309 bytes), server_sig_pk (1952 bytes)
class DecryptionError extends VaultSandboxError { message: string;}Example
Section titled “Example”import { DecryptionError } from '@vaultsandbox/client';
try { const emails = await inbox.listEmails();} catch (error) { if (error instanceof DecryptionError) { console.error('Failed to decrypt email:', error.message); // Common causes: // - "Unsupported protocol version: X, expected 1" // - "Unsupported KEM algorithm: X" // - "Invalid ct_kem size: expected 1088, got X" // - Actual decryption failures (rare)
// Log for investigation console.error('Inbox:', inbox.emailAddress); console.error('Time:', new Date().toISOString()); }}Handling
Section titled “Handling”Decryption errors should always be logged and investigated as they may indicate:
- Unsupported protocol version or algorithms
- Invalid cryptographic component sizes
- Data corruption
- SDK bug
- MITM attack (rare)
- Server-side encryption issue
SignatureVerificationError
Section titled “SignatureVerificationError”Thrown if the cryptographic signature of a message from the server cannot be verified. This is a critical security error that may indicate a man-in-the-middle (MITM) attack.
class SignatureVerificationError extends VaultSandboxError { message: string;}Example
Section titled “Example”import { SignatureVerificationError } from '@vaultsandbox/client';
try { const inbox = await client.createInbox();} catch (error) { if (error instanceof SignatureVerificationError) { console.error('CRITICAL: Signature verification failed!'); console.error('This may indicate a MITM attack'); console.error('Message:', error.message);
// Alert security team alertSecurityTeam({ error: error.message, timestamp: new Date().toISOString(), serverUrl: client.config.url, });
throw error; // Do not continue }}Handling
Section titled “Handling”Signature verification errors should never be ignored:
- Log immediately with full context
- Alert security/operations team
- Stop processing - do not continue with the operation
- Investigate - check for network issues, proxy problems, or actual attacks
SSEError
Section titled “SSEError”Thrown for errors related to the Server-Sent Events (SSE) connection.
class SSEError extends VaultSandboxError { message: string;}Example
Section titled “Example”import { SSEError } from '@vaultsandbox/client';
const client = new VaultSandboxClient({ url: process.env.VAULTSANDBOX_URL, apiKey: process.env.VAULTSANDBOX_API_KEY, strategy: 'sse',});
try { const inbox = await client.createInbox(); const subscription = inbox.onNewEmail((email) => { console.log('New email:', email.subject); });} catch (error) { if (error instanceof SSEError) { console.error('SSE connection error:', error.message); console.error('Falling back to polling strategy');
// Recreate client with polling const pollingClient = new VaultSandboxClient({ url: process.env.VAULTSANDBOX_URL, apiKey: process.env.VAULTSANDBOX_API_KEY, strategy: 'polling', }); }}StrategyError
Section titled “StrategyError”Thrown when a delivery strategy is not set or is invalid.
class StrategyError extends VaultSandboxError { message: string;}Example
Section titled “Example”import { StrategyError } from '@vaultsandbox/client';
try { const inbox = await client.createInbox(); const subscription = inbox.onNewEmail((email) => { console.log('New email:', email.subject); });} catch (error) { if (error instanceof StrategyError) { console.error('Strategy error:', error.message); console.error('This may indicate the delivery strategy is not properly configured'); }}Error Handling Patterns
Section titled “Error Handling Patterns”Basic Error Handling
Section titled “Basic Error Handling”import { VaultSandboxClient, ApiError, TimeoutError, NetworkError, VaultSandboxError } from '@vaultsandbox/client';
const client = new VaultSandboxClient({ url, apiKey });
try { const inbox = await client.createInbox(); console.log(`Send email to: ${inbox.emailAddress}`);
const email = await inbox.waitForEmail({ timeout: 10000 }); console.log('Email received:', email.subject);
await inbox.delete();} catch (error) { if (error instanceof TimeoutError) { console.error('Timed out waiting for email'); } else if (error instanceof ApiError) { console.error(`API Error (${error.statusCode}):`, error.message); } else if (error instanceof NetworkError) { console.error('Network error:', error.message); } else if (error instanceof VaultSandboxError) { console.error('VaultSandbox error:', error.message); } else { console.error('Unexpected error:', error); }}Retry with Custom Logic
Section titled “Retry with Custom Logic”async function waitForEmailWithRetry(inbox, options, maxAttempts = 3) { let lastError;
for (let attempt = 1; attempt <= maxAttempts; attempt++) { try { return await inbox.waitForEmail(options); } catch (error) { lastError = error;
if (error instanceof TimeoutError) { console.log(`Attempt ${attempt}/${maxAttempts} timed out`);
if (attempt < maxAttempts) { console.log('Retrying...'); await new Promise((resolve) => setTimeout(resolve, 2000)); } } else { // Non-timeout error, don't retry throw error; } } }
throw lastError;}
// Usagetry { const email = await waitForEmailWithRetry(inbox, { timeout: 10000, subject: /Welcome/ }, 3); console.log('Email received:', email.subject);} catch (error) { console.error('Failed after retries:', error.message);}Graceful Degradation
Section titled “Graceful Degradation”async function getEmailsWithFallback(inbox) { try { // Try to wait for new email return [await inbox.waitForEmail({ timeout: 5000 })]; } catch (error) { if (error instanceof TimeoutError) { console.log('No new emails, checking existing...'); // Fall back to listing existing emails return await inbox.listEmails(); } throw error; }}Test Cleanup with Error Handling
Section titled “Test Cleanup with Error Handling”describe('Email Tests', () => { let client; let inbox;
beforeEach(async () => { client = new VaultSandboxClient({ url, apiKey }); inbox = await client.createInbox(); });
afterEach(async () => { // Always clean up, even if test failed if (inbox) { try { await inbox.delete(); } catch (error) { if (error instanceof ApiError && error.statusCode === 404) { // Inbox already deleted, that's fine console.log('Inbox already deleted'); } else { // Log but don't fail the test console.error('Failed to delete inbox:', error.message); } } } });
test('should receive email', async () => { await sendEmail(inbox.emailAddress);
const email = await inbox.waitForEmail({ timeout: 10000, subject: /Test/, });
expect(email.subject).toContain('Test'); });});Best Practices
Section titled “Best Practices”1. Always Handle TimeoutError
Section titled “1. Always Handle TimeoutError”Timeouts are common in email testing. Always handle them explicitly:
try { const email = await inbox.waitForEmail({ timeout: 10000 });} catch (error) { if (error instanceof TimeoutError) { // List what emails did arrive const emails = await inbox.listEmails(); console.log(`Expected email not found. Received ${emails.length} emails:`); emails.forEach((e) => console.log(` - "${e.subject}" from ${e.from}`)); } throw error;}2. Log Critical Errors
Section titled “2. Log Critical Errors”Always log signature verification and decryption errors:
try { const inbox = await client.createInbox();} catch (error) { if (error instanceof SignatureVerificationError || error instanceof DecryptionError) { // Critical security/integrity error logger.critical({ error: error.message, type: error.constructor.name, timestamp: new Date().toISOString(), context: { serverUrl: client.config.url }, });
// Alert operations team alertOps(error);
throw error; }}3. Use Specific Error Types
Section titled “3. Use Specific Error Types”Catch specific errors before generic ones:
// Good: Specific to generaltry { // ...} catch (error) { if (error instanceof ApiError && error.statusCode === 404) { // Handle not found case (inbox or email) } else if (error instanceof ApiError) { // Handle other API errors } else if (error instanceof TimeoutError) { // Handle timeout case } else if (error instanceof VaultSandboxError) { // Handle any other SDK error } else { // Handle unexpected errors }}
// Avoid: Too generictry { // ...} catch (error) { if (error instanceof VaultSandboxError) { // Can't differentiate between error types }}4. Clean Up Resources
Section titled “4. Clean Up Resources”Always clean up, even when errors occur:
const client = new VaultSandboxClient({ url, apiKey });
try { const inbox = await client.createInbox(); // Use inbox...} catch (error) { console.error('Error:', error.message); throw error;} finally { await client.close();}Next Steps
Section titled “Next Steps”- CI/CD Integration - Error handling in CI
- VaultSandboxClient API - Client configuration