Skip to content

Waiting for Emails

VaultSandbox provides powerful methods for waiting for emails with filtering and timeout support.

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)
email, err := inbox.WaitForEmail(ctx,
vaultsandbox.WithWaitTimeout(30*time.Second),
)
if err != nil {
log.Fatal(err)
}
// Exact match
email, err := inbox.WaitForEmail(ctx,
vaultsandbox.WithWaitTimeout(10*time.Second),
vaultsandbox.WithSubject("Password Reset"),
)
// Regex match
email, err := inbox.WaitForEmail(ctx,
vaultsandbox.WithWaitTimeout(10*time.Second),
vaultsandbox.WithSubjectRegex(regexp.MustCompile(`(?i)reset`)), // Case-insensitive
)
// Exact match
email, err := inbox.WaitForEmail(ctx,
vaultsandbox.WithWaitTimeout(10*time.Second),
vaultsandbox.WithFrom("[email protected]"),
)
// Regex match
email, err := inbox.WaitForEmail(ctx,
vaultsandbox.WithWaitTimeout(10*time.Second),
vaultsandbox.WithFromRegex(regexp.MustCompile(`@example\.com$`)), // Any @example.com address
)
email, err := inbox.WaitForEmail(ctx,
vaultsandbox.WithWaitTimeout(10*time.Second),
vaultsandbox.WithSubjectRegex(regexp.MustCompile(`(?i)welcome`)),
vaultsandbox.WithFrom("[email protected]"),
)
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 {
if to == "[email protected]" {
hasRecipient = true
break
}
}
return hasRecipient &&
len(email.Links) > 0 &&
strings.Contains(email.Subject, "Verify")
}),
)
// Trigger multiple emails
sendNotifications(inbox.EmailAddress(), 3)
// Wait for all 3 to arrive
emails, err := inbox.WaitForEmailCount(ctx, 3,
vaultsandbox.WithWaitTimeout(30*time.Second),
)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Received %d emails\n", len(emails))
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
}
// Usage
emails, 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.

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)
}
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
}
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
}
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`)),
vaultsandbox.WithFrom("[email protected]"),
)
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)
}
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)
}
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")
}
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)
}
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
}
}
}
}
}
}
// Usage
email, 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") },
func(e *vaultsandbox.Email) bool { return e.From == "[email protected]" },
}, 30*time.Second)
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
}
}
}
}
// Usage
email, err := waitWithProgress(ctx, inbox, 10*time.Second, func(p WaitProgress) {
fmt.Printf("Attempt %d, %v elapsed\n", p.Attempts, p.Elapsed)
})
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),
)
}
// Good: Use WaitForEmail, not sleep
func 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 flakiness
func 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))
}
// Good: Short timeout for fast-sending systems
email, err := inbox.WaitForEmail(ctx,
vaultsandbox.WithWaitTimeout(2*time.Second),
)
// Bad: Unnecessarily long timeout slows tests
email, err := inbox.WaitForEmail(ctx,
vaultsandbox.WithWaitTimeout(60*time.Second),
)
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")
}
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")
}
// Add debug logging
email, 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)
// Log filter mismatches
email, 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
}),
)