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., a1b2c3d4@mail.example.com)
  • 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
package main
import (
"context"
"fmt"
"log"
vaultsandbox "github.com/vaultsandbox/client-go"
)
func main() {
client, err := vaultsandbox.New(apiKey)
if err != nil {
log.Fatal(err)
}
defer client.Close()
ctx := context.Background()
inbox, err := client.CreateInbox(ctx)
if err != nil {
log.Fatal(err)
}
fmt.Println(inbox.EmailAddress()) // "a1b2c3d4@mail.example.com"
fmt.Println(inbox.InboxHash()) // "Rr02MLnP7F0pRVC6QdcpSIeyklqu3PDkYglvsfN7Oss"
fmt.Println(inbox.ExpiresAt()) // time.Time
}
inbox, err := client.CreateInbox(ctx,
vaultsandbox.WithTTL(time.Hour), // 1 hour (default: 1 hour)
vaultsandbox.WithEmailAddress("test@mail.example.com"), // Request specific address
vaultsandbox.WithEmailAuth(false), // Disable email authentication
vaultsandbox.WithEncryption(vaultsandbox.EncryptionModePlain), // Request plain inbox
)

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

OptionTypeDescription
WithTTLtime.DurationTime-to-live for the inbox (min: 60s, max: 7 days, default: 1 hour)
WithEmailAddressstringRequest a specific email address
WithEmailAuthboolEnable/disable email authentication checks (SPF/DKIM/DMARC/PTR)
WithEncryptionEncryptionModeRequest encrypted or plain inbox
type EncryptionMode string
const (
EncryptionModeDefault EncryptionMode = "" // Use server default
EncryptionModeEncrypted EncryptionMode = "encrypted" // Request encrypted inbox
EncryptionModePlain EncryptionMode = "plain" // Request plain inbox
)

Whether encryption can be overridden depends on the server’s encryption policy. See ServerInfo for details.

When creating a client with vaultsandbox.New(), you can configure various options:

