Skip to content

Delivery Strategies

VaultSandbox Client supports two email delivery strategies: Server-Sent Events (SSE) for real-time updates and Polling for compatibility. SSE is the default strategy, providing near-instant email notifications.

When you wait for emails or watch for new email notifications, the SDK needs to know when emails arrive. It does this using one of two strategies:

  1. SSE (Server-Sent Events): Real-time push notifications from the server (default)
  2. Polling: Periodic checking for new emails
FeatureSSEPolling
LatencyNear-instant (~100ms)Poll interval (default: 2s)
Server LoadLower (persistent connection)Higher (repeated requests)
Network TrafficLower (only when emails arrive)Higher (constant polling)
CompatibilityRequires persistent connectionsWorks everywhere
Firewall/ProxyMay be blockedAlways works
Battery ImpactLower (push-based)Higher (constant requests)
public enum DeliveryStrategy
{
Sse, // Server-Sent Events (default)
Polling // Polling only
}

Server-Sent Events provide real-time push notifications when emails arrive.

  • Near-instant delivery: Emails appear within milliseconds
  • Lower server load: Single persistent connection
  • Efficient: Only transmits when emails arrive
  • Battery-friendly: No constant polling
var client = VaultSandboxClientBuilder.Create()
.WithBaseUrl("https://smtp.vaultsandbox.com")
.WithApiKey(Environment.GetEnvironmentVariable("VAULTSANDBOX_API_KEY")!)
.UseSseDelivery()
.WithSseReconnectInterval(TimeSpan.FromSeconds(5))
.WithSseMaxReconnectAttempts(10)
.Build();
OptionTypeDefaultDescription
SseReconnectIntervalTimeSpan2 secondsInitial delay before reconnection
SseMaxReconnectAttemptsint10Maximum reconnection attempts

SSE uses exponential backoff for reconnections:

1st attempt: SseReconnectInterval (2s)
2nd attempt: SseReconnectInterval * 2 (4s)
3rd attempt: SseReconnectInterval * 4 (8s)
...up to SseMaxReconnectAttempts
var client = VaultSandboxClientBuilder.Create()
.WithBaseUrl("https://smtp.vaultsandbox.com")
.WithApiKey(Environment.GetEnvironmentVariable("VAULTSANDBOX_API_KEY")!)
.UseSseDelivery()
.Build();
var inbox = await client.CreateInboxAsync();
// Real-time subscription using IAsyncEnumerable (uses SSE)
using var cts = new CancellationTokenSource();
await foreach (var email in inbox.WatchAsync(cts.Token))
{
Console.WriteLine($"Instant notification: {email.Subject}");
// Cancel after first email (or based on your logic)
if (ShouldStop(email))
{
cts.Cancel();
}
}
// Waiting also uses SSE (faster than polling)
var email = await inbox.WaitForEmailAsync(new WaitForEmailOptions
{
Timeout = TimeSpan.FromSeconds(10),
Subject = "Welcome",
UseRegex = true
});
  • 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
  • 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

Polling periodically checks for new emails at a configured interval.

  • Universal compatibility: Works in all environments
  • Firewall-friendly: Standard HTTP requests
  • Predictable: Easy to reason about behavior
  • Resilient: Automatically recovers from transient failures
var client = VaultSandboxClientBuilder.Create()
.WithBaseUrl("https://smtp.vaultsandbox.com")
.WithApiKey(Environment.GetEnvironmentVariable("VAULTSANDBOX_API_KEY")!)
.UsePollingDelivery()
.WithPollInterval(TimeSpan.FromSeconds(2))
.Build();
OptionTypeDefaultDescription
PollIntervalTimeSpan2 secondsHow often to poll for emails
var client = VaultSandboxClientBuilder.Create()
.WithBaseUrl("https://smtp.vaultsandbox.com")
.WithApiKey(Environment.GetEnvironmentVariable("VAULTSANDBOX_API_KEY")!)
.UsePollingDelivery()
.WithPollInterval(TimeSpan.FromSeconds(1))
.Build();
var inbox = await client.CreateInboxAsync();
// Polling-based subscription using IAsyncEnumerable
using var cts = new CancellationTokenSource(TimeSpan.FromMinutes(5));
await foreach (var email in inbox.WatchAsync(cts.Token))
{
Console.WriteLine($"Polled notification: {email.Subject}");
}
// Waiting uses polling (checks every PollInterval)
var email = await inbox.WaitForEmailAsync(new WaitForEmailOptions
{
Timeout = TimeSpan.FromSeconds(10),
Subject = "Welcome",
UseRegex = true
});

