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”ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)defer cancel()
email, err := inbox.WaitForEmail(ctx)if err != nil { log.Fatal(err)}
fmt.Println("Received:", email.Subject)With Explicit Timeout Option
Section titled “With Explicit Timeout Option”email, err := inbox.WaitForEmail(ctx, vaultsandbox.WithWaitTimeout(30*time.Second),)if err != nil { log.Fatal(err)}Filtering Options
Section titled “Filtering Options”Filter by Subject
Section titled “Filter by Subject”// Exact matchemail, err := inbox.WaitForEmail(ctx, vaultsandbox.WithWaitTimeout(10*time.Second), vaultsandbox.WithSubject("Password Reset"),)
// Regex matchemail, err := inbox.WaitForEmail(ctx, vaultsandbox.WithWaitTimeout(10*time.Second), vaultsandbox.WithSubjectRegex(regexp.MustCompile(`(?i)reset`)), // Case-insensitive)Filter by Sender
Section titled “Filter by Sender”// Exact matchemail, err := inbox.WaitForEmail(ctx, vaultsandbox.WithWaitTimeout(10*time.Second),)
// Regex matchemail, err := inbox.WaitForEmail(ctx, vaultsandbox.WithWaitTimeout(10*time.Second), vaultsandbox.WithFromRegex(regexp.MustCompile(`@example\.com$`)), // Any @example.com address)Multiple Filters
Section titled “Multiple Filters”email, err := inbox.WaitForEmail(ctx, vaultsandbox.WithWaitTimeout(10*time.Second), vaultsandbox.WithSubjectRegex(regexp.MustCompile(`(?i)welcome`)),)Custom Predicate
Section titled “Custom Predicate”email, err := inbox.WaitForEmail(ctx, vaultsandbox.WithWaitTimeout(10*time.Second), vaultsandbox.WithPredicate(func(email *vaultsandbox.Email) bool { // Custom logic hasRecipient := false for _, to := range email.To { hasRecipient = true break } } return hasRecipient && len(email.Links) > 0 && strings.Contains(email.Subject, "Verify") }),)Waiting for Multiple Emails
Section titled “Waiting for Multiple Emails”Wait for Specific Count
Section titled “Wait for Specific Count”// Trigger multiple emailssendNotifications(inbox.EmailAddress(), 3)
// Wait for all 3 to arriveemails, err := inbox.WaitForEmailCount(ctx, 3, vaultsandbox.WithWaitTimeout(30*time.Second),)if err != nil { log.Fatal(err)}
fmt.Printf("Received %d emails\n", len(emails))Process as They Arrive
Section titled “Process as They Arrive”func waitForEmails(ctx context.Context, inbox *vaultsandbox.Inbox, count int) ([]*vaultsandbox.Email, error) { emails := make([]*vaultsandbox.Email, 0, count)
for i := 0; i < count; i++ { email, err := inbox.WaitForEmail(ctx, vaultsandbox.WithWaitTimeout(30*time.Second), ) if err != nil { return emails, err } emails = append(emails, email) fmt.Printf("Received %d/%d: %s\n", i+1, count, email.Subject) }
return emails, nil}
// Usageemails, err := waitForEmails(ctx, inbox, 3)Tip: For real-time processing without specifying a count, consider using
inbox.Watch(ctx)which returns a channel. See Real-time Monitoring.
Timeout Handling
Section titled “Timeout Handling”With Error Handling
Section titled “With Error Handling”ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)defer cancel()
email, err := inbox.WaitForEmail(ctx)if err != nil { if errors.Is(err, context.DeadlineExceeded) { fmt.Println("No email received within 5 seconds") } else { log.Fatal(err) }}
if email != nil { fmt.Println("Email received:", email.Subject)}With Fallback
Section titled “With Fallback”func waitForEmailWithFallback(ctx context.Context, inbox *vaultsandbox.Inbox, opts ...vaultsandbox.WaitOption) (*vaultsandbox.Email, error) { email, err := inbox.WaitForEmail(ctx, opts...) if err != nil { if errors.Is(err, context.DeadlineExceeded) { fmt.Println("Timeout, checking if email arrived anyway") emails, listErr := inbox.GetEmails(ctx) if listErr != nil { return nil, listErr } if len(emails) > 0 { return emails[len(emails)-1], nil // Return latest } } return nil, err } return email, nil}Retry Pattern
Section titled “Retry Pattern”func waitWithRetry(ctx context.Context, inbox *vaultsandbox.Inbox, maxRetries int, opts ...vaultsandbox.WaitOption) (*vaultsandbox.Email, error) { var lastErr error for i := 0; i < maxRetries; i++ { email, err := inbox.WaitForEmail(ctx, opts...) if err == nil { return email, nil } lastErr = err if errors.Is(err, context.DeadlineExceeded) && i < maxRetries-1 { fmt.Printf("Attempt %d failed, retrying...\n", i+1) continue } break } return nil, lastErr}Real-World Examples
Section titled “Real-World Examples”Password Reset Flow
Section titled “Password Reset Flow”func TestPasswordResetEmail(t *testing.T) { ctx := context.Background() inbox, _ := client.CreateInbox(ctx) defer inbox.Delete(ctx)
// Trigger reset app.RequestPasswordReset(inbox.EmailAddress())
// Wait for reset email email, err := inbox.WaitForEmail(ctx, vaultsandbox.WithWaitTimeout(10*time.Second), vaultsandbox.WithSubjectRegex(regexp.MustCompile(`(?i)reset`)), ) require.NoError(t, err)
// Validate content assert.Contains(t, email.Subject, "Password Reset") assert.Greater(t, len(email.Links), 0)
// Extract and test link var resetLink string for _, link := range email.Links { if strings.Contains(link, "/reset") { resetLink = link break } } assert.NotEmpty(t, resetLink)}Welcome Email with Verification
Section titled “Welcome Email with Verification”func TestWelcomeEmailWithVerification(t *testing.T) { ctx := context.Background() inbox, _ := client.CreateInbox(ctx) defer inbox.Delete(ctx)
// Sign up app.Signup(SignupRequest{ Email: inbox.EmailAddress(), Name: "Test User", })
// Wait for welcome email email, err := inbox.WaitForEmail(ctx, vaultsandbox.WithWaitTimeout(10*time.Second), vaultsandbox.WithSubjectRegex(regexp.MustCompile(`(?i)welcome`)), vaultsandbox.WithPredicate(func(e *vaultsandbox.Email) bool { for _, link := range e.Links { if strings.Contains(link, "/verify") { return true } } return false }), ) require.NoError(t, err)
// Extract verification link var verifyLink string for _, link := range email.Links { if strings.Contains(link, "/verify") { verifyLink = link break } }
// Test verification resp, err := http.Get(verifyLink) require.NoError(t, err) assert.Equal(t, http.StatusOK, resp.StatusCode)}Multi-Step Email Flow
Section titled “Multi-Step Email Flow”func TestOrderConfirmationAndShipping(t *testing.T) { ctx := context.Background() inbox, _ := client.CreateInbox(ctx) defer inbox.Delete(ctx)
// Place order orderId := app.PlaceOrder(OrderRequest{ Email: inbox.EmailAddress(), Items: []string{"widget"}, })
// Wait for confirmation confirmation, err := inbox.WaitForEmail(ctx, vaultsandbox.WithWaitTimeout(10*time.Second), vaultsandbox.WithSubjectRegex(regexp.MustCompile(`(?i)order.*confirmed`)), ) require.NoError(t, err)
assert.Contains(t, confirmation.Subject, "Order Confirmed") assert.Contains(t, confirmation.Text, "Order #")
// Simulate shipping app.ShipOrder(orderId)
// Wait for shipping notification shipping, err := inbox.WaitForEmail(ctx, vaultsandbox.WithWaitTimeout(10*time.Second), vaultsandbox.WithSubjectRegex(regexp.MustCompile(`(?i)shipped`)), ) require.NoError(t, err)
assert.Contains(t, shipping.Subject, "Shipped") assert.Contains(t, shipping.Text, "tracking")}Email with Attachments
Section titled “Email with Attachments”func TestInvoiceWithPDFAttachment(t *testing.T) { ctx := context.Background() inbox, _ := client.CreateInbox(ctx) defer inbox.Delete(ctx)
app.SendInvoice(inbox.EmailAddress())
email, err := inbox.WaitForEmail(ctx, vaultsandbox.WithWaitTimeout(10*time.Second), vaultsandbox.WithSubjectRegex(regexp.MustCompile(`(?i)invoice`)), vaultsandbox.WithPredicate(func(e *vaultsandbox.Email) bool { return len(e.Attachments) > 0 }), ) require.NoError(t, err)
// Find PDF attachment var pdf *vaultsandbox.Attachment for i := range email.Attachments { if email.Attachments[i].ContentType == "application/pdf" { pdf = &email.Attachments[i] break } }
require.NotNil(t, pdf) assert.Regexp(t, regexp.MustCompile(`(?i)invoice.*\.pdf`), pdf.Filename) assert.Greater(t, pdf.Size, 0)}Advanced Patterns
Section titled “Advanced Patterns”Wait for First Matching Email
Section titled “Wait for First Matching Email”func waitForFirstMatch(ctx context.Context, inbox *vaultsandbox.Inbox, matchers []func(*vaultsandbox.Email) bool, timeout time.Duration) (*vaultsandbox.Email, error) { ctx, cancel := context.WithTimeout(ctx, timeout) defer cancel()
ticker := time.NewTicker(1 * time.Second) defer ticker.Stop()
for { select { case <-ctx.Done(): return nil, fmt.Errorf("no matching email found: %w", ctx.Err()) case <-ticker.C: emails, err := inbox.GetEmails(ctx) if err != nil { return nil, err }
for _, matcher := range matchers { for _, email := range emails { if matcher(email) { return email, nil } } } } }}
// Usageemail, err := waitForFirstMatch(ctx, inbox, []func(*vaultsandbox.Email) bool{ func(e *vaultsandbox.Email) bool { return strings.Contains(e.Subject, "Welcome") }, func(e *vaultsandbox.Email) bool { return strings.Contains(e.Subject, "Verify") },}, 30*time.Second)Wait with Progress Callback
Section titled “Wait with Progress Callback”type WaitProgress struct { Attempts int Elapsed time.Duration TimedOut bool}
func waitWithProgress(ctx context.Context, inbox *vaultsandbox.Inbox, timeout time.Duration, onProgress func(WaitProgress)) (*vaultsandbox.Email, error) { startTime := time.Now() attempts := 0
ctx, cancel := context.WithTimeout(ctx, timeout) defer cancel()
ticker := time.NewTicker(1 * time.Second) defer ticker.Stop()
for { select { case <-ctx.Done(): onProgress(WaitProgress{ Attempts: attempts, Elapsed: time.Since(startTime), TimedOut: true, }) return nil, ctx.Err() case <-ticker.C: attempts++ onProgress(WaitProgress{ Attempts: attempts, Elapsed: time.Since(startTime), TimedOut: false, })
emails, err := inbox.GetEmails(ctx) if err != nil { return nil, err } if len(emails) > 0 { return emails[0], nil } } }}
// Usageemail, err := waitWithProgress(ctx, inbox, 10*time.Second, func(p WaitProgress) { fmt.Printf("Attempt %d, %v elapsed\n", p.Attempts, p.Elapsed)})Conditional Waiting
Section titled “Conditional Waiting”func waitConditionally(ctx context.Context, inbox *vaultsandbox.Inbox, subjectPattern *regexp.Regexp) (*vaultsandbox.Email, error) { // First check if email already exists existing, err := inbox.GetEmails(ctx) if err != nil { return nil, err }
for _, email := range existing { if subjectPattern.MatchString(email.Subject) { fmt.Println("Email already present") return email, nil } }
// Wait for new email fmt.Println("Waiting for email...") return inbox.WaitForEmail(ctx, vaultsandbox.WithSubjectRegex(subjectPattern), )}Testing Patterns
Section titled “Testing Patterns”Flake-Free Tests
Section titled “Flake-Free Tests”// Good: Use WaitForEmail, not sleepfunc TestReceivesEmail(t *testing.T) { ctx := context.Background() inbox, _ := client.CreateInbox(ctx) defer inbox.Delete(ctx)
sendEmail(inbox.EmailAddress())
email, err := inbox.WaitForEmail(ctx, vaultsandbox.WithWaitTimeout(10*time.Second), ) require.NoError(t, err) assert.NotNil(t, email)}
// Bad: Arbitrary sleep causes flakinessfunc TestReceivesEmailBad(t *testing.T) { ctx := context.Background() inbox, _ := client.CreateInbox(ctx) defer inbox.Delete(ctx)
sendEmail(inbox.EmailAddress()) time.Sleep(5 * time.Second) // May not be enough, or wastes time
emails, _ := inbox.GetEmails(ctx) assert.Equal(t, 1, len(emails))}Fast Tests
Section titled “Fast Tests”// Good: Short timeout for fast-sending systemsemail, err := inbox.WaitForEmail(ctx, vaultsandbox.WithWaitTimeout(2*time.Second),)
// Bad: Unnecessarily long timeout slows testsemail, err := inbox.WaitForEmail(ctx, vaultsandbox.WithWaitTimeout(60*time.Second),)Parallel Email Tests
Section titled “Parallel Email Tests”func TestMultipleUsersReceiveEmails(t *testing.T) { ctx := context.Background() inbox1, _ := client.CreateInbox(ctx) inbox2, _ := client.CreateInbox(ctx) defer inbox1.Delete(ctx) defer inbox2.Delete(ctx)
// Send emails concurrently var wg sync.WaitGroup wg.Add(2) go func() { defer wg.Done() sendWelcome(inbox1.EmailAddress()) }() go func() { defer wg.Done() sendWelcome(inbox2.EmailAddress()) }() wg.Wait()
// Wait in parallel var email1, email2 *vaultsandbox.Email var err1, err2 error
wg.Add(2) go func() { defer wg.Done() email1, err1 = inbox1.WaitForEmail(ctx, vaultsandbox.WithWaitTimeout(10*time.Second), ) }() go func() { defer wg.Done() email2, err2 = inbox2.WaitForEmail(ctx, vaultsandbox.WithWaitTimeout(10*time.Second), ) }() wg.Wait()
require.NoError(t, err1) require.NoError(t, err2) assert.Contains(t, email1.Subject, "Welcome") assert.Contains(t, email2.Subject, "Welcome")}Using errgroup for Cleaner Parallel Tests
Section titled “Using errgroup for Cleaner Parallel Tests”func TestMultipleUsersWithErrgroup(t *testing.T) { ctx := context.Background() inbox1, _ := client.CreateInbox(ctx) inbox2, _ := client.CreateInbox(ctx) defer inbox1.Delete(ctx) defer inbox2.Delete(ctx)
// Send emails sendWelcome(inbox1.EmailAddress()) sendWelcome(inbox2.EmailAddress())
// Wait in parallel with errgroup g, ctx := errgroup.WithContext(ctx)
var email1, email2 *vaultsandbox.Email
g.Go(func() error { var err error email1, err = inbox1.WaitForEmail(ctx, vaultsandbox.WithWaitTimeout(10*time.Second), ) return err })
g.Go(func() error { var err error email2, err = inbox2.WaitForEmail(ctx, vaultsandbox.WithWaitTimeout(10*time.Second), ) return err })
require.NoError(t, g.Wait()) assert.Contains(t, email1.Subject, "Welcome") assert.Contains(t, email2.Subject, "Welcome")}Troubleshooting
Section titled “Troubleshooting”Email Not Arriving
Section titled “Email Not Arriving”// Add debug loggingemail, err := inbox.WaitForEmail(ctx, vaultsandbox.WithWaitTimeout(10*time.Second), vaultsandbox.WithSubjectRegex(regexp.MustCompile(`(?i)test`)),)if err != nil { fmt.Println("Timeout! Checking inbox manually:") emails, listErr := inbox.GetEmails(ctx) if listErr != nil { log.Fatal(listErr) } fmt.Printf("Found %d emails:\n", len(emails)) for _, e := range emails { fmt.Printf(" - %s\n", e.Subject) } log.Fatal(err)}
fmt.Println("Received:", email.Subject)Filter Not Matching
Section titled “Filter Not Matching”// Log filter mismatchesemail, err := inbox.WaitForEmail(ctx, vaultsandbox.WithWaitTimeout(10*time.Second), vaultsandbox.WithPredicate(func(e *vaultsandbox.Email) bool { matches := strings.Contains(e.Subject, "Test") if !matches { fmt.Printf("Subject %q doesn't match\n", e.Subject) } 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