Skip to content

Inbox API

The Inbox type represents a single email inbox in VaultSandbox. It provides methods for managing emails, waiting for new messages, and monitoring in real-time.

func (i *Inbox) EmailAddress() string

Returns the email address for this inbox. Use this address to send test emails.

inbox, err := client.CreateInbox(ctx)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Send email to: %s\n", inbox.EmailAddress())
// Use in your application
err = sendWelcomeEmail(inbox.EmailAddress())

func (i *Inbox) InboxHash() string

Returns the unique identifier (SHA-256 hash of the public key) for this inbox. Used internally for API operations.

fmt.Printf("Inbox ID: %s\n", inbox.InboxHash())

func (i *Inbox) ExpiresAt() time.Time

Returns the time when this inbox will expire and be automatically deleted.

inbox, err := client.CreateInbox(ctx)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Inbox expires at: %s\n", inbox.ExpiresAt().Format(time.RFC3339))
timeUntilExpiry := time.Until(inbox.ExpiresAt())
fmt.Printf("Time remaining: %v\n", timeUntilExpiry.Round(time.Second))

func (i *Inbox) IsExpired() bool

Returns whether the inbox has expired.

if inbox.IsExpired() {
fmt.Println("Inbox has expired")
}

Lists all emails in the inbox with full content. Emails are automatically decrypted.

func (i *Inbox) GetEmails(ctx context.Context) ([]*Email, error)

[]*Email - Slice of decrypted email objects with full content, sorted by received time (newest first)

emails, err := inbox.GetEmails(ctx)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Inbox has %d emails\n", len(emails))
for _, email := range emails {
fmt.Printf("- %s from %s\n", email.Subject, email.From)
}

Lists all emails in the inbox with metadata only (no body or attachments). This is more efficient when you only need to display email summaries.

func (i *Inbox) GetEmailsMetadataOnly(ctx context.Context) ([]*EmailMetadata, error)

[]*EmailMetadata - Slice of email metadata objects, sorted by received time (newest first)

type EmailMetadata struct {
ID string
From string
Subject string
ReceivedAt time.Time
IsRead bool
}
// Efficient listing for UI display
emails, err := inbox.GetEmailsMetadataOnly(ctx)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Inbox has %d emails\n", len(emails))
for _, email := range emails {
status := " "
if email.IsRead {
status = ""
}
fmt.Printf("[%s] %s - %s (%s)\n",
status,
email.From,
email.Subject,
email.ReceivedAt.Format(time.RFC822))
}
// Fetch full content only when needed
if len(emails) > 0 {
fullEmail, err := inbox.GetEmail(ctx, emails[0].ID)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Body: %s\n", fullEmail.Text)
}

Use GetEmailsMetadataOnly instead of GetEmails when:

  • Displaying email lists in a UI
  • Checking email counts or subjects without reading content
  • Implementing pagination or lazy loading
  • Reducing bandwidth and processing time

Retrieves a specific email by ID.

func (i *Inbox) GetEmail(ctx context.Context, emailID string) (*Email, error)
  • emailID: The unique identifier for the email

*Email - The decrypted email object

emails, err := inbox.GetEmails(ctx)
if err != nil {
log.Fatal(err)
}
firstEmail, err := inbox.GetEmail(ctx, emails[0].ID)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Subject: %s\n", firstEmail.Subject)
fmt.Printf("Body: %s\n", firstEmail.Text)
  • ErrEmailNotFound - Email does not exist

Waits for an email matching specified criteria. This is the recommended way to handle email arrival in tests.

func (i *Inbox) WaitForEmail(ctx context.Context, opts ...WaitOption) (*Email, error)
OptionDescription
WithWaitTimeout(d time.Duration)Maximum time to wait (default: 60s)
WithSubject(s string)Filter by exact subject match
WithSubjectRegex(r *regexp.Regexp)Filter by subject pattern
WithFrom(s string)Filter by exact sender address
WithFromRegex(r *regexp.Regexp)Filter by sender pattern
WithPredicate(fn func(*Email) bool)Custom filter function

*Email - The first email matching the criteria