Different intervals suit different scenarios:

// Fast polling (500ms) - Development/local testing
var fastClient = VaultSandboxClientBuilder.Create()
.WithBaseUrl("http://localhost:3000")
.WithApiKey("dev-key")
.UsePollingDelivery()
.WithPollInterval(TimeSpan.FromMilliseconds(500))
.Build();
// Standard polling (2s) - Default, good balance
var standardClient = VaultSandboxClientBuilder.Create()
.WithBaseUrl("https://smtp.vaultsandbox.com")
.WithApiKey(Environment.GetEnvironmentVariable("VAULTSANDBOX_API_KEY")!)
.UsePollingDelivery()
.WithPollInterval(TimeSpan.FromSeconds(2))
.Build();
// Slow polling (5s) - CI/CD or rate-limited environments
var slowClient = VaultSandboxClientBuilder.Create()
.WithBaseUrl("https://smtp.vaultsandbox.com")
.WithApiKey(Environment.GetEnvironmentVariable("VAULTSANDBOX_API_KEY")!)
.UsePollingDelivery()
.WithPollInterval(TimeSpan.FromSeconds(5))
.Build();
  • 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

For WaitForEmailAsync(), you can override the polling interval per-operation:

// Default client polling: 2s
var client = VaultSandboxClientBuilder.Create()
.WithBaseUrl("https://smtp.vaultsandbox.com")
.WithApiKey(Environment.GetEnvironmentVariable("VAULTSANDBOX_API_KEY")!)
.UsePollingDelivery()
.WithPollInterval(TimeSpan.FromSeconds(2))
.Build();
var inbox = await client.CreateInboxAsync();
// Override for specific operation (faster polling)
var email = await inbox.WaitForEmailAsync(new WaitForEmailOptions
{
Timeout = TimeSpan.FromSeconds(30),
PollInterval = TimeSpan.FromSeconds(1) // Check every 1s for this operation
});

SSE is the default strategy and recommended for most use cases:

var client = VaultSandboxClientBuilder.Create()
.WithBaseUrl(Environment.GetEnvironmentVariable("VAULTSANDBOX_URL")!)
.WithApiKey(Environment.GetEnvironmentVariable("VAULTSANDBOX_API_KEY")!)
// UseSseDelivery() is the default, can be omitted
.Build();

Best for:

  • General testing
  • Local development
  • Real-time monitoring dashboards
  • High-volume email testing
  • Latency-sensitive tests

Note: If SSE fails to connect, an SseException will be thrown. Consider using polling if you’re in an environment where SSE may be blocked.

When compatibility is more important than speed:

var isCI = Environment.GetEnvironmentVariable("CI") == "true";
var client = VaultSandboxClientBuilder.Create()
.WithBaseUrl(Environment.GetEnvironmentVariable("VAULTSANDBOX_URL")!)
.WithApiKey(Environment.GetEnvironmentVariable("VAULTSANDBOX_API_KEY")!)
.UsePollingDelivery()
.WithPollInterval(isCI ? TimeSpan.FromSeconds(3) : TimeSpan.FromSeconds(1))
.Build();

Best for:

  • CI/CD environments (guaranteed to work)
  • Corporate networks with restrictive proxies
  • When SSE is known to be problematic
  • Rate-limited scenarios
