Skip to main content

Python SDK for VaultSandbox - Secure, receive-only SMTP server for QA/testing with quantum-safe encryption

Project description

VaultSandbox

VaultSandbox is in Public Beta. Join the journey to 1.0. Share feedback on GitHub.

@vaultsandbox/client-python

PyPI version CI License: Apache 2.0 Python

Production-like email testing. Self-hosted & secure.

The official Python SDK for VaultSandbox Gateway — a secure, receive-only SMTP server for QA/testing environments. This SDK abstracts quantum-safe encryption complexity, making email testing workflows transparent and effortless.

Stop mocking your email stack. If your app sends real emails in production, it must send real emails in testing. VaultSandbox provides isolated inboxes that behave exactly like production without exposing a single byte of customer data.

Python 3.10+ required.

Why VaultSandbox?

Feature Simple Mocks Public SaaS VaultSandbox
TLS/SSL Ignored/Disabled Partial Real ACME certs
Data Privacy Local only Shared cloud Private VPC
Inbound Mail Outbound only Yes Real MX
Auth (SPF/DKIM) None Limited Full Validation
Crypto Plaintext Varies Zero-Knowledge

Features

  • Quantum-Safe Encryption — Automatic ML-KEM-768 (Kyber768) key encapsulation + AES-256-GCM encryption
  • Zero Crypto Knowledge Required — All cryptographic operations are invisible to the user
  • Real-Time Email Delivery — SSE-based delivery with polling alternative
  • Built for CI/CD — Deterministic tests without sleeps, polling, or flakiness
  • Full Email Access — Decrypt and access email content, headers, links, and attachments
  • Email Authentication — Built-in SPF/DKIM/DMARC validation helpers
  • Type-Safe — Full type hints with py.typed marker for IDE support

Installation

pip install vaultsandbox

Quick Start

import asyncio
from vaultsandbox import VaultSandboxClient

async def main():
    async with VaultSandboxClient(api_key="your-api-key") as client:
        # Create a temporary inbox
        inbox = await client.create_inbox()
        print(f"Send emails to: {inbox.email_address}")

        # Wait for an email (with 30 second timeout)
        email = await inbox.wait_for_email()

        print(f"From: {email.from_address}")
        print(f"Subject: {email.subject}")
        print(f"Body: {email.text}")

        # Clean up
        await inbox.delete()

asyncio.run(main())

Configuration

Client Options

from vaultsandbox import VaultSandboxClient, DeliveryStrategyType

client = VaultSandboxClient(
    api_key="your-api-key",
    base_url="https://smtp.vaultsandbox.com",  # Default
    timeout=30000,                              # HTTP timeout in ms
    max_retries=3,                              # Retry attempts
    retry_delay=1000,                           # Initial retry delay in ms
    strategy=DeliveryStrategyType.SSE,          # SSE or POLLING
)

Environment Variables

Create a .env file:

VAULTSANDBOX_URL=https://smtp.vaultsandbox.com
VAULTSANDBOX_API_KEY=your-api-key-here

Load with python-dotenv:

from dotenv import load_dotenv
import os

load_dotenv()

client = VaultSandboxClient(
    api_key=os.getenv("VAULTSANDBOX_API_KEY"),
    base_url=os.getenv("VAULTSANDBOX_URL"),
)

Usage Examples

Create Inbox with Options

from vaultsandbox import CreateInboxOptions

# Custom TTL (time-to-live)
inbox = await client.create_inbox(
    CreateInboxOptions(ttl=3600)  # 1 hour
)

# Request specific email address prefix (server adds its domain)
inbox = await client.create_inbox(
    CreateInboxOptions(email_address="my-test-inbox")
)

Wait for Email with Filters

import re
from vaultsandbox import WaitForEmailOptions

# Wait for email with specific subject
email = await inbox.wait_for_email(
    WaitForEmailOptions(subject="Welcome")
)

# Wait for email from specific sender (regex)
email = await inbox.wait_for_email(
    WaitForEmailOptions(from_address=re.compile(r".*@company\.com"))
)

# Custom predicate
email = await inbox.wait_for_email(
    WaitForEmailOptions(
        predicate=lambda e: len(e.attachments) > 0,
        timeout=60000,  # 60 seconds
    )
)

Wait for Multiple Emails

from vaultsandbox import WaitForCountOptions

