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 except block.
VaultSandboxError (base class)├── ApiError├── NetworkError├── TimeoutError├── InboxNotFoundError├── EmailNotFoundError├── InboxAlreadyExistsError├── InvalidImportDataError├── DecryptionError├── SignatureVerificationError├── UnsupportedVersionError├── InvalidPayloadError├── InvalidAlgorithmError├── InvalidSizeError├── ServerKeyMismatchError├── 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:
from vaultsandbox import VaultSandboxClient
async with VaultSandboxClient( api_key="your-api-key", base_url="https://smtp.vaultsandbox.com", max_retries=5, # Default: 3 retry_delay=2000, # Default: 1000ms retry_on_status_codes=(408, 429, 500, 502, 503, 504), # Default) as client: passRetry Strategy
Section titled “Retry Strategy”The SDK uses exponential backoff for retries:
- 1st retry:
retry_delayms - 2nd retry:
retry_delay * 2ms - 3rd retry:
retry_delay * 4ms - And so on…
Example
Section titled “Example”# With retry_delay=1000 and max_retries=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(Exception): """Base exception for all VaultSandbox SDK errors.""" passExample
Section titled “Example”from vaultsandbox import VaultSandboxError
try: inbox = await client.create_inbox() # Use inbox...except VaultSandboxError as e: print(f"VaultSandbox error: {e}")except Exception as e: print(f"Unexpected error: {e}")ApiError
Section titled “ApiError”Thrown for API-level errors such as invalid requests or permission denied.
class ApiError(VaultSandboxError): status_code: int message: strProperties
Section titled “Properties”status_code: HTTP status code from the APImessage: Error message from the server
Example
Section titled “Example”from vaultsandbox import ApiError
try: inbox = await client.create_inbox()except ApiError as e: print(f"API Error ({e.status_code}): {e.message}")
if e.status_code == 401: print("Invalid API key") elif e.status_code == 403: print("Permission denied") elif e.status_code == 429: print("Rate limit exceeded")NetworkError
Section titled “NetworkError”Thrown when there is a network-level failure (e.g., cannot connect to server).
class NetworkError(VaultSandboxError): passExample
Section titled “Example”from vaultsandbox import NetworkError
try: inbox = await client.create_inbox()except NetworkError as e: print(f"Network error: {e}") print("Check your internet connection and server URL")TimeoutError
Section titled “TimeoutError”Thrown by methods like wait_for_email() and wait_for_email_count() when the timeout is reached before the condition is met.
class TimeoutError(VaultSandboxError): passExample
Section titled “Example”from vaultsandbox import TimeoutError, WaitForEmailOptionsimport re
try: email = await inbox.wait_for_email( WaitForEmailOptions( timeout=5000, subject=re.compile(r"Welcome"), ) )except TimeoutError: print("Timed out waiting for email") print("Email may not have been sent or took too long to deliver")
# Check what emails did arrive emails = await inbox.list_emails() print(f"Found {len(emails)} emails:") for e in emails: print(f" - {e.subject}")InboxNotFoundError
Section titled “InboxNotFoundError”Thrown when an operation targets an inbox that does not exist (HTTP 404).
class InboxNotFoundError(VaultSandboxError): passExample
Section titled “Example”from vaultsandbox import InboxNotFoundError, ApiError
try: emails = await inbox.list_emails()except InboxNotFoundError: print("Inbox no longer exists") print("It may have expired or been deleted")except ApiError as e: if e.status_code == 404: print("Inbox not found")EmailNotFoundError
Section titled “EmailNotFoundError”Thrown when an operation targets an email that does not exist (HTTP 404).
class EmailNotFoundError(VaultSandboxError): passExample
Section titled “Example”from vaultsandbox import EmailNotFoundError, ApiError
try: email = await inbox.get_email("non-existent-id")except EmailNotFoundError: print("Email not found") print("It may have been deleted")except ApiError as e: if e.status_code == 404: print("Email not found")InboxAlreadyExistsError
Section titled “InboxAlreadyExistsError”Thrown when attempting to import an inbox that already exists in the client.
class InboxAlreadyExistsError(VaultSandboxError): passExample
Section titled “Example”from vaultsandbox import InboxAlreadyExistsError
try: inbox = await client.import_inbox(exported_data)except InboxAlreadyExistsError: print("Inbox already imported in this client") print("Use a new client instance or delete the existing inbox")InvalidImportDataError
Section titled “InvalidImportDataError”Thrown when imported inbox data fails validation (missing fields, invalid keys, server mismatch, etc.).
class InvalidImportDataError(VaultSandboxError): passExample
Section titled “Example”import jsonfrom vaultsandbox import InvalidImportDataError
try: corrupted_data = json.loads(corrupted_json) inbox = await client.import_inbox(corrupted_data)except InvalidImportDataError as e: print(f"Invalid import data: {e}") print("The exported data may be corrupted or from a different server")DecryptionError
Section titled “DecryptionError”Thrown if the client fails to decrypt an email. This is rare and may indicate data corruption or a bug.
class DecryptionError(VaultSandboxError): passExample
Section titled “Example”import loggingfrom vaultsandbox import DecryptionError
logger = logging.getLogger(__name__)
try: emails = await inbox.list_emails()except DecryptionError as e: print(f"Failed to decrypt email: {e}") print("This is a critical error - please report it")
# Log for investigation logger.critical( "Decryption failure", extra={ "inbox": inbox.email_address, "error": str(e), } )Handling
Section titled “Handling”Decryption errors should always be logged and investigated as they may indicate:
- 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(VaultSandboxError): passExample
Section titled “Example”import loggingfrom datetime import datetimefrom vaultsandbox import SignatureVerificationError
logger = logging.getLogger(__name__)
try: inbox = await client.create_inbox()except SignatureVerificationError as e: print("CRITICAL: Signature verification failed!") print("This may indicate a MITM attack") print(f"Message: {e}")
# Log immediately logger.critical( "Signature verification failed", extra={ "error": str(e), "timestamp": datetime.now().isoformat(), } )
# Alert security team alert_security_team(e)
raise # Do not continueHandling
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
UnsupportedVersionError
Section titled “UnsupportedVersionError”Thrown when the protocol version or export format version is not supported.
class UnsupportedVersionError(VaultSandboxError): passExample
Section titled “Example”from vaultsandbox import UnsupportedVersionError
try: inbox = await client.import_inbox_from_file("./old-export.json")except UnsupportedVersionError as e: print(f"Unsupported version: {e}") print("The export file was created with an incompatible SDK version")InvalidPayloadError
Section titled “InvalidPayloadError”Thrown when an encrypted payload has malformed JSON or is missing required fields.
class InvalidPayloadError(VaultSandboxError): passExample
Section titled “Example”from vaultsandbox import InvalidPayloadError
try: emails = await inbox.list_emails()except InvalidPayloadError as e: print(f"Invalid payload: {e}") print("The server response was malformed")InvalidAlgorithmError
Section titled “InvalidAlgorithmError”Thrown when an encrypted payload specifies an unrecognized or unsupported cryptographic algorithm.
class InvalidAlgorithmError(VaultSandboxError): passExample
Section titled “Example”from vaultsandbox import InvalidAlgorithmError
try: emails = await inbox.list_emails()except InvalidAlgorithmError as e: print(f"Invalid algorithm: {e}") print("The server is using an unsupported cryptographic algorithm")InvalidSizeError
Section titled “InvalidSizeError”Thrown when a decoded cryptographic field has an incorrect size (e.g., wrong key length).
class InvalidSizeError(VaultSandboxError): passExample
Section titled “Example”from vaultsandbox import InvalidSizeError
try: inbox = await client.import_inbox(exported)except InvalidSizeError as e: print(f"Invalid size: {e}") print("A cryptographic field has the wrong size - data may be corrupted")ServerKeyMismatchError
Section titled “ServerKeyMismatchError”Thrown when the server’s public key doesn’t match the pinned key from inbox creation. This is a critical security error that may indicate a man-in-the-middle (MITM) attack or server misconfiguration.
class ServerKeyMismatchError(VaultSandboxError): passExample
Section titled “Example”import loggingfrom vaultsandbox import ServerKeyMismatchError
logger = logging.getLogger(__name__)
try: emails = await inbox.list_emails()except ServerKeyMismatchError as e: print("CRITICAL: Server key mismatch!") print("This may indicate a MITM attack or server misconfiguration")
logger.critical( "Server key mismatch detected", extra={"error": str(e)} )
raise # Do not continueHandling
Section titled “Handling”Server key mismatch errors should be treated similarly to signature verification errors:
- Log immediately with full context
- Alert security/operations team
- Stop processing - do not continue with the operation
- Investigate - verify server configuration and network integrity
SSEError
Section titled “SSEError”Thrown for errors related to the Server-Sent Events (SSE) connection.
class SSEError(VaultSandboxError): passExample
Section titled “Example”from vaultsandbox import VaultSandboxClient, SSEError, DeliveryStrategyType
try: async with VaultSandboxClient( api_key="your-api-key", strategy=DeliveryStrategyType.SSE, ) as client: inbox = await client.create_inbox() subscription = await inbox.on_new_email( lambda email: print(f"New email: {email.subject}") )except SSEError as e: print(f"SSE connection error: {e}") print("Falling back to polling strategy")
# Recreate client with polling async with VaultSandboxClient( api_key="your-api-key", strategy=DeliveryStrategyType.POLLING, ) as client: passStrategyError
Section titled “StrategyError”Thrown when a delivery strategy is not set or is invalid.
class StrategyError(VaultSandboxError): passExample
Section titled “Example”from vaultsandbox import StrategyError
try: inbox = await client.create_inbox() subscription = await inbox.on_new_email( lambda email: print(f"New email: {email.subject}") )except StrategyError as e: print(f"Strategy error: {e}") print("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”from vaultsandbox import ( VaultSandboxClient, ApiError, TimeoutError, NetworkError, VaultSandboxError,)
async with VaultSandboxClient(api_key=api_key) as client: try: inbox = await client.create_inbox() print(f"Send email to: {inbox.email_address}")
email = await inbox.wait_for_email() print(f"Email received: {email.subject}")
await inbox.delete() except TimeoutError: print("Timed out waiting for email") except ApiError as e: print(f"API Error ({e.status_code}): {e.message}") except NetworkError as e: print(f"Network error: {e}") except VaultSandboxError as e: print(f"VaultSandbox error: {e}") except Exception as e: print(f"Unexpected error: {e}")Retry with Custom Logic
Section titled “Retry with Custom Logic”from vaultsandbox import TimeoutError, WaitForEmailOptionsimport asyncio
async def wait_for_email_with_retry(inbox, options, max_attempts=3): last_error = None
for attempt in range(1, max_attempts + 1): try: return await inbox.wait_for_email(options) except TimeoutError as e: last_error = e print(f"Attempt {attempt}/{max_attempts} timed out")
if attempt < max_attempts: print("Retrying...") await asyncio.sleep(2) except Exception: # Non-timeout error, don't retry raise
raise last_error
# Usagetry: email = await wait_for_email_with_retry( inbox, WaitForEmailOptions(timeout=10000, subject="Welcome"), max_attempts=3, ) print(f"Email received: {email.subject}")except TimeoutError: print("Failed after retries")Graceful Degradation
Section titled “Graceful Degradation”from vaultsandbox import TimeoutError, WaitForEmailOptions
async def get_emails_with_fallback(inbox): try: # Try to wait for new email return [await inbox.wait_for_email( WaitForEmailOptions(timeout=5000) )] except TimeoutError: print("No new emails, checking existing...") # Fall back to listing existing emails return await inbox.list_emails()pytest Cleanup with Error Handling
Section titled “pytest Cleanup with Error Handling”import pytestfrom vaultsandbox import VaultSandboxClient, ApiError
@pytest.fixtureasync def client(): async with VaultSandboxClient( api_key="your-api-key", ) as client: yield client
@pytest.fixtureasync def inbox(client): inbox = await client.create_inbox() yield inbox
# Always clean up, even if test failed try: await inbox.delete() except ApiError as e: if e.status_code == 404: # Inbox already deleted, that's fine print("Inbox already deleted") else: # Log but don't fail the test print(f"Failed to delete inbox: {e.message}")
@pytest.mark.asyncioasync def test_should_receive_email(inbox): # Send email to inbox.email_address...
email = await inbox.wait_for_email() assert "Test" in email.subjectBest 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:
from vaultsandbox import TimeoutError, WaitForEmailOptions
try: email = await inbox.wait_for_email( WaitForEmailOptions(timeout=10000) )except TimeoutError: # List what emails did arrive emails = await inbox.list_emails() print(f"Expected email not found. Received {len(emails)} emails:") for e in emails: print(f" - \"{e.subject}\" from {e.from_address}") raise2. Log Critical Errors
Section titled “2. Log Critical Errors”Always log signature verification and decryption errors:
import loggingfrom datetime import datetimefrom vaultsandbox import SignatureVerificationError, DecryptionError
logger = logging.getLogger(__name__)
try: inbox = await client.create_inbox()except (SignatureVerificationError, DecryptionError) as e: # Critical security/integrity error logger.critical( "Critical security error", extra={ "error": str(e), "error_type": type(e).__name__, "timestamp": datetime.now().isoformat(), } )
# Alert operations team alert_ops(e)
raise3. Use Specific Error Types
Section titled “3. Use Specific Error Types”Catch specific errors before generic ones:
# Good: Specific to generaltry: # ... passexcept ApiError as e: if e.status_code == 404: # Handle not found case (inbox or email) pass else: # Handle other API errors passexcept TimeoutError: # Handle timeout case passexcept VaultSandboxError: # Handle any other SDK error passexcept Exception: # Handle unexpected errors pass
# Avoid: Too generictry: # ... passexcept VaultSandboxError: # Can't differentiate between error types pass4. Clean Up Resources
Section titled “4. Clean Up Resources”Always clean up, even when errors occur:
async with VaultSandboxClient(api_key=api_key) as client: try: inbox = await client.create_inbox() # Use inbox... except Exception as e: print(f"Error: {e}") raise# Client automatically closed via context managerOr with manual cleanup:
client = VaultSandboxClient(api_key=api_key)try: inbox = await client.create_inbox() # Use inbox...except Exception as e: print(f"Error: {e}") raisefinally: await client.close()Next Steps
Section titled “Next Steps”- CI/CD Integration - Error handling in CI
- VaultSandboxClient API - Client configuration