Skip to content

Email Objects

Email objects in VaultSandbox represent decrypted emails with all their content, headers, and metadata.

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.From) // "[email protected]"
fmt.Println(email.To) // ["[email protected]"]
fmt.Println(email.Subject) // "Welcome to our service"
fmt.Println(email.Text) // Plain text content
fmt.Println(email.HTML) // HTML content
fmt.Println(email.ReceivedAt) // time.Time
fmt.Println(email.IsRead) // false
fmt.Println(email.Links) // ["https://example.com/verify"]
fmt.Println(email.Attachments) // []Attachment
fmt.Println(email.AuthResults) // SPF/DKIM/DMARC results

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).

fmt.Println(email.From) // "[email protected]"
// Use in assertions
if email.From != "[email protected]" {
t.Error("unexpected sender")
}

Type: []string

Slice of recipient email addresses.

fmt.Println(email.To) // ["[email protected]"]
// Multiple recipients
fmt.Println(email.To) // ["[email protected]", "[email protected]"]
// Check if sent to specific address
if !slices.Contains(email.To, inbox.EmailAddress()) {
t.Error("inbox address not in recipients")
}

Type: string

Email subject line.

fmt.Println(email.Subject) // "Password Reset Request"
// Use in filtering
email, 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-only
if 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 only
if email.HTML != "" {
if !strings.Contains(email.HTML, "<a href") {
t.Error("expected link in HTML")
}
}

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 recently
age := time.Since(email.ReceivedAt)
if age > time.Minute {
t.Error("email took too long to arrive")
}

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 status
updated, _ := inbox.GetEmail(ctx, email.ID)
fmt.Println(updated.IsRead) // true

Type: []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 link
var verifyLink string
for _, link := range email.Links {
if strings.Contains(link, "/verify") {
verifyLink = link
break
}
}
if verifyLink == "" {
t.Fatal("verify link not found")
}
// Test link
resp, 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)
}

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.

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)) // 1
if auth.DMARC != nil {
fmt.Println(auth.DMARC.Result) // "pass"
}
// Validate all checks
validation := auth.Validate()
if !validation.Passed {
fmt.Println("Authentication failed:", validation.Failures)
}

See Authentication Results for details.

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 headers
messageID := email.Headers["message-id"]
contentType := email.Headers["content-type"]

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 read
  • inbox.DeleteEmail(ctx, emailID) — Deletes an email

Mark an email as read.

func (i *Inbox) MarkEmailAsRead(ctx context.Context, emailID string) error
if err := inbox.MarkEmailAsRead(ctx, email.ID); err != nil {
log.Fatal(err)
}

Delete an email from the inbox.

func (i *Inbox) DeleteEmail(ctx context.Context, emailID string) error
if 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")
}

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..."
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
}
email, err := inbox.WaitForEmail(ctx,
vaultsandbox.WithSubjectRegex(regexp.MustCompile(`Welcome`)),
vaultsandbox.WithWaitTimeout(10*time.Second),
)
if err != nil {
t.Fatal(err)
}
// Validate sender
if email.From != "[email protected]" {
t.Errorf("unexpected sender: %s", email.From)
}
// Validate content
if !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 links
var verifyLink string
for _, 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")
}
email, err := inbox.WaitForEmail(ctx,
vaultsandbox.WithSubjectRegex(regexp.MustCompile(`Reset`)),
)
if err != nil {
t.Fatal(err)
}
// Extract reset link
var resetLink string
for _, 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 link
u, 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 link
resp, 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)
}
// Email with both text and HTML
if 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 email
if email.HTML != "" && email.Text == "" {
fmt.Println("HTML-only email")
if !strings.Contains(email.HTML, "<!DOCTYPE html>") {
t.Error("missing doctype")
}
}
// Plain text only
if email.Text != "" && email.HTML == "" {
fmt.Println("Plain text email")
}
startTime := time.Now()
// Trigger email
sendWelcomeEmail(inbox.EmailAddress())
// Wait and receive
email, err := inbox.WaitForEmail(ctx, vaultsandbox.WithWaitTimeout(10*time.Second))
if err != nil {
t.Fatal(err)
}
// Verify it arrived quickly
deliveryTime := email.ReceivedAt.Sub(startTime)
if deliveryTime > 5*time.Second {
t.Errorf("email took too long: %v", deliveryTime)
}
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 authentication
if 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)
}
}
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)
}
if email.From != "[email protected]" {
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")
}
}
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) {
if email.From != "[email protected]" {
t.Errorf("expected [email protected], got %s", email.From)
}
})
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))
}
})
}
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.Equal(t, "[email protected]", email.From)
assert.Contains(t, email.Subject, "Welcome")
assert.Contains(t, email.Text, "Thank you for signing up")
assert.NotEmpty(t, email.Links)
}
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)
}
}
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)
}
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")
}
}