client, err := vaultsandbox.New(apiKey,
vaultsandbox.WithBaseURL("https://custom-api.example.com"),
vaultsandbox.WithTimeout(30*time.Second),
vaultsandbox.WithRetries(3),
)
OptionDescription
WithBaseURL(url string)Set a custom API base URL (default: https://api.vaultsandbox.com)
WithHTTPClient(client *http.Client)Use a custom HTTP client for requests
WithDeliveryStrategy(strategy DeliveryStrategy)Set how new emails are delivered (see below)
WithTimeout(timeout time.Duration)Set default request timeout (default: 60s)
WithRetries(count int)Set number of retry attempts for failed requests
WithRetryOn(statusCodes []int)Set HTTP status codes that trigger retries (default: 408, 429, 500, 502, 503, 504)
WithPollingConfig(cfg PollingConfig)Set polling configuration (see below)

The DeliveryStrategy type controls how the client receives new email notifications:

const (
// StrategySSE uses Server-Sent Events for real-time push notifications (default)
StrategySSE DeliveryStrategy = "sse"
// StrategyPolling uses periodic API calls with exponential backoff
StrategyPolling DeliveryStrategy = "polling"
)
// Force polling for environments where SSE is blocked
client, err := vaultsandbox.New(apiKey,
vaultsandbox.WithDeliveryStrategy(vaultsandbox.StrategyPolling),
)
// Force SSE for lowest latency
client, err := vaultsandbox.New(apiKey,
vaultsandbox.WithDeliveryStrategy(vaultsandbox.StrategySSE),
)

When using polling delivery strategy, you can fine-tune the polling behavior:

type PollingConfig struct {
InitialInterval time.Duration // Starting interval between polls (default: 2s)
MaxBackoff time.Duration // Maximum interval after backoff (default: 30s)
BackoffMultiplier float64 // Multiplier for exponential backoff (default: 1.5)
JitterFactor float64 // Random jitter 0-1 to prevent thundering herd (default: 0.3)
}
// Configure polling with custom settings
client, err := vaultsandbox.New(apiKey,
vaultsandbox.WithDeliveryStrategy(vaultsandbox.StrategyPolling),
vaultsandbox.WithPollingConfig(vaultsandbox.PollingConfig{
InitialInterval: 1 * time.Second,
MaxBackoff: 10 * time.Second,
BackoffMultiplier: 2.0,
JitterFactor: 0.2,
}),
)

Returns: string

The full email address for this inbox.

fmt.Println(inbox.EmailAddress())
// "a1b2c3d4@mail.example.com"

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

Returns: string

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

fmt.Println(inbox.InboxHash())
// "Rr02MLnP7F0pRVC6QdcpSIeyklqu3PDkYglvsfN7Oss"

Note: This is not the same as the local part of the email address. The email address local part (e.g., a1b2c3d4 in a1b2c3d4@mail.example.com) is different from the InboxHash().

Returns: time.Time

When the inbox will automatically expire and be deleted.

fmt.Println(inbox.ExpiresAt())
// 2024-01-16 12:00:00 +0000 UTC
// Check if inbox is expiring soon
hoursUntilExpiry := time.Until(inbox.ExpiresAt()).Hours()
fmt.Printf("Expires in %.1f hours\n", hoursUntilExpiry)

Returns: bool

Checks if the inbox has expired.

if inbox.IsExpired() {
fmt.Println("Inbox has expired")
}

Returns: bool

Returns whether email authentication (SPF, DKIM, DMARC, PTR) is enabled for this inbox.

fmt.Printf("Email auth enabled: %v\n", inbox.EmailAuth())

When false, all authentication results will have status "skipped". The Validate() method treats "skipped" as passing (not a failure).


Returns: bool

Returns whether the inbox uses end-to-end encryption.

if inbox.Encrypted() {
fmt.Println("Inbox uses end-to-end encryption")
} else {
fmt.Println("Inbox uses plain text storage")
}

When true, the inbox has ML-KEM-768 encryption keys and emails are encrypted. When false, emails are stored as Base64-encoded plain text.

Note: The ServerSigPk field in exported inbox data is only present when encrypted is true.


Returns: (*SyncStatus, error)

Retrieves the synchronization status of the inbox, including the email count and a hash for efficient change detection.

status, err := inbox.GetSyncStatus(ctx)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Email count: %d\n", status.EmailCount)
fmt.Printf("Emails hash: %s\n", status.EmailsHash)

The SyncStatus struct contains:

type SyncStatus struct {
EmailCount int // Number of emails in the inbox
EmailsHash string // Hash of the email list for change detection
}
┌─────────────────────────────────────────────────────────┐
│ Inbox Lifecycle │
└─────────────────────────────────────────────────────────┘
1. Creation
client.CreateInbox(ctx) → *Inbox
- 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(ctx) or TTL expires
- All emails deleted
- Inbox address freed
- Keypair destroyed
emails, err := inbox.GetEmails(ctx)
if err != nil {
log.Fatal(err)
}
fmt.Printf("%d emails in inbox\n", len(emails))
for _, email := range emails {
fmt.Printf("%s: %s\n", email.From, email.Subject)
}
email, err := inbox.GetEmail(ctx, "email-id-123")
if err != nil {
log.Fatal(err)
}
fmt.Println(email.Subject)
fmt.Println(email.Text)

The Email struct contains the following fields:

FieldTypeDescription
IDstringUnique email identifier
FromstringSender email address
To[]stringRecipient email addresses
SubjectstringEmail subject line
TextstringPlain text body
HTMLstringHTML body
ReceivedAttime.TimeWhen the email was received
Headersmap[string]stringEmail headers
Attachments[]AttachmentFile attachments
Links[]stringLinks extracted from the email body
AuthResults*authresults.AuthResultsEmail authentication results (SPF, DKIM, DMARC)
IsReadboolWhether the email has been marked as read

The Attachment struct contains the following fields:

FieldTypeDescription
FilenamestringOriginal filename of the attachment
ContentTypestringMIME type (e.g., application/pdf, image/png)
SizeintSize in bytes
ContentIDstringContent-ID for inline attachments
ContentDispositionstringDisposition type (attachment or inline)
Content[]byteRaw attachment content
ChecksumstringChecksum for integrity verification
// Working with attachments
for _, att := range email.Attachments {
fmt.Printf("Filename: %s\n", att.Filename)
fmt.Printf("Type: %s\n", att.ContentType)
fmt.Printf("Size: %d bytes\n", att.Size)
// Save attachment to file
os.WriteFile(att.Filename, att.Content, 0644)
}

The AuthResults struct contains email authentication check results:

type AuthResults struct {
SPF *SPFResult // SPF check result
DKIM []DKIMResult // DKIM signature results (may have multiple)
DMARC *DMARCResult // DMARC policy result
ReverseDNS *ReverseDNSResult // Reverse DNS check result
}
type SPFResult struct {
Result string // pass, fail, softfail, neutral, none, temperror, permerror, skipped
Domain string
IP string
Info string
}
type DKIMResult struct {
Result string // pass, fail, none, skipped
Domain string
Selector string
Info string
}
type DMARCResult struct {
Result string // pass, fail, none, skipped
Policy string // none, quarantine, reject
Aligned bool
Domain string
Info string
}
type ReverseDNSResult struct {
Result string // pass, fail, none, skipped
IP string
Hostname string
}

Validation Methods:

// Quick check if all primary auth checks passed
if email.AuthResults.IsPassing() {
fmt.Println("Email authentication passed")
}
// Detailed validation with failure messages
validation := email.AuthResults.Validate()
fmt.Printf("Overall: %v\n", validation.Passed)
fmt.Printf("SPF: %v\n", validation.SPFPassed)
fmt.Printf("DKIM: %v\n", validation.DKIMPassed)
fmt.Printf("DMARC: %v\n", validation.DMARCPassed)
fmt.Printf("Reverse DNS: %v\n", validation.ReverseDNSPassed)
if len(validation.Failures) > 0 {
fmt.Println("Failures:")
for _, f := range validation.Failures {
fmt.Printf(" - %s\n", f)
}
}
// Accessing email fields
fmt.Printf("From: %s\n", email.From)
fmt.Printf("Subject: %s\n", email.Subject)
fmt.Printf("Received: %s\n", email.ReceivedAt)
fmt.Printf("Is Read: %v\n", email.IsRead)
fmt.Printf("Attachments: %d\n", len(email.Attachments))
// Check authentication results
if email.AuthResults != nil {
if email.AuthResults.SPF != nil {
fmt.Printf("SPF: %s\n", email.AuthResults.SPF.Result)
}
for _, dkim := range email.AuthResults.DKIM {
fmt.Printf("DKIM: %s (domain: %s)\n", dkim.Result, dkim.Domain)
}
if email.AuthResults.DMARC != nil {
fmt.Printf("DMARC: %s\n", email.AuthResults.DMARC.Result)
}
if email.AuthResults.ReverseDNS != nil {
fmt.Printf("Reverse DNS: %s\n", email.AuthResults.ReverseDNS.Result)
}
// Use convenience method to check if all auth checks passed
// Note: "skipped" status is treated as passing
if email.AuthResults.IsPassing() {
fmt.Println("All authentication checks passed")
}
}

Standalone Validation Functions:

The authresults package also provides standalone validation functions for granular control:

import "github.com/vaultsandbox/client-go/authresults"
// Validate all authentication results at once
err := authresults.Validate(email.AuthResults)
if err != nil {
var valErr *authresults.ValidationError
if errors.As(err, &valErr) {
fmt.Println("Validation failures:", valErr.Errors)
}
}
// Validate individual checks
if err := authresults.ValidateSPF(email.AuthResults); err != nil {
fmt.Println("SPF failed:", err)
}
if err := authresults.ValidateDKIM(email.AuthResults); err != nil {
fmt.Println("DKIM failed:", err)
}
if err := authresults.ValidateDMARC(email.AuthResults); err != nil {
fmt.Println("DMARC failed:", err)
}
if err := authresults.ValidateReverseDNS(email.AuthResults); err != nil {
fmt.Println("Reverse DNS failed:", err)
}

Available Validation Functions:

FunctionDescription
Validate(results)Validates all authentication results, returns ValidationError with all failures
ValidateSPF(results)Validates only SPF, returns ErrSPFFailed on failure
ValidateDKIM(results)Validates DKIM (passes if at least one signature passes), returns ErrDKIMFailed on failure
ValidateDMARC(results)Validates only DMARC, returns ErrDMARCFailed on failure
ValidateReverseDNS(results)Validates only reverse DNS, returns ErrReverseDNSFailed on failure

Sentinel Errors in authresults package:

ErrorDescription
ErrSPFFailedSPF check failed
ErrDKIMFailedDKIM check failed
ErrDMARCFailedDMARC check failed
ErrReverseDNSFailedReverse DNS check failed
ErrNoAuthResultsNo authentication results available
// Wait for any email
email, err := inbox.WaitForEmail(ctx,
vaultsandbox.WithWaitTimeout(30*time.Second),
)
// Wait for specific email
email, err := inbox.WaitForEmail(ctx,
vaultsandbox.WithWaitTimeout(30*time.Second),
vaultsandbox.WithSubjectRegex(regexp.MustCompile(`Password Reset`)),
vaultsandbox.WithFrom("noreply@example.com"),
)

Available Wait Options:

OptionDescription
WithWaitTimeout(duration)Maximum time to wait for an email
WithSubject(string)Filter by exact subject match
WithSubjectRegex(regexp)Filter by subject regex pattern
WithFrom(string)Filter by exact sender match
WithFromRegex(regexp)Filter by sender regex pattern
WithPredicate(func(*Email) bool)Filter by custom predicate function
// Using exact subject match
email, err := inbox.WaitForEmail(ctx,
vaultsandbox.WithSubject("Welcome to Our Service"),
)
// Using sender regex
email, err := inbox.WaitForEmail(ctx,
vaultsandbox.WithFromRegex(regexp.MustCompile(`.*@example\.com`)),
)
// Using custom predicate
email, err := inbox.WaitForEmail(ctx,
vaultsandbox.WithPredicate(func(e *vaultsandbox.Email) bool {
return len(e.Attachments) > 0
}),
)
// Combining multiple filters
email, err := inbox.WaitForEmail(ctx,
vaultsandbox.WithWaitTimeout(60*time.Second),
vaultsandbox.WithFrom("notifications@example.com"),
vaultsandbox.WithSubjectRegex(regexp.MustCompile(`Order #\d+`)),
)
// Wait until the inbox has at least 3 emails
emails, err := inbox.WaitForEmailCount(ctx, 3,
vaultsandbox.WithWaitTimeout(60*time.Second),
)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Received %d emails\n", len(emails))
// Get raw email (original MIME content)
rawContent, err := inbox.GetRawEmail(ctx, email.ID)
if err != nil {
log.Fatal(err)
}
fmt.Println(rawContent)
err := inbox.MarkEmailAsRead(ctx, email.ID)
if err != nil {
log.Fatal(err)
}
// Get the email first, then delete
email, err := inbox.GetEmail(ctx, "email-id-123")
if err != nil {
log.Fatal(err)
}
err = inbox.DeleteEmail(ctx, email.ID)
// Delete inbox and all its emails
err := inbox.Delete(ctx)

Each inbox is completely isolated:

inbox1, _ := client.CreateInbox(ctx)
inbox2, _ := client.CreateInbox(ctx)
// 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 default TTL (1 hour)
inbox, err := client.CreateInbox(ctx)
// Expire after 1 hour
inbox, err := client.CreateInbox(ctx, vaultsandbox.WithTTL(time.Hour))
// Expire after 10 minutes (useful for quick tests)
inbox, err := client.CreateInbox(ctx, vaultsandbox.WithTTL(10*time.Minute))
// Expire after 7 days (maximum allowed)
inbox, err := client.CreateInbox(ctx, vaultsandbox.WithTTL(7*24*time.Hour))
const (
MinTTL = 60 * time.Second // Minimum TTL: 1 minute
MaxTTL = 604800 * time.Second // Maximum TTL: 7 days
)
minutesLeft := time.Until(inbox.ExpiresAt()).Minutes()
if minutesLeft < 5 {
fmt.Println("Inbox expiring soon!")
}

Inboxes can be exported and imported for:

  • Test reproducibility
  • Sharing between environments
  • Backup and restore
exportData := inbox.Export()
// Save to file
jsonData, _ := json.MarshalIndent(exportData, "", " ")
os.WriteFile("inbox.json", jsonData, 0600)
// Or use the convenience method
err := client.ExportInboxToFile(inbox, "inbox.json")
// From ExportedInbox struct
jsonData, _ := os.ReadFile("inbox.json")
var exportData vaultsandbox.ExportedInbox
json.Unmarshal(jsonData, &exportData)
inbox, err := client.ImportInbox(ctx, &exportData)
// Or use the convenience method
inbox, err := client.ImportInboxFromFile(ctx, "inbox.json")
// Inbox restored with all encryption keys

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

Use Watch() to receive new emails as they arrive via a channel:

ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// Watch returns a channel that receives new emails
for email := range inbox.Watch(ctx) {
fmt.Printf("New email: %s\n", email.Subject)
}

The channel is closed when the context is cancelled.

Use WatchInboxes() to monitor multiple inboxes simultaneously:

ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// WatchInboxes returns a channel of InboxEvent
for event := range client.WatchInboxes(ctx, inbox1, inbox2) {
fmt.Printf("New email in %s: %s\n", event.Inbox.EmailAddress(), event.Email.Subject)
}

The InboxEvent struct contains:

type InboxEvent struct {
Inbox *Inbox // The inbox that received the email
Email *Email // The received email
}

Short TTL for fast cleanup:

inbox, err := client.CreateInbox(ctx, vaultsandbox.WithTTL(time.Hour))

Always clean up:

inbox, err := client.CreateInbox(ctx)
if err != nil {
log.Fatal(err)
}
defer inbox.Delete(context.Background())
// Run tests

Test setup and teardown:

func TestPasswordReset(t *testing.T) {
client, err := vaultsandbox.New(os.Getenv("VAULTSANDBOX_API_KEY"))
if err != nil {
t.Fatal(err)
}
defer client.Close()
ctx := context.Background()
inbox, err := client.CreateInbox(ctx, vaultsandbox.WithTTL(2*time.Hour))
if err != nil {
t.Fatal(err)
}
defer inbox.Delete(context.Background())
// Trigger password reset
triggerPasswordReset(inbox.EmailAddress())
// Wait for email
email, err := inbox.WaitForEmail(ctx,
vaultsandbox.WithWaitTimeout(10*time.Second),
)
if err != nil {
t.Fatal(err)
}
// Assertions
if email.Subject != "Password Reset" {
t.Errorf("expected subject 'Password Reset', got %q", email.Subject)
}
}

Longer TTL for convenience:

inbox, err := client.CreateInbox(ctx, vaultsandbox.WithTTL(24*time.Hour))

Export for reuse:

// Export after creating
err := client.ExportInboxToFile(inbox, "test-inbox.json")
// Reuse in later sessions
inbox, err := client.ImportInboxFromFile(ctx, "test-inbox.json")

Monitor expiration:

go func() {
ticker := time.NewTicker(time.Minute)
defer ticker.Stop()
for range ticker.C {
minutesLeft := time.Until(inbox.ExpiresAt()).Minutes()
if minutesLeft < 10 {
log.Printf("Inbox %s expiring in %.0f minutes",
inbox.EmailAddress(), minutesLeft)
}
}
}()
var testInbox *vaultsandbox.Inbox
var testClient *vaultsandbox.Client
func TestMain(m *testing.M) {
var err error
testClient, err = vaultsandbox.New(os.Getenv("VAULTSANDBOX_API_KEY"))
if err != nil {
log.Fatal(err)
}
testInbox, err = testClient.CreateInbox(context.Background(),
vaultsandbox.WithTTL(2*time.Hour),
)
if err != nil {
log.Fatal(err)
}
code := m.Run()
testInbox.Delete(context.Background())
testClient.Close()
os.Exit(code)
}
func TestPasswordReset(t *testing.T) {
ctx := context.Background()
triggerPasswordReset(testInbox.EmailAddress())
email, err := testInbox.WaitForEmail(ctx,
vaultsandbox.WithWaitTimeout(10*time.Second),
)
if err != nil {
t.Fatal(err)
}
// ...
}
user1Inbox, _ := client.CreateInbox(ctx)
user2Inbox, _ := client.CreateInbox(ctx)
adminInbox, _ := client.CreateInbox(ctx)
// Each inbox receives emails independently
sendWelcomeEmail(user1Inbox.EmailAddress())
sendWelcomeEmail(user2Inbox.EmailAddress())
sendAdminReport(adminInbox.EmailAddress())
type InboxPool struct {
client *vaultsandbox.Client
pool chan *vaultsandbox.Inbox
size int
}
func NewInboxPool(client *vaultsandbox.Client, size int) *InboxPool {
return &InboxPool{
client: client,
pool: make(chan *vaultsandbox.Inbox, size),
size: size,
}
}
func (p *InboxPool) Initialize(ctx context.Context) error {
for i := 0; i < p.size; i++ {
inbox, err := p.client.CreateInbox(ctx)
if err != nil {
return err
}
p.pool <- inbox
}
return nil
}
func (p *InboxPool) Get() *vaultsandbox.Inbox {
return <-p.pool
}
func (p *InboxPool) Return(inbox *vaultsandbox.Inbox) {
p.pool <- inbox
}
func (p *InboxPool) Cleanup(ctx context.Context) {
close(p.pool)
for inbox := range p.pool {
inbox.Delete(ctx)
}
}

Use errors.Is() to check for specific error conditions:

import "errors"
emails, err := inbox.GetEmails(ctx)
if err != nil {
switch {
case errors.Is(err, vaultsandbox.ErrInboxNotFound):
log.Println("Inbox not found or expired")
case errors.Is(err, vaultsandbox.ErrUnauthorized):
log.Println("Invalid API key")
case errors.Is(err, vaultsandbox.ErrRateLimited):
log.Println("Rate limit exceeded, retry later")
default:
log.Printf("Unexpected error: %v", err)
}
}
ErrorDescription
ErrMissingAPIKeyNo API key was provided to New()
ErrClientClosedOperation attempted on a closed client
ErrUnauthorizedAPI key is invalid or expired (HTTP 401)
ErrInboxNotFoundInbox does not exist or has expired (HTTP 404)
ErrEmailNotFoundEmail does not exist (HTTP 404)
ErrInboxAlreadyExistsInbox with requested address already exists (HTTP 409)
ErrInvalidImportDataImported inbox data is malformed or invalid
ErrDecryptionFailedEmail decryption failed
ErrSignatureInvalidSignature verification failed (potential tampering)
ErrRateLimitedAPI rate limit exceeded (HTTP 429)

The SDK provides structured error types for detailed error handling:

// APIError - HTTP errors from the VaultSandbox API
var apiErr *vaultsandbox.APIError
if errors.As(err, &apiErr) {
fmt.Printf("Status: %d\n", apiErr.StatusCode)
fmt.Printf("Message: %s\n", apiErr.Message)
fmt.Printf("Request ID: %s\n", apiErr.RequestID)
}
// NetworkError - Network-level failures
var netErr *vaultsandbox.NetworkError
if errors.As(err, &netErr) {
fmt.Printf("URL: %s\n", netErr.URL)
fmt.Printf("Attempt: %d\n", netErr.Attempt)
fmt.Printf("Underlying: %v\n", netErr.Err)
}
// SignatureVerificationError - Signature verification failure
var sigErr *vaultsandbox.SignatureVerificationError
if errors.As(err, &sigErr) {
fmt.Printf("Message: %s\n", sigErr.Message)
if sigErr.IsKeyMismatch {
fmt.Println("Server key mismatch - potential key substitution attack")
}
}
// Timeouts - use context.DeadlineExceeded
if errors.Is(err, context.DeadlineExceeded) {
fmt.Println("Operation timed out")
}
type APIError struct {
StatusCode int
Message string
RequestID string
ResourceType ResourceType
}
type NetworkError struct {
Err error
URL string
Attempt int
}
type SignatureVerificationError struct {
Message string
IsKeyMismatch bool // true if caused by server key mismatch
}

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
_, err := inbox.GetEmails(ctx) // Will error if inbox expired
if errors.Is(err, vaultsandbox.ErrInboxNotFound) {
log.Println("Inbox has expired or was deleted")
}

When requesting a specific email address:

inbox, err := client.CreateInbox(ctx,
vaultsandbox.WithEmailAddress("test@mail.example.com"),
)
if errors.Is(err, vaultsandbox.ErrInboxAlreadyExists) {
// Address already in use, generate random instead
inbox, err = client.CreateInbox(ctx)
}
emails, err := inbox.GetEmails(ctx)
if errors.Is(err, vaultsandbox.ErrInboxNotFound) {
log.Println("Inbox has expired")
// Create new inbox
newInbox, err := client.CreateInbox(ctx)
if err != nil {
log.Fatal(err)
}
// Continue with newInbox
}

All inbox operations accept a context for cancellation and timeouts:

ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
emails, err := inbox.GetEmails(ctx)
if errors.Is(err, context.DeadlineExceeded) {
log.Println("Operation timed out")
}
// Get all inboxes managed by this client
inboxes := client.Inboxes()
for _, inbox := range inboxes {
fmt.Printf("Inbox: %s (expires: %s)\n",
inbox.EmailAddress(), inbox.ExpiresAt())
}
inbox, exists := client.GetInbox("test@mail.example.com")
if !exists {
log.Println("Inbox not found in client")
}
err := client.DeleteInbox(ctx, "test@mail.example.com")
if err != nil {
log.Fatal(err)
}
count, err := client.DeleteAllInboxes(ctx)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Deleted %d inboxes\n", count)
err := client.CheckKey(ctx)
if err != nil {
log.Fatal("API key is invalid:", err)
}
fmt.Println("API key is valid")
info := client.ServerInfo()
fmt.Printf("Allowed domains: %v\n", info.AllowedDomains)
fmt.Printf("Max TTL: %v\n", info.MaxTTL)
fmt.Printf("Default TTL: %v\n", info.DefaultTTL)
fmt.Printf("Encryption policy: %s\n", info.EncryptionPolicy)

The ServerInfo struct contains:

type ServerInfo struct {
AllowedDomains []string // Email domains available for inbox creation
MaxTTL time.Duration // Maximum allowed TTL for inboxes
DefaultTTL time.Duration // Default TTL when not specified
EncryptionPolicy EncryptionPolicy // Server's encryption policy
}

The EncryptionPolicy field indicates how the server handles inbox encryption:

type EncryptionPolicy string
const (
EncryptionPolicyAlways EncryptionPolicy = "always" // All inboxes encrypted, no override
EncryptionPolicyEnabled EncryptionPolicy = "enabled" // Encrypted by default, can request plain
EncryptionPolicyDisabled EncryptionPolicy = "disabled" // Plain by default, can request encrypted
EncryptionPolicyNever EncryptionPolicy = "never" // All inboxes plain, no override
)
PolicyDefault EncryptionPer-Inbox Override
alwaysEncryptedNo - all inboxes encrypted
enabledEncryptedYes - can request plain
disabledPlainYes - can request encrypted
neverPlainNo - all inboxes plain

Helper Methods:

// Check if we can override encryption settings per-inbox
if info.EncryptionPolicy.CanOverride() {
// Can use WithEncryption() option
inbox, err := client.CreateInbox(ctx, vaultsandbox.WithEncryption(vaultsandbox.EncryptionModePlain))
}
// Check default encryption state
if info.EncryptionPolicy.DefaultEncrypted() {
fmt.Println("Inboxes are encrypted by default")
}