Email Objects
Email objects in VaultSandbox represent decrypted emails with all their content, headers, and metadata.
Email Structure
Section titled “Email Structure”email, err := inbox.WaitForEmail(ctx, vaultsandbox.WithWaitTimeout(30*time.Second))if err != nil { log.Fatal(err)}
fmt.Println(email.ID) // "email_abc123"fmt.Println(email.Subject) // "Welcome to our service"fmt.Println(email.Text) // Plain text contentfmt.Println(email.HTML) // HTML contentfmt.Println(email.ReceivedAt) // time.Timefmt.Println(email.IsRead) // falsefmt.Println(email.Links) // ["https://example.com/verify"]fmt.Println(email.Attachments) // []Attachmentfmt.Println(email.AuthResults) // SPF/DKIM/DMARC resultsCore Properties
Section titled “Core Properties”Type: string
Unique identifier for the email.
emailID := email.ID// Later...sameEmail, err := inbox.GetEmail(ctx, emailID)Type: string
Sender’s email address (from the From header).
// Use in assertions t.Error("unexpected sender")}Type: []string
Slice of recipient email addresses.
// Multiple recipients
// Check if sent to specific addressif !slices.Contains(email.To, inbox.EmailAddress()) { t.Error("inbox address not in recipients")}Subject
Section titled “Subject”Type: string
Email subject line.
fmt.Println(email.Subject) // "Password Reset Request"
// Use in filteringemail, err := inbox.WaitForEmail(ctx, vaultsandbox.WithSubjectRegex(regexp.MustCompile(`Password Reset`)),)Type: string
Plain text content of the email. Empty string if email is HTML-only.
fmt.Println(email.Text)// "Hello,\n\nClick here to reset your password:\nhttps://..."
// May be empty if email is HTML-onlyif email.Text != "" { if !strings.Contains(email.Text, "reset your password") { t.Error("expected reset text") }}Type: string
HTML content of the email. Empty string if email is plain text only.
fmt.Println(email.HTML)// "<html><body><p>Hello,</p><a href='https://...'>Reset Password</a></body></html>"
// May be empty if email is plain text onlyif email.HTML != "" { if !strings.Contains(email.HTML, "<a href") { t.Error("expected link in HTML") }}ReceivedAt
Section titled “ReceivedAt”Type: time.Time
When the email was received by the gateway.
fmt.Println(email.ReceivedAt) // 2024-01-15 12:00:00 +0000 UTC
// Check if email arrived recentlyage := time.Since(email.ReceivedAt)if age > time.Minute { t.Error("email took too long to arrive")}IsRead
Section titled “IsRead”Type: bool
Whether the email has been marked as read.
fmt.Println(email.IsRead) // false
if err := inbox.MarkEmailAsRead(ctx, email.ID); err != nil { log.Fatal(err)}
// Refetch to see updated statusupdated, _ := inbox.GetEmail(ctx, email.ID)fmt.Println(updated.IsRead) // trueType: []string
All URLs extracted from the email (text and HTML).
fmt.Println(email.Links)// [// "https://example.com/verify?token=abc123",// "https://example.com/unsubscribe",// "https://example.com/privacy"// ]
// Find specific linkvar verifyLink stringfor _, link := range email.Links { if strings.Contains(link, "/verify") { verifyLink = link break }}if verifyLink == "" { t.Fatal("verify link not found")}
// Test linkresp, err := http.Get(verifyLink)if err != nil { t.Fatal(err)}defer resp.Body.Close()if resp.StatusCode != http.StatusOK { t.Errorf("expected 200, got %d", resp.StatusCode)}Attachments
Section titled “Attachments”Type: []Attachment
Slice of email attachments.
fmt.Println(len(email.Attachments)) // 2
for _, att := range email.Attachments { fmt.Println(att.Filename) // "invoice.pdf" fmt.Println(att.ContentType) // "application/pdf" fmt.Println(att.Size) // 15234 bytes fmt.Println(len(att.Content)) // byte slice length}See Working with Attachments for details.
AuthResults
Section titled “AuthResults”Type: *authresults.AuthResults
Email authentication results (SPF, DKIM, DMARC, reverse DNS).
auth := email.AuthResults
if auth.SPF != nil { fmt.Println(auth.SPF.Result) // "pass"}fmt.Println(len(auth.DKIM)) // 1if auth.DMARC != nil { fmt.Println(auth.DMARC.Result) // "pass"}
// Validate all checksvalidation := auth.Validate()if !validation.Passed { fmt.Println("Authentication failed:", validation.Failures)}See Authentication Results for details.
Headers
Section titled “Headers”Type: map[string]string
All email headers as a key-value map.
fmt.Println(email.Headers)// map[// "from": "[email protected]",// "to": "[email protected]",// "subject": "Welcome",// "message-id": "<[email protected]>",// "date": "Mon, 15 Jan 2024 12:00:00 +0000",// "content-type": "text/html; charset=utf-8",// ...// ]
// Access specific headersmessageID := email.Headers["message-id"]contentType := email.Headers["content-type"]Email Operations
Section titled “Email Operations”Email is a pure data struct with no methods. Use Inbox methods to perform operations on emails:
inbox.GetRawEmail(ctx, emailID)— Gets raw email source (RFC 5322 format)inbox.MarkEmailAsRead(ctx, emailID)— Marks email as readinbox.DeleteEmail(ctx, emailID)— Deletes an email
MarkEmailAsRead
Section titled “MarkEmailAsRead”Mark an email as read.
func (i *Inbox) MarkEmailAsRead(ctx context.Context, emailID string) errorif err := inbox.MarkEmailAsRead(ctx, email.ID); err != nil { log.Fatal(err)}DeleteEmail
Section titled “DeleteEmail”Delete an email from the inbox.
func (i *Inbox) DeleteEmail(ctx context.Context, emailID string) errorif err := inbox.DeleteEmail(ctx, email.ID); err != nil { log.Fatal(err)}
// Email is now deleted_, err := inbox.GetEmail(ctx, email.ID)if errors.Is(err, vaultsandbox.ErrEmailNotFound) { fmt.Println("Email deleted")}GetRawEmail
Section titled “GetRawEmail”Get the raw email source (decrypted MIME).
func (i *Inbox) GetRawEmail(ctx context.Context, emailID string) (string, error)raw, err := inbox.GetRawEmail(ctx, email.ID)if err != nil { log.Fatal(err)}
fmt.Println(raw)// "From: [email protected]\r\nTo: [email protected]\r\n..."Attachment Structure
Section titled “Attachment Structure”type Attachment struct { Filename string // The attachment's filename ContentType string // MIME type (e.g., "application/pdf") Size int // Size in bytes ContentID string // Content-ID for inline attachments ContentDisposition string // "inline" or "attachment" Content []byte // Raw attachment data Checksum string // SHA-256 hash for integrity verification}Common Patterns
Section titled “Common Patterns”Content Validation
Section titled “Content Validation”email, err := inbox.WaitForEmail(ctx, vaultsandbox.WithSubjectRegex(regexp.MustCompile(`Welcome`)), vaultsandbox.WithWaitTimeout(10*time.Second),)if err != nil { t.Fatal(err)}
// Validate sender t.Errorf("unexpected sender: %s", email.From)}
// Validate contentif !strings.Contains(email.Text, "Thank you for signing up") { t.Error("missing welcome text")}if !strings.Contains(email.HTML, "<h1>Welcome</h1>") { t.Error("missing welcome heading")}
// Validate linksvar verifyLink stringfor _, link := range email.Links { if strings.Contains(link, "/verify") { verifyLink = link break }}if verifyLink == "" { t.Fatal("verify link not found")}if !strings.HasPrefix(verifyLink, "https://") { t.Error("verify link should use HTTPS")}Link Extraction and Testing
Section titled “Link Extraction and Testing”email, err := inbox.WaitForEmail(ctx, vaultsandbox.WithSubjectRegex(regexp.MustCompile(`Reset`)),)if err != nil { t.Fatal(err)}
// Extract reset linkvar resetLink stringfor _, link := range email.Links { if strings.Contains(link, "reset-password") || strings.Contains(link, "token=") { resetLink = link break }}if resetLink == "" { t.Fatal("reset link not found")}
// Extract token from linku, err := url.Parse(resetLink)if err != nil { t.Fatal(err)}token := u.Query().Get("token")
if token == "" { t.Fatal("token not found in link")}if len(token) <= 20 { t.Error("token seems too short")}
// Test the linkresp, err := http.Get(resetLink)if err != nil { t.Fatal(err)}defer resp.Body.Close()if resp.StatusCode != http.StatusOK { t.Errorf("expected 200, got %d", resp.StatusCode)}Multi-Part Emails
Section titled “Multi-Part Emails”// Email with both text and HTMLif email.Text != "" && email.HTML != "" { // Validate both versions have key content if !strings.Contains(email.Text, "Welcome") { t.Error("missing welcome in text") } if !strings.Contains(email.HTML, "<h1>Welcome</h1>") { t.Error("missing welcome in HTML") }}
// HTML-only emailif email.HTML != "" && email.Text == "" { fmt.Println("HTML-only email") if !strings.Contains(email.HTML, "<!DOCTYPE html>") { t.Error("missing doctype") }}
// Plain text onlyif email.Text != "" && email.HTML == "" { fmt.Println("Plain text email")}Time-Based Assertions
Section titled “Time-Based Assertions”startTime := time.Now()
// Trigger emailsendWelcomeEmail(inbox.EmailAddress())
// Wait and receiveemail, err := inbox.WaitForEmail(ctx, vaultsandbox.WithWaitTimeout(10*time.Second))if err != nil { t.Fatal(err)}
// Verify it arrived quicklydeliveryTime := email.ReceivedAt.Sub(startTime)if deliveryTime > 5*time.Second { t.Errorf("email took too long: %v", deliveryTime)}Email Metadata Analysis
Section titled “Email Metadata Analysis”fmt.Println("Email details:")fmt.Printf("- From: %s\n", email.From)fmt.Printf("- Subject: %s\n", email.Subject)fmt.Printf("- Received: %s\n", email.ReceivedAt.Format(time.RFC3339))fmt.Printf("- Size: %d chars\n", len(email.Text))fmt.Printf("- Links: %d\n", len(email.Links))fmt.Printf("- Attachments: %d\n", len(email.Attachments))
// Check email authenticationif email.AuthResults != nil { auth := email.AuthResults.Validate() fmt.Printf("- Auth passed: %v\n", auth.Passed) if !auth.Passed { fmt.Printf("- Auth failures: %v\n", auth.Failures) }}Testing Examples
Section titled “Testing Examples”Standard Test
Section titled “Standard Test”func TestWelcomeEmail(t *testing.T) { ctx := context.Background()
inbox, err := client.CreateInbox(ctx) if err != nil { t.Fatal(err) } defer inbox.Delete(ctx)
// Trigger email registerUser(inbox.EmailAddress())
email, err := inbox.WaitForEmail(ctx, vaultsandbox.WithSubjectRegex(regexp.MustCompile(`Welcome`)), vaultsandbox.WithWaitTimeout(10*time.Second), ) if err != nil { t.Fatal(err) }
t.Errorf("unexpected sender: %s", email.From) } if !strings.Contains(email.Subject, "Welcome") { t.Errorf("unexpected subject: %s", email.Subject) } if !strings.Contains(email.Text, "Thank you for signing up") { t.Error("missing welcome text") }
var verifyLink string for _, link := range email.Links { if strings.Contains(link, "/verify") { verifyLink = link break } } if verifyLink == "" { t.Error("verify link not found") }}
func TestUnsubscribeLink(t *testing.T) { ctx := context.Background()
inbox, err := client.CreateInbox(ctx) if err != nil { t.Fatal(err) } defer inbox.Delete(ctx)
registerUser(inbox.EmailAddress())
email, err := inbox.WaitForEmail(ctx, vaultsandbox.WithWaitTimeout(10*time.Second)) if err != nil { t.Fatal(err) }
var unsubLink string for _, link := range email.Links { if strings.Contains(link, "/unsubscribe") || strings.Contains(link, "list-unsubscribe") { unsubLink = link break } } if unsubLink == "" { t.Error("unsubscribe link not found") }}Table-Driven Test
Section titled “Table-Driven Test”func TestPasswordResetFlow(t *testing.T) { ctx := context.Background()
inbox, err := client.CreateInbox(ctx) if err != nil { t.Fatal(err) } defer inbox.Delete(ctx)
requestPasswordReset(inbox.EmailAddress())
email, err := inbox.WaitForEmail(ctx, vaultsandbox.WithSubjectRegex(regexp.MustCompile(`(?i)reset`)), vaultsandbox.WithWaitTimeout(10*time.Second), ) if err != nil { t.Fatal(err) }
t.Run("sender", func(t *testing.T) { } })
t.Run("reset link", func(t *testing.T) { if len(email.Links) == 0 { t.Fatal("no links found") }
resetLink := email.Links[0] if !strings.HasPrefix(resetLink, "https://") { t.Error("reset link should use HTTPS") } if !strings.Contains(resetLink, "token=") { t.Error("reset link should contain token") }
// Verify token format u, err := url.Parse(resetLink) if err != nil { t.Fatal(err) } token := u.Query().Get("token") if len(token) != 64 { t.Errorf("expected token length 64, got %d", len(token)) } })}Using testify/assert
Section titled “Using testify/assert”import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require")
func TestWelcomeEmailWithTestify(t *testing.T) { ctx := context.Background()
inbox, err := client.CreateInbox(ctx) require.NoError(t, err) defer inbox.Delete(ctx)
registerUser(inbox.EmailAddress())
email, err := inbox.WaitForEmail(ctx, vaultsandbox.WithSubjectRegex(regexp.MustCompile(`Welcome`)), vaultsandbox.WithWaitTimeout(10*time.Second), ) require.NoError(t, err)
assert.Contains(t, email.Subject, "Welcome") assert.Contains(t, email.Text, "Thank you for signing up") assert.NotEmpty(t, email.Links)}Troubleshooting
Section titled “Troubleshooting”Email Content is Empty
Section titled “Email Content is Empty”if email.Text == "" && email.HTML == "" { fmt.Println("Email has no content") fmt.Printf("Headers: %v\n", email.Headers)
raw, err := inbox.GetRawEmail(ctx, email.ID) if err == nil { fmt.Printf("Raw: %s\n", raw) }}Links Not Extracted
Section titled “Links Not Extracted”if len(email.Links) == 0 { fmt.Println("No links found") fmt.Printf("Text: %s\n", email.Text) fmt.Printf("HTML: %s\n", email.HTML)
// Manually extract urlRegex := regexp.MustCompile(`https?://[^\s]+`) textLinks := urlRegex.FindAllString(email.Text, -1) fmt.Printf("Manual extraction: %v\n", textLinks)}Decryption Errors
Section titled “Decryption Errors”email, err := inbox.GetEmail(ctx, emailID)if err != nil { if errors.Is(err, vaultsandbox.ErrDecryptionFailed) { fmt.Println("Failed to decrypt email") fmt.Println("This may indicate:") fmt.Println("- Wrong private key") fmt.Println("- Corrupted data") fmt.Println("- Server issue") }}Next Steps
Section titled “Next Steps”- Authentication Results - Email authentication details
- Working with Attachments - Handle email attachments
- Email Authentication - Test SPF/DKIM/DMARC
- API Reference: Email - Complete API documentation