# Wait until inbox has at least 3 emails
emails = await inbox.wait_for_email_count(3, WaitForCountOptions(timeout=60000))

Subscribe to New Emails

async with VaultSandboxClient(api_key="your-api-key") as client:
    inbox = await client.create_inbox()

    async def handle_email(email):
        print(f"New email: {email.subject}")
        await email.mark_as_read()

    subscription = await inbox.on_new_email(handle_email)

    # Later, unsubscribe
    await inbox.unsubscribe(subscription)

Monitor Multiple Inboxes

inbox1 = await client.create_inbox()
inbox2 = await client.create_inbox()

def on_email(inbox, email):
    print(f"Email to {email.to}: {email.subject}")

monitor = client.monitor_inboxes([inbox1, inbox2])
monitor.on_email(on_email)
await monitor.start()

# ... wait for emails ...

await monitor.unsubscribe()

Email Content and Attachments

email = await inbox.wait_for_email()

# Text and HTML content
print(email.text)  # Plain text body
print(email.html)  # HTML body

# Headers
print(email.headers.get("message-id"))

# Attachments
for attachment in email.attachments:
    print(f"{attachment.filename}: {attachment.size} bytes")
    # attachment.content is bytes
    with open(attachment.filename, "wb") as f:
        f.write(attachment.content)

# Links found in the email
for link in email.links:
    print(link)

# Raw email source (MIME)
raw_email = await email.get_raw()
print(raw_email.id)   # Email ID
print(raw_email.raw)  # Raw MIME content

Email Authentication Results

email = await inbox.wait_for_email()

# Individual results
print(f"SPF: {email.auth_results.spf.result}")
print(f"DKIM: {[d.result for d in email.auth_results.dkim]}")
print(f"DMARC: {email.auth_results.dmarc.result}")

# Validate all at once
validation = email.auth_results.validate()
if validation.passed:
    print("All authentication checks passed")
else:
    print(f"Failures: {validation.failures}")

Export and Import Inboxes

Export an inbox to persist its keypair:

# Export to object
exported = client.export_inbox(inbox)

# Export to file
await client.export_inbox_to_file(inbox, "inbox_backup.json")

Import in a later session:

# Import from object
inbox = await client.import_inbox(exported)

# Import from file
inbox = await client.import_inbox_from_file("inbox_backup.json")

Security Warning: Exported data contains private keys. Store securely.

Delete Inboxes

# Delete single inbox
await inbox.delete()

# Delete all inboxes for API key
deleted_count = await client.delete_all_inboxes()

API Reference

VaultSandboxClient

The main client class for interacting with the VaultSandbox Gateway.

Constructor

VaultSandboxClient(
    api_key: str,
    base_url: str = "https://smtp.vaultsandbox.com",
    timeout: int = 30000,
    max_retries: int = 3,
    retry_delay: int = 1000,
    strategy: DeliveryStrategyType = DeliveryStrategyType.SSE,
)

Parameters:

  • api_key: str - Your API key
  • base_url: str - Gateway URL (default: 'https://smtp.vaultsandbox.com')
  • timeout: int - HTTP timeout in ms (default: 30000)
  • max_retries: int - Max retry attempts for HTTP requests (default: 3)
  • retry_delay: int - Delay in ms between retry attempts (default: 1000)
  • strategy: DeliveryStrategyType - Delivery strategy: SSE or POLLING (default: SSE)

Methods

Method Description
check_key() Validate the API key
get_server_info() Get server capabilities
create_inbox(options?) Create a temporary inbox
delete_all_inboxes() Delete all inboxes for API key
monitor_inboxes(inboxes) Monitor multiple inboxes
export_inbox(inbox) Export inbox data
import_inbox(data) Import inbox from data
export_inbox_to_file(inbox, path) Export inbox to JSON file
import_inbox_from_file(path) Import inbox from JSON file
close() Close client and release resources

Inbox Import/Export: For advanced use cases like test reproducibility or sharing inboxes between environments, you can export an inbox (including its encryption keys) to a JSON file and import it later. This allows you to persist inboxes across test runs or share them with other tools.

Inbox

Represents a single email inbox.

Properties

  • email_address: str - The inbox email address
  • inbox_hash: str - Unique inbox identifier
  • expires_at: datetime - When the inbox expires

Methods

