Inboxes
Inboxes are the core concept in VaultSandbox. Each inbox is an isolated, encrypted email destination with its own unique address and encryption keys.
What is an Inbox?
Section titled “What is an Inbox?”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
Creating Inboxes
Section titled “Creating Inboxes”Basic Creation
Section titled “Basic Creation”from vaultsandbox import VaultSandboxClient
async with VaultSandboxClient(base_url=url, api_key=api_key) as client: inbox = await client.create_inbox()
print(inbox.inbox_hash) # "a1b2c3d4" print(inbox.expires_at) # datetime objectWith Options
Section titled “With Options”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) ) )Note: Requesting a specific email address may fail if it’s already in use. The server will return an error.
Inbox Properties
Section titled “Inbox Properties”email_address
Section titled “email_address”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.
inbox_hash
Section titled “inbox_hash”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.
expires_at
Section titled “expires_at”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 soonhours_until_expiry = (inbox.expires_at - datetime.now(timezone.utc)).total_seconds() / 3600print(f"Expires in {hours_until_expiry:.1f} hours")server_sig_pk
Section titled “server_sig_pk”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 stringInbox Lifecycle
Section titled “Inbox Lifecycle”┌─────────────────────────────────────────────────────────┐│ 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 destroyedWorking with Inboxes
Section titled “Working with Inboxes”Listing Emails
Section titled “Listing Emails”# Get all emails with full contentemails = 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)Getting a Specific Email
Section titled “Getting a Specific Email”email = await inbox.get_email("email-id-123")
print(email.subject)print(email.text)Waiting for Emails
Section titled “Waiting for Emails”from vaultsandbox import WaitForEmailOptionsimport re
# Wait for any emailemail = await inbox.wait_for_email(WaitForEmailOptions(timeout=30000))
# Wait for specific emailemail = await inbox.wait_for_email( WaitForEmailOptions( timeout=30000, subject=re.compile(r"Password Reset"), ))Deleting Emails
Section titled “Deleting Emails”# Delete specific email via inboxawait inbox.delete_email("email-id-123")
# Or via email objectawait email.delete()Deleting Inbox
Section titled “Deleting Inbox”# Delete inbox and all its emailsawait inbox.delete()Inbox Isolation
Section titled “Inbox Isolation”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 timeTime-to-Live (TTL)
Section titled “Time-to-Live (TTL)”Inboxes automatically expire after their TTL:
Default TTL
Section titled “Default TTL”# Uses server's DEFAULT_INBOX_TTL (typically 24 hours)inbox = await client.create_inbox()Custom TTL
Section titled “Custom TTL”from vaultsandbox import CreateInboxOptions
# Expire after 1 hourinbox = 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 daysinbox = await client.create_inbox(CreateInboxOptions(ttl=604800))Checking Expiration
Section titled “Checking Expiration”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!")Import and Export
Section titled “Import and Export”Inboxes can be exported and imported for:
- Test reproducibility
- Sharing between environments
- Backup and restore
Export
Section titled “Export”import json
export_data = inbox.export()
# Save to filewith open("inbox.json", "w") as f: json.dump(export_data.__dict__, f)Import
Section titled “Import”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 keysSecurity Warning: Exported data contains private keys. Treat as sensitive.
Best Practices
Section titled “Best Practices”CI/CD Pipelines
Section titled “CI/CD Pipelines”Short TTL for fast cleanup:
inbox = await client.create_inbox(CreateInboxOptions(ttl=3600)) # 1 hourAlways clean up:
inbox = await client.create_inbox()try: # Run tests passfinally: await inbox.delete()Manual Testing
Section titled “Manual Testing”Longer TTL for convenience:
inbox = await client.create_inbox(CreateInboxOptions(ttl=86400)) # 24 hoursExport for reuse:
# Export after creatingexport_data = inbox.export()with open("test-inbox.json", "w") as f: json.dump(export_data.__dict__, f)
# Reuse in later sessionsinbox = await client.import_inbox_from_file("test-inbox.json")Production Monitoring
Section titled “Production Monitoring”Monitor expiration:
import asynciofrom 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 minuteCommon Patterns
Section titled “Common Patterns”Dedicated Test Inbox (pytest)
Section titled “Dedicated Test Inbox (pytest)”import pytestfrom vaultsandbox import VaultSandboxClient, CreateInboxOptions
@pytest.fixtureasync def test_inbox(client): inbox = await client.create_inbox(CreateInboxOptions(ttl=7200)) # 2 hours yield inbox await inbox.delete()
@pytest.mark.asyncioasync def test_password_reset(test_inbox): await trigger_password_reset(test_inbox.email_address) email = await test_inbox.wait_for_email(WaitForEmailOptions(timeout=10000)) # ...Multiple Inboxes
Section titled “Multiple Inboxes”user1_inbox = await client.create_inbox()user2_inbox = await client.create_inbox()admin_inbox = await client.create_inbox()
# Each inbox receives emails independentlyawait send_welcome_email(user1_inbox.email_address)await send_welcome_email(user2_inbox.email_address)await send_admin_report(admin_inbox.email_address)Inbox Pool
Section titled “Inbox Pool”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])Troubleshooting
Section titled “Troubleshooting”Inbox Not Receiving Emails
Section titled “Inbox Not Receiving Emails”Check:
- Email is sent to correct address
- Inbox hasn’t expired
- DNS/MX records configured correctly
- SMTP connection successful
# Verify inbox still existstry: emails = await inbox.list_emails() # Will error if inbox expiredexcept InboxNotFoundError: print("Inbox has expired or been deleted")Inbox Already Exists Error
Section titled “Inbox Already Exists Error”When requesting a specific email address:
from vaultsandbox.errors import InboxAlreadyExistsError
try: inbox = await client.create_inbox( )except InboxAlreadyExistsError: # Address already in use, generate random instead inbox = await client.create_inbox()Inbox Expired
Section titled “Inbox Expired”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()Next Steps
Section titled “Next Steps”- Email Objects - Learn about email structure
- Managing Inboxes - Common inbox operations
- Import/Export - Advanced inbox persistence
- API Reference: Inbox - Complete API documentation