// Wait for any email
email, err := inbox.WaitForEmail(ctx,
vaultsandbox.WithWaitTimeout(10*time.Second),
)
// Wait for email with specific subject
email, err := inbox.WaitForEmail(ctx,
vaultsandbox.WithWaitTimeout(10*time.Second),
vaultsandbox.WithSubjectRegex(regexp.MustCompile(`Password Reset`)),
)
// Wait for email from specific sender
email, err := inbox.WaitForEmail(ctx,
vaultsandbox.WithWaitTimeout(10*time.Second),
vaultsandbox.WithFrom("[email protected]"),
)
// Wait with custom predicate
email, err := inbox.WaitForEmail(ctx,
vaultsandbox.WithWaitTimeout(15*time.Second),
vaultsandbox.WithPredicate(func(e *vaultsandbox.Email) bool {
for _, to := range e.To {
if to == "[email protected]" {
return true
}
}
return false
}),
)
// Combine multiple filters
email, err := inbox.WaitForEmail(ctx,
vaultsandbox.WithWaitTimeout(10*time.Second),
vaultsandbox.WithSubjectRegex(regexp.MustCompile(`Welcome`)),
vaultsandbox.WithFromRegex(regexp.MustCompile(`noreply@`)),
vaultsandbox.WithPredicate(func(e *vaultsandbox.Email) bool {
return len(e.Links) > 0
}),
)
  • context.DeadlineExceeded - No matching email received within timeout period

Waits until the inbox has at least the specified number of emails. More efficient than using arbitrary sleeps when testing multiple emails.

func (i *Inbox) WaitForEmailCount(ctx context.Context, count int, opts ...WaitOption) ([]*Email, error)
  • count: Minimum number of emails to wait for
OptionDescription
WithWaitTimeout(d time.Duration)Maximum time to wait (default: 60s)
WithSubject(s string)Filter by exact subject match
WithSubjectRegex(r *regexp.Regexp)Filter by subject pattern
WithFrom(s string)Filter by exact sender address
WithFromRegex(r *regexp.Regexp)Filter by sender pattern
WithPredicate(fn func(*Email) bool)Custom filter function

[]*Email - All matching emails in the inbox once count is reached

// Trigger multiple emails
err := sendMultipleNotifications(inbox.EmailAddress(), 3)
if err != nil {
log.Fatal(err)
}
// Wait for all 3 to arrive
emails, err := inbox.WaitForEmailCount(ctx, 3,
vaultsandbox.WithWaitTimeout(30*time.Second),
)
if err != nil {
log.Fatal(err)
}
// Now process all emails
if len(emails) != 3 {
log.Fatalf("expected 3 emails, got %d", len(emails))
}
  • context.DeadlineExceeded - Required count not reached within timeout

Returns a channel that receives emails as they arrive. The channel closes when the context is cancelled.

func (i *Inbox) Watch(ctx context.Context) <-chan *Email
  • ctx: Context for cancellation - when cancelled, the channel closes
  • <-chan *Email - Receive-only channel of emails
inbox, err := client.CreateInbox(ctx)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Watching: %s\n", inbox.EmailAddress())
watchCtx, cancel := context.WithTimeout(ctx, 5*time.Minute)
defer cancel()
for email := range inbox.Watch(watchCtx) {
fmt.Printf("New email: %q\n", email.Subject)
fmt.Printf("From: %s\n", email.From)
}
  • Channel has buffer size of 16
  • Non-blocking sends: if buffer is full, events may be dropped
  • Channel closes automatically when context is cancelled
  • Watcher is automatically unregistered on context cancellation

Use context for lifecycle management:

func TestEmailFlow(t *testing.T) {
inbox, err := client.CreateInbox(ctx)
if err != nil {
t.Fatal(err)
}
defer inbox.Delete(ctx)
watchCtx, cancel := context.WithTimeout(ctx, 10*time.Second)
defer cancel()
for email := range inbox.Watch(watchCtx) {
// Process email
if foundDesiredEmail(email) {
cancel() // Stop watching early
break
}
}
}

Calls a callback function for each email as they arrive until the context is cancelled. This is a convenience wrapper around Watch for simpler use cases where you don’t need channel semantics.

func (i *Inbox) WatchFunc(ctx context.Context, fn func(*Email))
  • ctx: Context for cancellation - when cancelled, watching stops
  • fn: Callback function called for each new email
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
defer cancel()
inbox.WatchFunc(ctx, func(email *vaultsandbox.Email) {
fmt.Printf("New email: %s\n", email.Subject)
fmt.Printf("From: %s\n", email.From)
// Process the email
if strings.Contains(email.Subject, "Password Reset") {
processPasswordReset(email)
}
})
  • Blocks until context is cancelled
  • Each email is passed to the callback function
  • Uses Watch internally with proper context handling

Use WatchFunc instead of Watch when:

  • You prefer callback-style processing over channel iteration
  • You want simpler code without channel select statements
  • You’re processing emails in a blocking manner