appsettings.json
{
"VaultSandbox": {
"BaseUrl": "https://smtp.vaultsandbox.com",
"ApiKey": "your-api-key",
"DefaultDeliveryStrategy": "Sse",
"PollIntervalMs": 2000,
"SseReconnectIntervalMs": 2000,
"SseMaxReconnectAttempts": 10
}
}
// Program.cs or Startup.cs
services.AddVaultSandboxClient(options =>
{
options.BaseUrl = configuration["VaultSandbox:BaseUrl"]!;
options.ApiKey = configuration["VaultSandbox:ApiKey"]!;
options.DefaultDeliveryStrategy = Enum.Parse<DeliveryStrategy>(
configuration["VaultSandbox:DefaultDeliveryStrategy"] ?? "Sse");
options.PollIntervalMs = int.Parse(
configuration["VaultSandbox:PollIntervalMs"] ?? "2000");
options.SseReconnectIntervalMs = int.Parse(
configuration["VaultSandbox:SseReconnectIntervalMs"] ?? "2000");
options.SseMaxReconnectAttempts = int.Parse(
configuration["VaultSandbox:SseMaxReconnectAttempts"] ?? "10");
});
services.AddVaultSandboxClient();
services.Configure<VaultSandboxClientOptions>(
configuration.GetSection("VaultSandbox"));

Fast feedback with SSE (default):

appsettings.Development.json
{
"VaultSandbox": {
"BaseUrl": "http://localhost:3000",
"DefaultDeliveryStrategy": "Sse"
}
}
// Configuration helper
public static IVaultSandboxClient CreateClient(IConfiguration configuration)
{
var builder = VaultSandboxClientBuilder.Create()
.WithBaseUrl(configuration["VaultSandbox:BaseUrl"]!)
.WithApiKey(configuration["VaultSandbox:ApiKey"]!);
var strategy = configuration["VaultSandbox:DefaultDeliveryStrategy"];
return strategy switch
{
"Polling" => builder.UsePollingDelivery().Build(),
_ => builder.UseSseDelivery().Build() // SSE is default
};
}

Reliable polling:

appsettings.CI.json
{
"VaultSandbox": {
"BaseUrl": "https://smtp.vaultsandbox.com",
"DefaultDeliveryStrategy": "Polling",
"PollIntervalMs": 3000
}
}

SSE with tuned reconnection:

var client = VaultSandboxClientBuilder.Create()
.WithBaseUrl(Environment.GetEnvironmentVariable("VAULTSANDBOX_URL")!)
.WithApiKey(Environment.GetEnvironmentVariable("VAULTSANDBOX_API_KEY")!)
.UseSseDelivery()
.WithSseReconnectInterval(TimeSpan.FromSeconds(5))
.WithSseMaxReconnectAttempts(5)
.Build();
using VaultSandbox.Client.Exceptions;
try
{
var client = VaultSandboxClientBuilder.Create()
.WithBaseUrl(Environment.GetEnvironmentVariable("VAULTSANDBOX_URL")!)
.WithApiKey(Environment.GetEnvironmentVariable("VAULTSANDBOX_API_KEY")!)
.UseSseDelivery()
.Build();
var inbox = await client.CreateInboxAsync();
// Use inbox...
}
catch (SseException ex)
{
Console.WriteLine($"SSE failed: {ex.Message}");
Console.WriteLine("Falling back to polling...");
// Recreate with polling
var fallbackClient = VaultSandboxClientBuilder.Create()
.WithBaseUrl(Environment.GetEnvironmentVariable("VAULTSANDBOX_URL")!)
.WithApiKey(Environment.GetEnvironmentVariable("VAULTSANDBOX_API_KEY")!)
.UsePollingDelivery()
.Build();
var inbox = await fallbackClient.CreateInboxAsync();
// Continue with polling...
}
try
{
await foreach (var email in inbox.WatchAsync(cancellationToken))
{
await ProcessEmailAsync(email);
}
}
catch (SseException ex)
{
Console.WriteLine($"SSE error: {ex.Message}");
// Consider recreating client with polling if SSE is blocked
}
catch (StrategyException ex)
{
Console.WriteLine($"Strategy error: {ex.Message}");
}
catch (OperationCanceledException)
{
Console.WriteLine("Watch cancelled");
}

If emails arrive slowly with polling:

