Skip to content

Inboxes

Inboxes are the core concept in VaultSandbox. Each inbox is an isolated, encrypted email destination with its own unique address and encryption keys.

An inbox is a temporary, encrypted email destination that:

  • Has a unique email address (e.g., [email protected])
  • Uses client-side encryption (ML-KEM-768 keypair)
  • Expires automatically after a configurable time-to-live (TTL)
  • Is isolated from other inboxes
  • Stores emails in memory on the gateway
from vaultsandbox import VaultSandboxClient
async with VaultSandboxClient(base_url=url, api_key=api_key) as client:
inbox = await client.create_inbox()
print(inbox.email_address) # "[email protected]"
print(inbox.inbox_hash) # "a1b2c3d4"
print(inbox.expires_at) # datetime object
from vaultsandbox import VaultSandboxClient, CreateInboxOptions
async with VaultSandboxClient(base_url=url, api_key=api_key) as client:
inbox = await client.create_inbox(
CreateInboxOptions(
ttl=3600, # 1 hour (default: 24 hours)
email_address="[email protected]", # Request specific address
)
)

Note: Requesting a specific email address may fail if it’s already in use. The server will return an error.

Type: str

The full email address for this inbox.

print(inbox.email_address)

Send emails to this address to have them appear in the inbox.

Type: str

A unique cryptographic hash identifier for the inbox. This is used internally for encryption and identification purposes.

print(inbox.inbox_hash)
# "Rr02MLnP7F0pRVC6QdcpSIeyklqu3PDkYglvsfN7Oss"

Note: This is not the same as the local part of the email address. The email address local part (e.g., a1b2c3d4 in [email protected]) is different from the inbox_hash.

Type: datetime

When the inbox will automatically expire and be deleted.

from datetime import datetime, timezone
print(inbox.expires_at)
# datetime(2024, 1, 16, 12, 0, 0, tzinfo=timezone.utc)
# Check if inbox is expiring soon
hours_until_expiry = (inbox.expires_at - datetime.now(timezone.utc)).total_seconds() / 3600
print(f"Expires in {hours_until_expiry:.1f} hours")

Type: str

The server’s signing public key (ML-DSA-65) used to verify email signatures.

print(inbox.server_sig_pk)
# Base64URL-encoded public key string
┌─────────────────────────────────────────────────────────┐
│ Inbox Lifecycle │
└─────────────────────────────────────────────────────────┘
1. Creation
client.create_inbox() → Inbox object
- Keypair generated client-side
- Public key sent to server
- Unique email address assigned
- TTL timer starts
2. Active
- Receive emails
- List/read emails
- Wait for emails
- Monitor for new emails
3. Expiration (TTL reached) or Manual Deletion
inbox.delete() or TTL expires
- All emails deleted
- Inbox address freed
- Keypair destroyed
# Get all emails with full content
emails = await inbox.list_emails()
print(f"{len(emails)} emails in inbox")
for email in emails:
print(f"{email.from_address}: {email.subject}")

For better performance when you only need basic info, use metadata-only listing:

# Get only metadata (more efficient)
metadata_list = await inbox.list_emails_metadata_only()
for meta in metadata_list:
print(f"{meta.from_address}: {meta.subject}")
# Fetch full content only if needed
if "important" in meta.subject.lower():
full_email = await inbox.get_email(meta.id)
email = await inbox.get_email("email-id-123")
print(email.subject)
print(email.text)
from vaultsandbox import WaitForEmailOptions
import re
# Wait for any email
email = await inbox.wait_for_email(WaitForEmailOptions(timeout=30000))
# Wait for specific email
email = await inbox.wait_for_email(
WaitForEmailOptions(
timeout=30000,
subject=re.compile(r"Password Reset"),
from_address="[email protected]",
)
)
# Delete specific email via inbox
await inbox.delete_email("email-id-123")
# Or via email object
await email.delete()
# Delete inbox and all its emails
await inbox.delete()

Each inbox is completely isolated:

inbox1 = await client.create_inbox()
inbox2 = await client.create_inbox()
# inbox1 cannot access inbox2's emails
# inbox2 cannot access inbox1's emails
# Each has its own:
# - Email address
# - Encryption keys
# - Email storage
# - Expiration time