Method Description
list_emails() List all emails
get_email(id) Get specific email
get_raw_email(id) Get raw MIME source (returns RawEmail)
wait_for_email(options?) Wait for matching email
wait_for_email_count(n, options?) Wait for N emails
on_new_email(callback) Subscribe to new emails
mark_email_as_read(id) Mark email as read
delete_email(id) Delete email
delete() Delete inbox
get_sync_status() Get email count and hash
export() Export inbox data

Email

Represents a decrypted email.

Properties

Property Type Description
id str Unique email ID
from_address str Sender address
to list[str] Recipient addresses
subject str Email subject
text str | None Plain text body
html str | None HTML body
attachments list[Attachment] File attachments
links list[str] URLs in email
headers dict Email headers
auth_results AuthResults SPF/DKIM/DMARC results
received_at datetime When received
is_read bool Read status

Methods

Method Description
mark_as_read() Mark as read
delete() Delete email
get_raw() Get raw MIME source (returns RawEmail)

AuthResults

Returned by email.auth_results, this object contains email authentication results (SPF, DKIM, DMARC) and a validation helper.

Properties

  • spf: SPFResult | None - SPF result
  • dkim: list[DKIMResult] | None - All DKIM results
  • dmarc: DMARCResult | None - DMARC result
  • reverse_dns: ReverseDNSResult | None - Reverse DNS result

Methods

  • validate() -> AuthResultsValidation - Validates all authentication results and returns a summary object with passed (bool), individual results (spf_passed, dkim_passed, dmarc_passed, reverse_dns_passed), and failures (list of failure descriptions).

CreateInboxOptions

Options for creating an inbox with client.create_inbox().

Properties:

  • ttl: int | None - Time-to-live for the inbox in seconds (default: server-defined).
  • email_address: str | None - A specific email address to request. If unavailable, the server will generate one.

WaitForEmailOptions

Options for waiting for emails with inbox.wait_for_email().

Properties:

  • timeout: int | None - Maximum time to wait in milliseconds (default: 30000)
  • poll_interval: int | None - Polling interval in milliseconds (default: 2000)
  • subject: str | Pattern | None - Filter emails by subject
  • from_address: str | Pattern | None - Filter emails by sender address
  • predicate: Callable[[Email], bool] | None - Custom filter function

WaitForCountOptions

Options for waiting for a specific number of emails with inbox.wait_for_email_count().

Properties:

  • timeout: int | None - Maximum time to wait in milliseconds (default: 30000)

RawEmail

Represents raw email content returned by email.get_raw() and inbox.get_raw_email().

Properties:

  • id: str - The email ID
  • raw: str - The raw MIME email content

Error Handling

The SDK is designed to be resilient and provide clear feedback when issues occur. It includes automatic retries for transient network and server errors, and raises specific, catchable errors for different failure scenarios.

All custom errors raised by the SDK extend from the base VaultSandboxError class, so you can catch all SDK-specific errors with a single except block if needed.

Automatic Retries

By default, the client automatically retries failed HTTP requests that result in one of the following status codes: 408, 429, 500, 502, 503, 504. This helps mitigate transient network or server-side issues.

The retry behavior can be configured via the VaultSandboxClient constructor:

  • max_retries: The maximum number of retry attempts (default: 3).
  • retry_delay: The base delay in milliseconds between retries (default: 1000). The delay uses exponential backoff.
  • retry_on: A list of HTTP status codes that should trigger a retry.

Custom Error Types

The following custom error classes may be raised:

  • ApiError: Raised for API-level errors (e.g., invalid request, permission denied). Includes a status_code property.
  • NetworkError: Raised when there is a network-level failure (e.g., the client cannot connect to the server).
  • TimeoutError: Raised by methods like wait_for_email and wait_for_email_count when the timeout is reached before the condition is met.
  • InboxNotFoundError: Raised when an operation targets an inbox that does not exist (HTTP 404).
  • EmailNotFoundError: Raised when an operation targets an email that does not exist (HTTP 404).
  • InboxAlreadyExistsError: Raised when attempting to import an inbox that already exists in the client.
  • InvalidImportDataError: Raised when imported inbox data fails validation (missing fields, invalid keys, server mismatch, etc.).
  • DecryptionError: Raised if the client fails to decrypt an email. This is rare and may indicate data corruption or a bug.
  • SignatureVerificationError: Raised if the cryptographic signature of a message from the server cannot be verified. This is a critical error that may indicate a man-in-the-middle (MITM) attack.
  • SSEError: Raised for errors related to the Server-Sent Events (SSE) connection.