// Problem: Default 2s polling is too slow
var client = VaultSandboxClientBuilder.Create()
.WithBaseUrl(url)
.WithApiKey(apiKey)
.UsePollingDelivery()
.WithPollInterval(TimeSpan.FromSeconds(2))
.Build();
// Solution 1: Faster polling
var fasterClient = VaultSandboxClientBuilder.Create()
.WithBaseUrl(url)
.WithApiKey(apiKey)
.UsePollingDelivery()
.WithPollInterval(TimeSpan.FromMilliseconds(500))
.Build();
// Solution 2: Use SSE if available
var sseClient = VaultSandboxClientBuilder.Create()
.WithBaseUrl(url)
.WithApiKey(apiKey)
.UseSseDelivery()
.Build();
// Solution 3: Override poll interval for specific wait
var email = await inbox.WaitForEmailAsync(new WaitForEmailOptions
{
Timeout = TimeSpan.FromSeconds(10),
PollInterval = TimeSpan.FromMilliseconds(500) // Fast polling for this operation
});

SSE is the default and provides the best performance:

// Good: Use SSE (default)
var client = VaultSandboxClientBuilder.Create()
.WithBaseUrl(url)
.WithApiKey(apiKey)
.Build();
// Only specify polling when needed
var ciClient = VaultSandboxClientBuilder.Create()
.WithBaseUrl(url)
.WithApiKey(apiKey)
.UsePollingDelivery() // CI may need guaranteed compatibility
.Build();

Configure differently for each environment:

public static IVaultSandboxClient CreateClient(IConfiguration config)
{
var builder = VaultSandboxClientBuilder.Create()
.WithBaseUrl(config["VaultSandbox:BaseUrl"]!)
.WithApiKey(config["VaultSandbox:ApiKey"]!);
var isCI = Environment.GetEnvironmentVariable("CI") == "true";
if (isCI)
{
// CI: Use polling if SSE may be blocked
return builder
.UsePollingDelivery()
.WithPollInterval(TimeSpan.FromSeconds(3))
.Build();
}
else
{
// Default: SSE with tuned reconnection
return builder
.UseSseDelivery()
.WithSseReconnectInterval(TimeSpan.FromSeconds(2))
.WithSseMaxReconnectAttempts(10)
.Build();
}
}

Always have a fallback if forcing SSE:

async Task<IVaultSandboxClient> CreateClientWithFallbackAsync()
{
try
{
var client = VaultSandboxClientBuilder.Create()
.WithBaseUrl(url)
.WithApiKey(apiKey)
.UseSseDelivery()
.Build();
// Test SSE connectivity
var inbox = await client.CreateInboxAsync();
await client.DeleteInboxAsync(inbox.EmailAddress);
return client;
}
catch (SseException)
{
Console.WriteLine("SSE unavailable, using polling");
return VaultSandboxClientBuilder.Create()
.WithBaseUrl(url)
.WithApiKey(apiKey)
.UsePollingDelivery()
.Build();
}
}

Avoid very short polling intervals in production:

// Avoid: Too aggressive
var client = VaultSandboxClientBuilder.Create()
.WithBaseUrl(url)
.WithApiKey(apiKey)
.UsePollingDelivery()
.WithPollInterval(TimeSpan.FromMilliseconds(100)) // 100ms - too frequent!
.Build();
// Good: Reasonable interval
var client = VaultSandboxClientBuilder.Create()
.WithBaseUrl(url)
.WithApiKey(apiKey)
.UsePollingDelivery()
.WithPollInterval(TimeSpan.FromSeconds(2)) // 2s - balanced
.Build();

5. Use CancellationToken for Long-Running Operations

Section titled “5. Use CancellationToken for Long-Running Operations”

Always provide cancellation support for watch operations:

using var cts = new CancellationTokenSource();
// Cancel after timeout
cts.CancelAfter(TimeSpan.FromMinutes(5));
// Or cancel on Ctrl+C
Console.CancelKeyPress += (_, e) =>
{
e.Cancel = true;
cts.Cancel();
};
try
{
await foreach (var email in inbox.WatchAsync(cts.Token))
{
Console.WriteLine($"Received: {email.Subject}");
}
}
catch (OperationCanceledException)
{
Console.WriteLine("Watch operation cancelled");
}