Delivery Strategies
VaultSandbox Client supports two email delivery strategies: Server-Sent Events (SSE) for real-time updates and Polling for compatibility. The SDK intelligently chooses the best strategy automatically or allows manual configuration.
Overview
Section titled “Overview”When you wait for emails or subscribe to new email notifications, the SDK needs to know when emails arrive. It does this using one of two strategies:
- SSE (Server-Sent Events): Real-time push notifications from the server
- Polling: Periodic checking for new emails with adaptive backoff
Strategy Comparison
Section titled “Strategy Comparison”| Feature | SSE | Polling |
|---|---|---|
| Latency | Near-instant (~100ms) | Poll interval (default: 2s) |
| Server Load | Lower (persistent connection) | Higher (repeated requests) |
| Network Traffic | Lower (only when emails arrive) | Higher (constant polling) |
| Compatibility | Requires persistent connections | Works everywhere |
| Firewall/Proxy | May be blocked | Always works |
| Battery Impact | Lower (push-based) | Higher (constant requests) |
Auto Strategy (Recommended)
Section titled “Auto Strategy (Recommended)”The default StrategyAuto automatically selects the best delivery method:
- Tries SSE first - Attempts to establish an SSE connection
- Falls back to polling - If SSE fails within 5 seconds, uses polling
- Adapts to environment - Works seamlessly in different network conditions
package main
import ( "context" "os" "time"
vaultsandbox "github.com/vaultsandbox/client-go")
func main() { ctx := context.Background()
// Auto strategy (default) client, err := vaultsandbox.New( os.Getenv("VAULTSANDBOX_API_KEY"), // vaultsandbox.WithDeliveryStrategy(vaultsandbox.StrategyAuto), // Default, can be omitted ) if err != nil { panic(err) } defer client.Close()
// SDK will automatically choose the best strategy inbox, err := client.CreateInbox(ctx) if err != nil { panic(err) }
email, err := inbox.WaitForEmail(ctx, vaultsandbox.WithWaitTimeout(10*time.Second), ) if err != nil { panic(err) }}When Auto Chooses SSE
Section titled “When Auto Chooses SSE”- Gateway supports SSE
- Network allows persistent connections
- SSE connection established within 5 seconds
- No restrictive proxy/firewall
When Auto Falls Back to Polling
Section titled “When Auto Falls Back to Polling”- Gateway doesn’t support SSE
- SSE connection fails
- SSE doesn’t connect within timeout
- Behind restrictive proxy/firewall
- Network requires periodic reconnection
SSE Strategy
Section titled “SSE Strategy”Server-Sent Events provide real-time push notifications when emails arrive.
Advantages
Section titled “Advantages”- Near-instant delivery: Emails appear within milliseconds
- Lower server load: Single persistent connection
- Efficient: Only transmits when emails arrive
- Battery-friendly: No constant polling
Configuration
Section titled “Configuration”client, err := vaultsandbox.New( os.Getenv("VAULTSANDBOX_API_KEY"), vaultsandbox.WithDeliveryStrategy(vaultsandbox.StrategySSE),)SSE Constants
Section titled “SSE Constants”The SSE strategy uses these configuration values (defined in the delivery package):
| Constant | Value | Description |
|---|---|---|
SSEReconnectInterval | 5s | Base interval before reconnection attempts |
SSEMaxReconnectAttempts | 10 | Maximum consecutive reconnection attempts |
SSEBackoffMultiplier | 2 | Multiplier for exponential backoff |
Reconnection Behavior
Section titled “Reconnection Behavior”SSE uses exponential backoff for reconnections:
1st attempt: SSEReconnectInterval (5s)2nd attempt: SSEReconnectInterval * 2 (10s)3rd attempt: SSEReconnectInterval * 4 (20s)...up to SSEMaxReconnectAttemptsExample Usage
Section titled “Example Usage”package main
import ( "context" "fmt" "os" "time"
vaultsandbox "github.com/vaultsandbox/client-go")
func main() { ctx := context.Background()
client, err := vaultsandbox.New( os.Getenv("VAULTSANDBOX_API_KEY"), vaultsandbox.WithDeliveryStrategy(vaultsandbox.StrategySSE), ) if err != nil { panic(err) } defer client.Close()
inbox, err := client.CreateInbox(ctx) if err != nil { panic(err) } defer inbox.Delete(ctx)
// Create cancellable context for watching watchCtx, cancel := context.WithTimeout(ctx, 10*time.Second) defer cancel()
// Real-time watching (uses SSE) for email := range inbox.Watch(watchCtx) { fmt.Printf("Instant notification: %s\n", email.Subject) if strings.Contains(email.Subject, "Welcome") { break } }}When to Use SSE
Section titled “When to Use SSE”- Real-time monitoring: When you need instant email notifications
- Long-running tests: Reduces overall test time
- High email volume: More efficient than polling
- Development/local: Fast feedback during development
Limitations
Section titled “Limitations”- Requires persistent HTTP connection support
- May not work behind some corporate proxies
- Some cloud environments may close long-lived connections
- Requires server-side SSE support
Adding Inboxes After Connection
Section titled “Adding Inboxes After Connection”When using SSE, the SDK automatically handles adding new inboxes after the connection is established. If you call client.CreateInbox or client.ImportInbox while SSE is already connected, the SDK will:
- Immediately trigger a reconnection (without exponential backoff)
- Include the new inbox in the updated connection
- Sync all inboxes after reconnection to catch any emails that arrived during the brief reconnection window
This means you can safely add inboxes dynamically without any manual intervention:
ctx, cancel := context.WithCancel(context.Background())defer cancel()
// Create initial inbox and start watchinginbox1, _ := client.CreateInbox(ctx)go func() { for email := range inbox1.Watch(ctx) { handler(email) }}()
// Later, add another inbox - events start flowing automaticallyinbox2, _ := client.CreateInbox(ctx)go func() { for email := range inbox2.Watch(ctx) { handler(email) }}()
// Both inboxes now receive real-time eventsThe reconnection is transparent and fast, so there’s no need to manually restart the client or coordinate inbox creation timing.
Polling Strategy
Section titled “Polling Strategy”Polling periodically checks for new emails with adaptive backoff and jitter.
Advantages
Section titled “Advantages”- Universal compatibility: Works in all environments
- Firewall-friendly: Standard HTTP requests
- Predictable: Easy to reason about behavior
- Resilient: Automatically recovers from transient failures
Configuration
Section titled “Configuration”client, err := vaultsandbox.New( os.Getenv("VAULTSANDBOX_API_KEY"), vaultsandbox.WithDeliveryStrategy(vaultsandbox.StrategyPolling),)Polling Constants
Section titled “Polling Constants”The polling strategy uses these configuration values (defined in the delivery package):
| Constant | Value | Description |
|---|---|---|
DefaultPollingInitialInterval | 2s | Starting interval between polls |
DefaultPollingMaxBackoff | 30s | Maximum interval between polls |
DefaultPollingBackoffMultiplier | 1.5 | Multiplier for adaptive backoff |
DefaultPollingJitterFactor | 0.3 | Random jitter to prevent thundering herd |
Adaptive Backoff
Section titled “Adaptive Backoff”The polling strategy uses sync-status-based change detection with adaptive backoff:
- First checks a lightweight sync endpoint for changes
- Only fetches full email lists when changes are detected
- When no changes occur, polling intervals gradually increase
- When changes are detected, intervals reset to initial value
- Random jitter is added to prevent synchronized polling across clients
Initial poll: 2sNo changes: 2s * 1.5 = 3s (+ jitter)No changes: 3s * 1.5 = 4.5s (+ jitter)No changes: 4.5s * 1.5 = 6.75s (+ jitter)...up to 30s maximumChanges detected: reset to 2sExample Usage
Section titled “Example Usage”package main
import ( "context" "fmt" "os" "time"
vaultsandbox "github.com/vaultsandbox/client-go")
func main() { ctx := context.Background()
client, err := vaultsandbox.New( os.Getenv("VAULTSANDBOX_API_KEY"), vaultsandbox.WithDeliveryStrategy(vaultsandbox.StrategyPolling), ) if err != nil { panic(err) } defer client.Close()
inbox, err := client.CreateInbox(ctx) if err != nil { panic(err) } defer inbox.Delete(ctx)
// Create cancellable context for watching watchCtx, cancel := context.WithTimeout(ctx, 10*time.Second) defer cancel()
// Polling-based watching for email := range inbox.Watch(watchCtx) { fmt.Printf("Polled notification: %s\n", email.Subject) if strings.Contains(email.Subject, "Welcome") { break } }}When to Use Polling
Section titled “When to Use Polling”- Corporate networks: Restrictive firewall/proxy environments
- CI/CD pipelines: Guaranteed compatibility
- Rate-limited APIs: Avoid hitting request limits
- Debugging: Predictable request timing
- Low email volume: Polling overhead is minimal
Choosing the Right Strategy
Section titled “Choosing the Right Strategy”Use Auto (Default)
Section titled “Use Auto (Default)”For most use cases, let the SDK choose:
client, err := vaultsandbox.New( os.Getenv("VAULTSANDBOX_API_KEY"), // strategy defaults to StrategyAuto)Best for:
- General testing
- Unknown network conditions
- Mixed environments (dev, staging, CI)
- When you want it to “just work”
Force SSE
Section titled “Force SSE”When you need guaranteed real-time performance:
client, err := vaultsandbox.New( os.Getenv("VAULTSANDBOX_API_KEY"), vaultsandbox.WithDeliveryStrategy(vaultsandbox.StrategySSE),)Best for:
- Local development (known to support SSE)
- Real-time monitoring dashboards
- High-volume email testing
- Latency-sensitive tests
Caveat: Will fall back to polling if SSE fails to connect.
Force Polling
Section titled “Force Polling”When compatibility is more important than speed:
strategy := vaultsandbox.StrategyPollingif os.Getenv("CI") != "" { strategy = vaultsandbox.StrategyPolling}
client, err := vaultsandbox.New( os.Getenv("VAULTSANDBOX_API_KEY"), vaultsandbox.WithDeliveryStrategy(strategy),)Best for:
- CI/CD environments (guaranteed to work)
- Corporate networks with restrictive proxies
- When SSE is known to be problematic
- Rate-limited scenarios
Environment-Specific Configuration
Section titled “Environment-Specific Configuration”Development
Section titled “Development”Fast feedback with SSE:
func newDevClient() (*vaultsandbox.Client, error) { return vaultsandbox.New( os.Getenv("VAULTSANDBOX_API_KEY"), vaultsandbox.WithBaseURL("http://localhost:3000"), vaultsandbox.WithDeliveryStrategy(vaultsandbox.StrategySSE), )}Reliable polling:
func newCIClient() (*vaultsandbox.Client, error) { return vaultsandbox.New( os.Getenv("VAULTSANDBOX_API_KEY"), vaultsandbox.WithDeliveryStrategy(vaultsandbox.StrategyPolling), )}Production Testing
Section titled “Production Testing”Auto with reasonable defaults:
func newProductionClient() (*vaultsandbox.Client, error) { return vaultsandbox.New( os.Getenv("VAULTSANDBOX_API_KEY"), vaultsandbox.WithDeliveryStrategy(vaultsandbox.StrategyAuto), vaultsandbox.WithTimeout(60*time.Second), )}Environment-Aware Factory
Section titled “Environment-Aware Factory”func createClient() (*vaultsandbox.Client, error) { opts := []vaultsandbox.Option{ vaultsandbox.WithBaseURL(os.Getenv("VAULTSANDBOX_URL")), }
switch { case os.Getenv("CI") != "": // CI: Reliable polling opts = append(opts, vaultsandbox.WithDeliveryStrategy(vaultsandbox.StrategyPolling)) case os.Getenv("GO_ENV") == "development": // Dev: Fast SSE opts = append(opts, vaultsandbox.WithDeliveryStrategy(vaultsandbox.StrategySSE)) default: // Production: Auto opts = append(opts, vaultsandbox.WithDeliveryStrategy(vaultsandbox.StrategyAuto)) }
return vaultsandbox.New(os.Getenv("VAULTSANDBOX_API_KEY"), opts...)}Monitoring Strategy Performance
Section titled “Monitoring Strategy Performance”Measure Email Delivery Latency
Section titled “Measure Email Delivery Latency”func measureDeliveryLatency(ctx context.Context) { client, _ := vaultsandbox.New(os.Getenv("VAULTSANDBOX_API_KEY")) defer client.Close()
inbox, _ := client.CreateInbox(ctx) defer inbox.Delete(ctx)
startTime := time.Now()
// Send email sendTestEmail(inbox.EmailAddress())
// Wait for email _, err := inbox.WaitForEmail(ctx, vaultsandbox.WithWaitTimeout(30*time.Second), ) if err != nil { panic(err) }
latency := time.Since(startTime) fmt.Printf("Email delivery latency: %v\n", latency)}Compare Strategies
Section titled “Compare Strategies”func compareStrategies(ctx context.Context) { // Test SSE sseClient, _ := vaultsandbox.New( os.Getenv("VAULTSANDBOX_API_KEY"), vaultsandbox.WithDeliveryStrategy(vaultsandbox.StrategySSE), ) defer sseClient.Close()
sseInbox, _ := sseClient.CreateInbox(ctx) sseStart := time.Now() sendTestEmail(sseInbox.EmailAddress()) sseInbox.WaitForEmail(ctx, vaultsandbox.WithWaitTimeout(10*time.Second)) sseLatency := time.Since(sseStart)
// Test Polling pollClient, _ := vaultsandbox.New( os.Getenv("VAULTSANDBOX_API_KEY"), vaultsandbox.WithDeliveryStrategy(vaultsandbox.StrategyPolling), ) defer pollClient.Close()
pollInbox, _ := pollClient.CreateInbox(ctx) pollStart := time.Now() sendTestEmail(pollInbox.EmailAddress()) pollInbox.WaitForEmail(ctx, vaultsandbox.WithWaitTimeout(10*time.Second)) pollLatency := time.Since(pollStart)
fmt.Printf("SSE latency: %v\n", sseLatency) fmt.Printf("Polling latency: %v\n", pollLatency) fmt.Printf("Difference: %v\n", pollLatency-sseLatency)
sseInbox.Delete(ctx) pollInbox.Delete(ctx)}Troubleshooting
Section titled “Troubleshooting”SSE Connection Failures
Section titled “SSE Connection Failures”When SSE fails, the auto strategy automatically falls back to polling:
import ( "errors"
vaultsandbox "github.com/vaultsandbox/client-go")
// Using auto strategy handles SSE failures gracefullyclient, err := vaultsandbox.New( os.Getenv("VAULTSANDBOX_API_KEY"), vaultsandbox.WithDeliveryStrategy(vaultsandbox.StrategyAuto),)if err != nil { // Handle initialization error var netErr *vaultsandbox.NetworkError if errors.As(err, &netErr) { fmt.Printf("Network error: %v\n", netErr.Err) }}
// For explicit SSE strategy, the SDK will fall back to polling on failureclient, err = vaultsandbox.New( os.Getenv("VAULTSANDBOX_API_KEY"), vaultsandbox.WithDeliveryStrategy(vaultsandbox.StrategySSE),)if err != nil { var netErr *vaultsandbox.NetworkError if errors.As(err, &netErr) { fmt.Printf("Connection failed: %v\n", netErr.Err) // Consider using polling strategy }}Polling Too Slow
Section titled “Polling Too Slow”If emails arrive slowly with polling:
// Solution 1: Use SSE for real-time deliveryclient, _ := vaultsandbox.New( os.Getenv("VAULTSANDBOX_API_KEY"), vaultsandbox.WithDeliveryStrategy(vaultsandbox.StrategySSE), // Real-time delivery)
// Solution 2: Use auto and let it chooseclient, _ := vaultsandbox.New( os.Getenv("VAULTSANDBOX_API_KEY"), vaultsandbox.WithDeliveryStrategy(vaultsandbox.StrategyAuto),)Context Cancellation
Section titled “Context Cancellation”All wait operations respect context cancellation:
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)defer cancel()
email, err := inbox.WaitForEmail(ctx, vaultsandbox.WithSubject("Welcome"),)if err != nil { if errors.Is(err, context.DeadlineExceeded) { fmt.Println("Timed out waiting for email") } else if errors.Is(err, context.Canceled) { fmt.Println("Wait was canceled") }}Best Practices
Section titled “Best Practices”1. Use Auto Strategy by Default
Section titled “1. Use Auto Strategy by Default”Let the SDK choose unless you have specific requirements:
// Good: Let SDK chooseclient, _ := vaultsandbox.New(os.Getenv("VAULTSANDBOX_API_KEY"))
// Only specify when neededciClient, _ := vaultsandbox.New( os.Getenv("VAULTSANDBOX_API_KEY"), vaultsandbox.WithDeliveryStrategy(vaultsandbox.StrategyPolling), // CI needs guaranteed compatibility)2. Tune for Environment
Section titled “2. Tune for Environment”Configure differently for each environment:
func createClient() (*vaultsandbox.Client, error) { opts := []vaultsandbox.Option{}
if os.Getenv("CI") != "" { // CI: Reliable polling opts = append(opts, vaultsandbox.WithDeliveryStrategy(vaultsandbox.StrategyPolling)) } else if os.Getenv("GO_ENV") == "development" { // Dev: Fast SSE opts = append(opts, vaultsandbox.WithDeliveryStrategy(vaultsandbox.StrategySSE)) } else { // Production: Auto opts = append(opts, vaultsandbox.WithDeliveryStrategy(vaultsandbox.StrategyAuto)) }
return vaultsandbox.New(os.Getenv("VAULTSANDBOX_API_KEY"), opts...)}3. Always Use Contexts
Section titled “3. Always Use Contexts”All operations should use contexts for timeout and cancellation:
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)defer cancel()
email, err := inbox.WaitForEmail(ctx, vaultsandbox.WithSubjectRegex(regexp.MustCompile(`Welcome`)),)4. Clean Up Resources
Section titled “4. Clean Up Resources”Always close clients and unsubscribe from subscriptions:
client, _ := vaultsandbox.New(os.Getenv("VAULTSANDBOX_API_KEY"))defer client.Close()
ctx, cancel := context.WithCancel(context.Background())defer cancel()
inbox, _ := client.CreateInbox(ctx)defer inbox.Delete(ctx)
go func() { for email := range inbox.Watch(ctx) { fmt.Println(email.Subject) }}()5. Handle Errors Appropriately
Section titled “5. Handle Errors Appropriately”Use Go’s error handling patterns:
import "errors"
email, err := inbox.WaitForEmail(ctx, vaultsandbox.WithWaitTimeout(10*time.Second))if err != nil { switch { case errors.Is(err, context.DeadlineExceeded): fmt.Println("Timeout waiting for email") case errors.Is(err, vaultsandbox.ErrInboxNotFound): fmt.Println("Inbox was deleted or has expired") default: fmt.Printf("Unexpected error: %v\n", err) } return}Next Steps
Section titled “Next Steps”- Real-time Monitoring Guide - Using subscriptions
- Configuration Reference - All config options
- Error Handling - Handle SSE errors
- CI/CD Integration - Strategy for CI environments