Waiting for Emails
VaultSandbox provides powerful methods for waiting for emails with filtering and timeout support.
Basic Waiting
Section titled “Basic Waiting”Wait for Any Email
Section titled “Wait for Any Email”var email = await inbox.WaitForEmailAsync(new WaitForEmailOptions{ Timeout = TimeSpan.FromSeconds(30)});
Console.WriteLine($"Received: {email.Subject}");With Default Timeout
Section titled “With Default Timeout”// Uses default 30 second timeoutvar email = await inbox.WaitForEmailAsync();Filtering Options
Section titled “Filtering Options”Filter by Subject
Section titled “Filter by Subject”// Exact matchvar email = await inbox.WaitForEmailAsync(new WaitForEmailOptions{ Timeout = TimeSpan.FromSeconds(10), Subject = "Password Reset"});
// Regex matchvar email = await inbox.WaitForEmailAsync(new WaitForEmailOptions{ Timeout = TimeSpan.FromSeconds(10), Subject = "reset", UseRegex = true});Filter by Sender
Section titled “Filter by Sender”// Exact matchvar email = await inbox.WaitForEmailAsync(new WaitForEmailOptions{ Timeout = TimeSpan.FromSeconds(10),});
// Regex matchvar email = await inbox.WaitForEmailAsync(new WaitForEmailOptions{ Timeout = TimeSpan.FromSeconds(10), From = @"@example\.com$", UseRegex = true});Multiple Filters
Section titled “Multiple Filters”var email = await inbox.WaitForEmailAsync(new WaitForEmailOptions{ Timeout = TimeSpan.FromSeconds(10), Subject = "welcome", UseRegex = true,});Custom Predicate
Section titled “Custom Predicate”var email = await inbox.WaitForEmailAsync(new WaitForEmailOptions{ Timeout = TimeSpan.FromSeconds(10), && e.Links?.Count > 0 && e.Subject.Contains("Verify")});Waiting for Multiple Emails
Section titled “Waiting for Multiple Emails”Wait for Specific Count
Section titled “Wait for Specific Count”// Trigger multiple emailsawait SendNotificationsAsync(inbox.EmailAddress, 3);
// Wait for all 3 to arriveawait inbox.WaitForEmailCountAsync(3, new WaitForEmailCountOptions{ Timeout = TimeSpan.FromSeconds(30)});
// Now list all emailsvar emails = await inbox.GetEmailsAsync();Assert.Equal(3, emails.Count);Process as They Arrive
Section titled “Process as They Arrive”async Task<List<Email>> WaitForEmailsAsync(IInbox inbox, int count){ var emails = new List<Email>();
for (var i = 0; i < count; i++) { var email = await inbox.WaitForEmailAsync(new WaitForEmailOptions { Timeout = TimeSpan.FromSeconds(30) }); emails.Add(email); Console.WriteLine($"Received {i + 1}/{count}: {email.Subject}"); }
return emails;}
// Usagevar emails = await WaitForEmailsAsync(inbox, 3);Timeout Handling
Section titled “Timeout Handling”With Exception Handling
Section titled “With Exception Handling”try{ var email = await inbox.WaitForEmailAsync(new WaitForEmailOptions { Timeout = TimeSpan.FromSeconds(5) }); Console.WriteLine($"Email received: {email.Subject}");}catch (VaultSandboxTimeoutException){ Console.WriteLine("No email received within 5 seconds");}With CancellationToken
Section titled “With CancellationToken”using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30));
try{ var email = await inbox.WaitForEmailAsync(ct: cts.Token); Console.WriteLine($"Received: {email.Subject}");}catch (OperationCanceledException){ Console.WriteLine("Operation was cancelled");}With Fallback
Section titled “With Fallback”async Task<Email?> WaitForEmailWithFallbackAsync( IInbox inbox, WaitForEmailOptions options){ try { return await inbox.WaitForEmailAsync(options); } catch (VaultSandboxTimeoutException) { Console.WriteLine("Timeout! Checking if email arrived anyway"); var emails = await inbox.GetEmailsAsync(); return emails.LastOrDefault(); }}Retry Pattern
Section titled “Retry Pattern”async Task<Email> WaitWithRetryAsync( IInbox inbox, WaitForEmailOptions options, int maxRetries = 3){ for (var i = 0; i < maxRetries; i++) { try { return await inbox.WaitForEmailAsync(options); } catch (VaultSandboxTimeoutException) when (i < maxRetries - 1) { Console.WriteLine($"Attempt {i + 1} failed, retrying..."); } }
throw new VaultSandboxTimeoutException("Max retries exceeded", options.Timeout ?? TimeSpan.FromSeconds(30));}Polling Configuration
Section titled “Polling Configuration”Custom Poll Interval
Section titled “Custom Poll Interval”// Poll every 500ms (more responsive)var email = await inbox.WaitForEmailAsync(new WaitForEmailOptions{ Timeout = TimeSpan.FromSeconds(10), PollInterval = TimeSpan.FromMilliseconds(500)});
// Poll every 5 seconds (less frequent)var email = await inbox.WaitForEmailAsync(new WaitForEmailOptions{ Timeout = TimeSpan.FromMinutes(1), PollInterval = TimeSpan.FromSeconds(5)});Efficient Polling
Section titled “Efficient Polling”// For quick tests - poll frequentlyvar email = await inbox.WaitForEmailAsync(new WaitForEmailOptions{ Timeout = TimeSpan.FromSeconds(5), PollInterval = TimeSpan.FromMilliseconds(200)});
// For slow email services - poll less frequentlyvar email = await inbox.WaitForEmailAsync(new WaitForEmailOptions{ Timeout = TimeSpan.FromMinutes(2), PollInterval = TimeSpan.FromSeconds(10)});Real-World Examples
Section titled “Real-World Examples”Password Reset Flow
Section titled “Password Reset Flow”[Fact]public async Task Password_Reset_Email_Contains_Valid_Link(){ // Trigger reset await _app.RequestPasswordResetAsync(inbox.EmailAddress);
// Wait for reset email var email = await inbox.WaitForEmailAsync(new WaitForEmailOptions { Timeout = TimeSpan.FromSeconds(10), Subject = "reset", UseRegex = true, });
// Validate content Assert.Contains("Password Reset", email.Subject); Assert.NotEmpty(email.Links);
// Extract and test link var resetLink = email.Links.FirstOrDefault(url => url.Contains("/reset")); Assert.NotNull(resetLink);}Welcome Email with Verification
Section titled “Welcome Email with Verification”[Fact]public async Task Welcome_Email_With_Verification_Link(){ // Sign up await _app.SignupAsync(new SignupRequest { Email = inbox.EmailAddress, Name = "Test User" });
// Wait for welcome email with verification link var email = await inbox.WaitForEmailAsync(new WaitForEmailOptions { Timeout = TimeSpan.FromSeconds(10), Subject = "welcome", UseRegex = true, Predicate = e => e.Links?.Any(link => link.Contains("/verify")) == true });
// Extract verification link var verifyLink = email.Links.First(url => url.Contains("/verify"));
// Test verification using var httpClient = new HttpClient(); var response = await httpClient.GetAsync(verifyLink); Assert.True(response.IsSuccessStatusCode);}Multi-Step Email Flow
Section titled “Multi-Step Email Flow”[Fact]public async Task Order_Confirmation_And_Shipping_Notification(){ // Place order var orderId = await _app.PlaceOrderAsync(new OrderRequest { Email = inbox.EmailAddress, Items = ["widget"] });
// Wait for confirmation var confirmation = await inbox.WaitForEmailAsync(new WaitForEmailOptions { Timeout = TimeSpan.FromSeconds(10), Subject = @"order.*confirmed", UseRegex = true });
Assert.Contains("Order Confirmed", confirmation.Subject); Assert.Contains("Order #", confirmation.Text);
// Simulate shipping await _app.ShipOrderAsync(orderId);
// Wait for shipping notification var shipping = await inbox.WaitForEmailAsync(new WaitForEmailOptions { Timeout = TimeSpan.FromSeconds(10), Subject = "shipped", UseRegex = true });
Assert.Contains("Shipped", shipping.Subject); Assert.Contains("tracking", shipping.Text, StringComparison.OrdinalIgnoreCase);}Email with Attachments
Section titled “Email with Attachments”[Fact]public async Task Invoice_Email_With_Pdf_Attachment(){ await _app.SendInvoiceAsync(inbox.EmailAddress);
var email = await inbox.WaitForEmailAsync(new WaitForEmailOptions { Timeout = TimeSpan.FromSeconds(10), Subject = "invoice", UseRegex = true, Predicate = e => e.Attachments?.Count > 0 });
// Validate attachment var pdf = email.Attachments?.FirstOrDefault( att => att.ContentType == "application/pdf");
Assert.NotNull(pdf); Assert.Matches(@"(?i)invoice.*\.pdf", pdf.Filename); Assert.True(pdf.Size > 0);}Advanced Patterns
Section titled “Advanced Patterns”Wait for First Matching Email
Section titled “Wait for First Matching Email”async Task<Email> WaitForFirstMatchAsync( IInbox inbox, IEnumerable<Func<Email, bool>> matchers, TimeSpan timeout){ var startTime = DateTime.UtcNow;
while (DateTime.UtcNow - startTime < timeout) { var emails = await inbox.GetEmailsAsync();
foreach (var matcher in matchers) { var match = emails.FirstOrDefault(matcher); if (match != null) return match; }
await Task.Delay(TimeSpan.FromSeconds(1)); }
throw new VaultSandboxTimeoutException("No matching email found", timeout);}
// Usagevar email = await WaitForFirstMatchAsync(inbox,[ e => e.Subject.Contains("Welcome"), e => e.Subject.Contains("Verify"),], TimeSpan.FromSeconds(30));Conditional Waiting
Section titled “Conditional Waiting”async Task<Email> WaitConditionallyAsync( IInbox inbox, WaitForEmailOptions options){ // First check if email already exists var existing = await inbox.GetEmailsAsync(); var match = existing.FirstOrDefault(e => e.Subject.Contains(options.Subject ?? "", StringComparison.OrdinalIgnoreCase));
if (match != null) { Console.WriteLine("Email already present"); return match; }
// Wait for new email Console.WriteLine("Waiting for email..."); return await inbox.WaitForEmailAsync(options);}Testing Patterns
Section titled “Testing Patterns”Flake-Free Tests
Section titled “Flake-Free Tests”// Good: Use WaitForEmailAsync, not Task.Delay[Fact]public async Task Receives_Email(){ await SendEmailAsync(inbox.EmailAddress); var email = await inbox.WaitForEmailAsync(new WaitForEmailOptions { Timeout = TimeSpan.FromSeconds(10) }); Assert.NotNull(email);}
// Bad: Arbitrary delay causes flakiness[Fact]public async Task Receives_Email_Bad(){ await SendEmailAsync(inbox.EmailAddress); await Task.Delay(5000); // May not be enough, or wastes time var emails = await inbox.GetEmailsAsync(); Assert.Single(emails);}Fast Tests
Section titled “Fast Tests”// Good: Short timeout for fast-sending systemsvar email = await inbox.WaitForEmailAsync(new WaitForEmailOptions{ Timeout = TimeSpan.FromSeconds(2)});
// Bad: Unnecessarily long timeout slows testsvar email = await inbox.WaitForEmailAsync(new WaitForEmailOptions{ Timeout = TimeSpan.FromMinutes(1)});Parallel Email Tests
Section titled “Parallel Email Tests”[Fact]public async Task Multiple_Users_Receive_Emails(){ var inbox1 = await _client.CreateInboxAsync(); var inbox2 = await _client.CreateInboxAsync();
try { // Send emails await Task.WhenAll( SendWelcomeAsync(inbox1.EmailAddress), SendWelcomeAsync(inbox2.EmailAddress));
// Wait in parallel var results = await Task.WhenAll( inbox1.WaitForEmailAsync(new WaitForEmailOptions { Timeout = TimeSpan.FromSeconds(10) }), inbox2.WaitForEmailAsync(new WaitForEmailOptions { Timeout = TimeSpan.FromSeconds(10) }));
Assert.Contains("Welcome", results[0].Subject); Assert.Contains("Welcome", results[1].Subject); } finally { await Task.WhenAll( _client.DeleteInboxAsync(inbox1.EmailAddress), _client.DeleteInboxAsync(inbox2.EmailAddress)); }}Troubleshooting
Section titled “Troubleshooting”Email Not Arriving
Section titled “Email Not Arriving”try{ Console.WriteLine("Waiting for email..."); var email = await inbox.WaitForEmailAsync(new WaitForEmailOptions { Timeout = TimeSpan.FromSeconds(10), Subject = "test", UseRegex = true }); Console.WriteLine($"Received: {email.Subject}");}catch (VaultSandboxTimeoutException){ Console.WriteLine("Timeout! Checking inbox manually:"); var emails = await inbox.GetEmailsAsync(); Console.WriteLine($"Found {emails.Count} emails:"); foreach (var e in emails) { Console.WriteLine($" - {e.Subject}"); } throw;}Filter Not Matching
Section titled “Filter Not Matching”var email = await inbox.WaitForEmailAsync(new WaitForEmailOptions{ Timeout = TimeSpan.FromSeconds(10), Predicate = e => { var matches = e.Subject.Contains("Test"); if (!matches) { Console.WriteLine($"Subject \"{e.Subject}\" doesn't match"); } return matches; }});Next Steps
Section titled “Next Steps”- Managing Inboxes - Learn inbox operations
- Real-time Monitoring - Subscribe to emails as they arrive
- Testing Patterns - Real-world testing examples
- API Reference: Inbox - Complete API documentation