Inboxes automatically expire after their TTL:

# Uses server's DEFAULT_INBOX_TTL (typically 24 hours)
inbox = await client.create_inbox()
from vaultsandbox import CreateInboxOptions
# Expire after 1 hour
inbox = await client.create_inbox(CreateInboxOptions(ttl=3600))
# Expire after 10 minutes (useful for quick tests)
inbox = await client.create_inbox(CreateInboxOptions(ttl=600))
# Expire after 7 days
inbox = await client.create_inbox(CreateInboxOptions(ttl=604800))
from datetime import datetime, timezone
minutes_left = (inbox.expires_at - datetime.now(timezone.utc)).total_seconds() / 60
if minutes_left < 5:
print("Inbox expiring soon!")

Inboxes can be exported and imported for:

  • Test reproducibility
  • Sharing between environments
  • Backup and restore
import json
export_data = inbox.export()
# Save to file
with open("inbox.json", "w") as f:
json.dump(export_data.__dict__, f)
import json
with open("inbox.json") as f:
export_data = json.load(f)
inbox = await client.import_inbox(export_data)
# Inbox restored with all encryption keys

Security Warning: Exported data contains private keys. Treat as sensitive.

Short TTL for fast cleanup:

inbox = await client.create_inbox(CreateInboxOptions(ttl=3600)) # 1 hour

Always clean up:

inbox = await client.create_inbox()
try:
# Run tests
pass
finally:
await inbox.delete()

Longer TTL for convenience:

inbox = await client.create_inbox(CreateInboxOptions(ttl=86400)) # 24 hours

Export for reuse:

# Export after creating
export_data = inbox.export()
with open("test-inbox.json", "w") as f:
json.dump(export_data.__dict__, f)
# Reuse in later sessions
inbox = await client.import_inbox_from_file("test-inbox.json")

Monitor expiration:

import asyncio
from datetime import datetime, timezone
async def monitor_expiration(inbox):
while True:
minutes_left = (inbox.expires_at - datetime.now(timezone.utc)).total_seconds() / 60
if minutes_left < 10:
print(f"Inbox {inbox.email_address} expiring in {minutes_left:.1f} minutes")
await asyncio.sleep(60) # Check every minute
import pytest
from vaultsandbox import VaultSandboxClient, CreateInboxOptions
@pytest.fixture
async def test_inbox(client):
inbox = await client.create_inbox(CreateInboxOptions(ttl=7200)) # 2 hours
yield inbox
await inbox.delete()
@pytest.mark.asyncio
async def test_password_reset(test_inbox):
await trigger_password_reset(test_inbox.email_address)
email = await test_inbox.wait_for_email(WaitForEmailOptions(timeout=10000))
# ...
user1_inbox = await client.create_inbox()
user2_inbox = await client.create_inbox()
admin_inbox = await client.create_inbox()
# Each inbox receives emails independently
await send_welcome_email(user1_inbox.email_address)
await send_welcome_email(user2_inbox.email_address)
await send_admin_report(admin_inbox.email_address)
class InboxPool:
def __init__(self, client, size=5):
self.client = client
self.pool = []
self.size = size
async def initialize(self):
for _ in range(self.size):
inbox = await self.client.create_inbox()
self.pool.append(inbox)
def get(self):
return self.pool.pop(0) if self.pool else None
async def cleanup(self):
await asyncio.gather(*[inbox.delete() for inbox in self.pool])

Check:

  1. Email is sent to correct address
  2. Inbox hasn’t expired
  3. DNS/MX records configured correctly
  4. SMTP connection successful
# Verify inbox still exists
try:
emails = await inbox.list_emails() # Will error if inbox expired
except InboxNotFoundError:
print("Inbox has expired or been deleted")

When requesting a specific email address:

from vaultsandbox.errors import InboxAlreadyExistsError
try:
inbox = await client.create_inbox(
CreateInboxOptions(email_address="[email protected]")
)
except InboxAlreadyExistsError:
# Address already in use, generate random instead
inbox = await client.create_inbox()
from vaultsandbox.errors import InboxNotFoundError
try:
emails = await inbox.list_emails()
except InboxNotFoundError:
print("Inbox has expired")
# Create new inbox
new_inbox = await client.create_inbox()