Gets the current synchronization status of the inbox with the server.

func (i *Inbox) GetSyncStatus(ctx context.Context) (*SyncStatus, error)

*SyncStatus - Sync status information

type SyncStatus struct {
EmailCount int // Number of emails in the inbox
EmailsHash string // Hash of the email list for change detection
}
status, err := inbox.GetSyncStatus(ctx)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Email count: %d\n", status.EmailCount)
fmt.Printf("Emails hash: %s\n", status.EmailsHash)

Deletes this inbox and all its emails.

func (i *Inbox) Delete(ctx context.Context) error
inbox, err := client.CreateInbox(ctx)
if err != nil {
log.Fatal(err)
}
// Use inbox...
// Clean up
err = inbox.Delete(ctx)
if err != nil {
log.Fatal(err)
}
fmt.Println("Inbox deleted")

Always delete inboxes after tests using t.Cleanup:

func TestEmailFlow(t *testing.T) {
inbox, err := client.CreateInbox(ctx)
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() {
inbox.Delete(context.Background())
})
// Test logic...
}

Exports inbox data and encryption keys for backup or sharing.

func (i *Inbox) Export() *ExportedInbox

*ExportedInbox - Serializable inbox data including sensitive keys

type ExportedInbox struct {
EmailAddress string `json:"emailAddress"`
ExpiresAt time.Time `json:"expiresAt"`
InboxHash string `json:"inboxHash"`
ServerSigPk string `json:"serverSigPk"`
PublicKeyB64 string `json:"publicKeyB64"`
SecretKeyB64 string `json:"secretKeyB64"`
ExportedAt time.Time `json:"exportedAt"`
}

Validates that the exported data is valid before import.

func (e *ExportedInbox) Validate() error

Returns ErrInvalidImportData if the email address is empty or the secret key is missing/invalid.

inbox, err := client.CreateInbox(ctx)
if err != nil {
log.Fatal(err)
}
data := inbox.Export()
// Save for later
jsonData, err := json.MarshalIndent(data, "", " ")
if err != nil {
log.Fatal(err)
}
err = os.WriteFile("inbox-backup.json", jsonData, 0600)
if err != nil {
log.Fatal(err)
}

Exported data contains private encryption keys. Store securely with restrictive file permissions (0600)!

package main
import (
"context"
"encoding/json"
"fmt"
"log"
"os"
"regexp"
"time"
"github.com/vaultsandbox/client-go"
)
func completeInboxExample() error {
ctx := context.Background()
client, err := vaultsandbox.New(
os.Getenv("VAULTSANDBOX_API_KEY"),
vaultsandbox.WithBaseURL(os.Getenv("VAULTSANDBOX_URL")),
)
if err != nil {
return err
}
defer client.Close()
// Create inbox
inbox, err := client.CreateInbox(ctx)
if err != nil {
return err
}
fmt.Printf("Created: %s\n", inbox.EmailAddress())
fmt.Printf("Expires: %s\n", inbox.ExpiresAt().Format(time.RFC3339))
// Set up watching in a goroutine
watchCtx, cancelWatch := context.WithCancel(ctx)
go func() {
for email := range inbox.Watch(watchCtx) {
fmt.Printf("Received via watch: %s\n", email.Subject)
}
}()
// Trigger test email
err = sendTestEmail(inbox.EmailAddress())
if err != nil {
return err
}
// Wait for specific email
email, err := inbox.WaitForEmail(ctx,
vaultsandbox.WithWaitTimeout(10*time.Second),
vaultsandbox.WithSubjectRegex(regexp.MustCompile(`Test`)),
)
if err != nil {
return err
}
fmt.Printf("Found email: %s\n", email.Subject)
fmt.Printf("Body: %s\n", email.Text)
// Mark as read
err = inbox.MarkEmailAsRead(ctx, email.ID)
if err != nil {
return err
}
// Get all emails
allEmails, err := inbox.GetEmails(ctx)
if err != nil {
return err
}
fmt.Printf("Total emails: %d\n", len(allEmails))
// Export inbox
exportData := inbox.Export()
jsonData, err := json.Marshal(exportData)
if err != nil {
return err
}
err = os.WriteFile("inbox.json", jsonData, 0600)
if err != nil {
return err
}
// Clean up
cancelWatch()
err = inbox.Delete(ctx)
if err != nil {
return err
}
return nil
}
func main() {
if err := completeInboxExample(); err != nil {
log.Fatal(err)
}
}