Example

from vaultsandbox import (
    VaultSandboxError,
    ApiError,
    NetworkError,
    TimeoutError,
    InboxNotFoundError,
    EmailNotFoundError,
    DecryptionError,
    SignatureVerificationError,
)

try:
    email = await inbox.wait_for_email(
        WaitForEmailOptions(timeout=5000)
    )
except TimeoutError:
    print("No email received within timeout")
except SignatureVerificationError:
    print("CRITICAL: Email signature verification failed!")
except DecryptionError as e:
    print(f"Failed to decrypt email: {e}")
except ApiError as e:
    print(f"API error {e.status_code}: {e.message}")
except NetworkError:
    print("Network connection failed")
except VaultSandboxError as e:
    print(f"VaultSandbox error: {e}")

Requirements

  • Python >= 3.10 (tested on Python 3.10, 3.11, 3.12, and 3.13)
  • VaultSandbox Gateway server
  • Valid API key

Dependencies:

  • httpx - Async HTTP client
  • httpx-sse - Server-Sent Events support
  • pqcrypto - Post-quantum cryptography (ML-KEM-768, ML-DSA-65)
  • cryptography - AES-256-GCM and HKDF

Testing

# Run unit tests
pytest

# Run tests with coverage
pytest --cov=vaultsandbox

Building

# Build package
python -m build

# Clean build artifacts
rm -rf dist/ build/ *.egg-info

Architecture

The SDK is built on several layers:

  1. Crypto Layer: Handles ML-KEM-768 keypair generation, AES-256-GCM encryption/decryption, and ML-DSA-65 signature verification
  2. HTTP Layer: REST API client with automatic retry and error handling
  3. Domain Layer: Email, Inbox, and Client classes with intuitive APIs
  4. Strategy Layer: SSE and polling strategies for email delivery

All cryptographic operations are performed transparently - developers never need to handle keys, encryption, or signatures directly.

Security

  • Cryptography: ML-KEM-768 (Kyber768) for key encapsulation + AES-256-GCM for payload encryption, with HKDF-SHA-512 key derivation.
  • Signatures: ML-DSA-65 (Dilithium3) signatures are verified before any decryption using the gateway-provided transcript context (vaultsandbox:email:v1 today).
  • Threat model: protects confidentiality/integrity of gateway responses and detects tampering/mitm. Skipping signature verification defeats these guarantees.
  • Key handling: inbox keypairs stay in memory only; exported inbox data contains secrets and must be treated as sensitive.
  • Validation: signature verification failures raise SignatureVerificationError; decryption issues raise DecryptionError. Always surface these in logs/alerts for investigation.

Related

Support

Contributing

Contributions are welcome! Please read our contributing guidelines before submitting PRs.

License

Apache 2.0 — see LICENSE for details.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

vaultsandbox-0.8.0.tar.gz (107.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

vaultsandbox-0.8.0-py3-none-any.whl (61.3 kB view details)

Uploaded Python 3

File details

Details for the file vaultsandbox-0.8.0.tar.gz.

File metadata

  • Download URL: vaultsandbox-0.8.0.tar.gz
  • Upload date:
  • Size: 107.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for vaultsandbox-0.8.0.tar.gz
Algorithm Hash digest
SHA256 7dabcc3bb5c3dd2a0f0a33c2531e605f830c16b8942f82768645904f89262aeb
MD5 134876bbcaed0688991e41e2b8157a1d
BLAKE2b-256 1c76cb854a8bb71d99ed6d630d8ed4f672c44c9ab1c3e8b40fc7e26d64a37eb6

See more details on using hashes here.

File details

Details for the file vaultsandbox-0.8.0-py3-none-any.whl.

File metadata

  • Download URL: vaultsandbox-0.8.0-py3-none-any.whl
  • Upload date:
  • Size: 61.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for vaultsandbox-0.8.0-py3-none-any.whl
Algorithm Hash digest
SHA256 bac2f11d2b0dacc95b8a16f1cc42181e3191dfd7ebcc308e2e5674f061950568
MD5 a8bea9ad2659dd66e4252d3694755b47
BLAKE2b-256 591729f0e65f4362bb33038c1842ba11a5bb420c5b3939f7451efa0d370e131c

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page