gmail

package
v0.7.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 9, 2026 License: MIT Imports: 17 Imported by: 0

Documentation

Overview

Package gmail provides a Gmail API client with rate limiting and retry logic.

Index

Constants

View Source
const (
	OpTrash       = "trash"
	OpDelete      = "delete"
	OpBatchDelete = "batch_delete"
)

Operation constants for call tracking.

View Source
const DefaultCapacity = 250

DefaultCapacity is the default token bucket capacity (Gmail's per-user quota).

View Source
const DefaultRefillRate = 250.0

DefaultRefillRate is tokens per second at the default rate.

View Source
const MinQPS = 0.1

MinQPS is the minimum allowed QPS to prevent division by zero.

Variables

This section is empty.

Functions

This section is empty.

Types

type API

type API interface {
	AccountReader
	MessageReader
	MessageDeleter

	// Close releases any resources held by the client.
	Close() error
}

API defines the interface for Gmail operations. This interface enables mocking for tests without hitting the real API.

type AccountReader added in v0.4.0

type AccountReader interface {
	// GetProfile returns the authenticated user's profile.
	GetProfile(ctx context.Context) (*Profile, error)

	// ListLabels returns all labels for the account.
	ListLabels(ctx context.Context) ([]*Label, error)
}

AccountReader provides read access to account-level Gmail data.

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client implements the Gmail API interface.

func NewClient

func NewClient(tokenSource oauth2.TokenSource, opts ...ClientOption) *Client

NewClient creates a new Gmail API client.

func (*Client) BatchDeleteMessages

func (c *Client) BatchDeleteMessages(ctx context.Context, messageIDs []string) error

BatchDeleteMessages permanently deletes multiple messages.

func (*Client) Close

func (c *Client) Close() error

Close releases resources held by the client.

func (*Client) DeleteMessage

func (c *Client) DeleteMessage(ctx context.Context, messageID string) error

DeleteMessage permanently deletes a message.

func (*Client) GetMessageRaw

func (c *Client) GetMessageRaw(ctx context.Context, messageID string) (*RawMessage, error)

GetMessageRaw fetches a single message with raw MIME data.

func (*Client) GetMessagesRawBatch

func (c *Client) GetMessagesRawBatch(ctx context.Context, messageIDs []string) ([]*RawMessage, error)

GetMessagesRawBatch fetches multiple messages in parallel with rate limiting.

func (*Client) GetProfile

func (c *Client) GetProfile(ctx context.Context) (*Profile, error)

GetProfile returns the authenticated user's profile.

func (*Client) ListHistory

func (c *Client) ListHistory(ctx context.Context, startHistoryID uint64, pageToken string) (*HistoryResponse, error)

ListHistory returns changes since the given history ID.

func (*Client) ListLabels

func (c *Client) ListLabels(ctx context.Context) ([]*Label, error)

ListLabels returns all labels for the account.

func (*Client) ListMessages

func (c *Client) ListMessages(ctx context.Context, query string, pageToken string) (*MessageListResponse, error)

ListMessages returns message IDs matching the query.

func (*Client) TrashMessage

func (c *Client) TrashMessage(ctx context.Context, messageID string) error

TrashMessage moves a message to trash.

type ClientOption

type ClientOption func(*Client)

ClientOption configures a Client.

func WithConcurrency

func WithConcurrency(n int) ClientOption

WithConcurrency sets the max concurrent requests for batch operations.

func WithLogger

func WithLogger(logger *slog.Logger) ClientOption

WithLogger sets the logger for the client.

func WithRateLimiter

func WithRateLimiter(rl *RateLimiter) ClientOption

WithRateLimiter sets a custom rate limiter.

type Clock added in v0.2.0

type Clock interface {
	Now() time.Time
	After(d time.Duration) <-chan time.Time
}

Clock abstracts time operations for testability.

type DeletionCall

type DeletionCall struct {
	Operation string   // OpTrash, OpDelete, or OpBatchDelete
	MessageID string   // For single operations
	BatchIDs  []string // For batch operations
	Error     error    // Error returned (nil for success)
}

DeletionCall represents a single API call for sequence tracking.

type DeletionMockAPI

type DeletionMockAPI struct {

	// Per-message error injection for Trash operations
	// Key: messageID, Value: error to return (or nil for success)
	TrashErrors map[string]error

	// Per-message error injection for Delete operations
	DeleteErrors map[string]error

	// Batch delete error - returned when BatchDeleteMessages is called
	BatchDeleteError error

	// Transient errors - fail N times then succeed
	// Key: messageID, Value: number of failures remaining
	TransientTrashFailures  map[string]int
	TransientDeleteFailures map[string]int

	// Rate limit simulation
	RateLimitAfterCalls int // Trigger rate limit after this many calls (0 = disabled)
	RateLimitDuration   int // Seconds to suggest retry (for 429 Retry-After)

	// Call tracking
	TrashCalls       []string   // Message IDs passed to TrashMessage
	DeleteCalls      []string   // Message IDs passed to DeleteMessage
	BatchDeleteCalls [][]string // Batches passed to BatchDeleteMessages

	// Call sequence tracking (for verifying retry behavior)
	CallSequence []DeletionCall

	// Hooks for custom behavior
	BeforeTrash       func(messageID string) error // Called before processing trash
	BeforeDelete      func(messageID string) error // Called before processing delete
	BeforeBatchDelete func(messageIDs []string) error
	// contains filtered or unexported fields
}

DeletionMockAPI is a mock Gmail API specifically designed for testing deletion scenarios with comprehensive error injection and call tracking.

func NewDeletionMockAPI

func NewDeletionMockAPI() *DeletionMockAPI

NewDeletionMockAPI creates a new deletion mock with empty state.

func (*DeletionMockAPI) BatchDeleteMessages

func (m *DeletionMockAPI) BatchDeleteMessages(ctx context.Context, messageIDs []string) error

BatchDeleteMessages simulates batch deletion with error injection.

func (*DeletionMockAPI) Close

func (m *DeletionMockAPI) Close() error

func (*DeletionMockAPI) DeleteMessage

func (m *DeletionMockAPI) DeleteMessage(ctx context.Context, messageID string) error

DeleteMessage simulates permanently deleting a message with error injection.

func (*DeletionMockAPI) GetDeleteCallCount

func (m *DeletionMockAPI) GetDeleteCallCount(messageID string) int

GetDeleteCallCount returns the number of delete calls for a message.

func (*DeletionMockAPI) GetMessageRaw

func (m *DeletionMockAPI) GetMessageRaw(ctx context.Context, messageID string) (*RawMessage, error)

func (*DeletionMockAPI) GetMessagesRawBatch

func (m *DeletionMockAPI) GetMessagesRawBatch(ctx context.Context, messageIDs []string) ([]*RawMessage, error)

func (*DeletionMockAPI) GetProfile

func (m *DeletionMockAPI) GetProfile(ctx context.Context) (*Profile, error)

func (*DeletionMockAPI) GetTrashCallCount

func (m *DeletionMockAPI) GetTrashCallCount(messageID string) int

GetTrashCallCount returns the number of trash calls for a message.

func (*DeletionMockAPI) ListHistory

func (m *DeletionMockAPI) ListHistory(ctx context.Context, startHistoryID uint64, pageToken string) (*HistoryResponse, error)

func (*DeletionMockAPI) ListLabels

func (m *DeletionMockAPI) ListLabels(ctx context.Context) ([]*Label, error)

func (*DeletionMockAPI) ListMessages

func (m *DeletionMockAPI) ListMessages(ctx context.Context, query string, pageToken string) (*MessageListResponse, error)

func (*DeletionMockAPI) Reset

func (m *DeletionMockAPI) Reset()

Reset clears all state and call tracking.

func (*DeletionMockAPI) SetNotFoundError

func (m *DeletionMockAPI) SetNotFoundError(messageID string)

SetNotFoundError sets a 404 error for a message ID.

func (*DeletionMockAPI) SetTransientFailure

func (m *DeletionMockAPI) SetTransientFailure(messageID string, failCount int, forTrash bool)

SetTransientFailure sets a message to fail N times then succeed.

func (*DeletionMockAPI) TrashMessage

func (m *DeletionMockAPI) TrashMessage(ctx context.Context, messageID string) error

TrashMessage simulates trashing a message with error injection.

type HistoryLabelChange

type HistoryLabelChange struct {
	Message  MessageID
	LabelIDs []string
}

HistoryLabelChange represents a label change in history.

type HistoryMessage

type HistoryMessage struct {
	Message MessageID
}

HistoryMessage represents a message in history.

type HistoryRecord

type HistoryRecord struct {
	ID              uint64
	MessagesAdded   []HistoryMessage
	MessagesDeleted []HistoryMessage
	LabelsAdded     []HistoryLabelChange
	LabelsRemoved   []HistoryLabelChange
}

HistoryRecord represents a single history change.

type HistoryResponse

type HistoryResponse struct {
	History       []HistoryRecord
	NextPageToken string
	HistoryID     uint64
}

HistoryResponse contains changes since a history ID.

type Label

type Label struct {
	ID                    string
	Name                  string
	Type                  string // "system" or "user"
	MessagesTotal         int64
	MessagesUnread        int64
	MessageListVisibility string
	LabelListVisibility   string
}

Label represents a Gmail label.

type MessageDeleter added in v0.4.0

type MessageDeleter interface {
	// TrashMessage moves a message to trash (recoverable for 30 days).
	TrashMessage(ctx context.Context, messageID string) error

	// DeleteMessage permanently deletes a message.
	DeleteMessage(ctx context.Context, messageID string) error

	// BatchDeleteMessages permanently deletes multiple messages (max 1000).
	BatchDeleteMessages(ctx context.Context, messageIDs []string) error
}

MessageDeleter provides write operations for deleting Gmail messages.

type MessageID

type MessageID struct {
	ID       string
	ThreadID string
}

MessageID represents a message reference from list operations.

type MessageListResponse

type MessageListResponse struct {
	Messages           []MessageID
	NextPageToken      string
	ResultSizeEstimate int64
}

MessageListResponse contains a page of message IDs.

type MessageReader added in v0.4.0

type MessageReader interface {
	// ListMessages returns message IDs matching the query.
	// Use pageToken for pagination. Returns next page token if more results exist.
	ListMessages(ctx context.Context, query string, pageToken string) (*MessageListResponse, error)

	// GetMessageRaw fetches a single message with raw MIME data.
	GetMessageRaw(ctx context.Context, messageID string) (*RawMessage, error)

	// GetMessagesRawBatch fetches multiple messages in parallel with rate limiting.
	// Returns results in the same order as input IDs. Failed fetches return nil.
	GetMessagesRawBatch(ctx context.Context, messageIDs []string) ([]*RawMessage, error)

	// ListHistory returns changes since the given history ID.
	ListHistory(ctx context.Context, startHistoryID uint64, pageToken string) (*HistoryResponse, error)
}

MessageReader provides read access to Gmail messages and history.

type MockAPI

type MockAPI struct {

	// Profile to return
	Profile *Profile

	// Labels to return
	Labels []*Label

	// Messages indexed by ID
	Messages map[string]*RawMessage

	// Message list pages - each page is a list of message IDs
	MessagePages [][]string

	// History records
	HistoryRecords []HistoryRecord
	HistoryID      uint64

	// UseRawThreadID uses the ThreadID from RawMessage instead of generating "thread_" + id
	UseRawThreadID bool

	// ListThreadIDOverride overrides the threadID returned by ListMessages for specific message IDs.
	// This allows testing the case where list returns empty threadID but raw message has one.
	ListThreadIDOverride map[string]string

	// Error injection
	ProfileError      error
	LabelsError       error
	ListMessagesError error
	GetMessageError   map[string]error // Per-message errors
	HistoryError      error

	// Call tracking for assertions
	ProfileCalls      int
	LabelsCalls       int
	ListMessagesCalls int
	LastQuery         string // Last query passed to ListMessages
	GetMessageCalls   []string
	HistoryCalls      []uint64
	TrashCalls        []string
	DeleteCalls       []string
	BatchDeleteCalls  [][]string
	// contains filtered or unexported fields
}

MockAPI is a mock implementation of the Gmail API for testing.

func NewMockAPI

func NewMockAPI() *MockAPI

NewMockAPI creates a new mock API with empty state.

func (*MockAPI) AddMessage

func (m *MockAPI) AddMessage(id string, raw []byte, labelIDs []string)

AddMessage adds a message to the mock store.

func (*MockAPI) BatchDeleteMessages

func (m *MockAPI) BatchDeleteMessages(ctx context.Context, messageIDs []string) error

BatchDeleteMessages records a batch delete call.

func (*MockAPI) Close

func (m *MockAPI) Close() error

Close is a no-op for the mock.

func (*MockAPI) DeleteMessage

func (m *MockAPI) DeleteMessage(ctx context.Context, messageID string) error

DeleteMessage records a delete call.

func (*MockAPI) GetMessageRaw

func (m *MockAPI) GetMessageRaw(ctx context.Context, messageID string) (*RawMessage, error)

GetMessageRaw returns a mock message.

func (*MockAPI) GetMessagesRawBatch

func (m *MockAPI) GetMessagesRawBatch(ctx context.Context, messageIDs []string) ([]*RawMessage, error)

GetMessagesRawBatch fetches multiple messages. Mirrors the real Client behavior: individual fetch errors leave a nil entry in the results slice rather than failing the entire batch. Callers must handle nil entries (see sync.go).

func (*MockAPI) GetProfile

func (m *MockAPI) GetProfile(ctx context.Context) (*Profile, error)

GetProfile returns the mock profile.

func (*MockAPI) ListHistory

func (m *MockAPI) ListHistory(ctx context.Context, startHistoryID uint64, pageToken string) (*HistoryResponse, error)

ListHistory returns mock history records.

func (*MockAPI) ListLabels

func (m *MockAPI) ListLabels(ctx context.Context) ([]*Label, error)

ListLabels returns the mock labels.

func (*MockAPI) ListMessages

func (m *MockAPI) ListMessages(ctx context.Context, query string, pageToken string) (*MessageListResponse, error)

ListMessages returns mock message IDs with pagination.

func (*MockAPI) Reset

func (m *MockAPI) Reset()

Reset clears all state and call tracking.

func (*MockAPI) SetupMessages added in v0.4.0

func (m *MockAPI) SetupMessages(msgs ...*RawMessage)

SetupMessages adds multiple pre-built RawMessage values to the mock store in a thread-safe manner. Nil entries in the input slice are silently skipped.

func (*MockAPI) TrashMessage

func (m *MockAPI) TrashMessage(ctx context.Context, messageID string) error

TrashMessage records a trash call.

type NotFoundError

type NotFoundError struct {
	Path string
}

NotFoundError indicates a 404 response.

func (*NotFoundError) Error

func (e *NotFoundError) Error() string

type NullProgress

type NullProgress struct{}

NullProgress is a no-op progress reporter.

func (NullProgress) OnComplete

func (NullProgress) OnComplete(summary *SyncSummary)

func (NullProgress) OnError

func (NullProgress) OnError(err error)

func (NullProgress) OnLatestDate

func (NullProgress) OnLatestDate(date time.Time)

func (NullProgress) OnProgress

func (NullProgress) OnProgress(processed, added, skipped int64)

func (NullProgress) OnStart

func (NullProgress) OnStart(total int64)

type Operation

type Operation int

Operation represents a Gmail API operation with its quota cost.

const (
	OpMessagesGet         Operation = iota // 5 units
	OpMessagesGetRaw                       // 5 units
	OpMessagesList                         // 5 units
	OpLabelsList                           // 1 unit
	OpHistoryList                          // 2 units
	OpMessagesTrash                        // 5 units
	OpMessagesDelete                       // 10 units
	OpMessagesBatchDelete                  // 50 units
	OpProfile                              // 1 unit
)

func (Operation) Cost

func (o Operation) Cost() int

Cost returns the quota cost for an operation.

type Profile

type Profile struct {
	EmailAddress  string
	MessagesTotal int64
	ThreadsTotal  int64
	HistoryID     uint64
}

Profile represents a Gmail user profile.

type RateLimitError

type RateLimitError struct {
	RetryAfter int // Seconds to wait before retry
}

RateLimitError represents a rate limit response.

func (*RateLimitError) Error

func (e *RateLimitError) Error() string

type RateLimiter

type RateLimiter struct {
	// contains filtered or unexported fields
}

RateLimiter implements a token bucket rate limiter for Gmail API calls. It is safe for concurrent use and supports adaptive throttling.

func NewRateLimiter

func NewRateLimiter(qps float64) *RateLimiter

NewRateLimiter creates a rate limiter with the specified QPS. A qps of 5 is the default safe rate for Gmail API. QPS is clamped to a minimum of MinQPS (0.1) to prevent division by zero.

func (*RateLimiter) Acquire

func (r *RateLimiter) Acquire(ctx context.Context, op Operation) error

Acquire blocks until the required tokens are available. Returns an error if the context is cancelled.

func (*RateLimiter) Available

func (r *RateLimiter) Available() float64

Available returns the current number of available tokens.

func (*RateLimiter) RecoverRate

func (r *RateLimiter) RecoverRate()

RecoverRate restores the original refill rate after throttling.

func (*RateLimiter) Throttle

func (r *RateLimiter) Throttle(duration time.Duration)

Throttle temporarily reduces the rate when we hit API rate limits. This provides adaptive back-pressure when Gmail returns 429/403 quota errors.

func (*RateLimiter) TryAcquire

func (r *RateLimiter) TryAcquire(op Operation) bool

TryAcquire attempts to acquire tokens without blocking. Returns true if successful, false if insufficient tokens.

type RawMessage

type RawMessage struct {
	ID           string
	ThreadID     string
	LabelIDs     []string
	Snippet      string
	HistoryID    uint64
	InternalDate int64 // Unix milliseconds
	SizeEstimate int64
	Raw          []byte // Decoded from base64url
}

RawMessage contains the raw MIME data for a message.

type SyncProgress

type SyncProgress interface {
	// OnStart is called when sync begins.
	OnStart(total int64)

	// OnProgress is called periodically during sync.
	OnProgress(processed, added, skipped int64)

	// OnComplete is called when sync finishes.
	OnComplete(summary *SyncSummary)

	// OnError is called when an error occurs.
	OnError(err error)
}

SyncProgress reports sync progress to the caller.

type SyncProgressWithDate

type SyncProgressWithDate interface {
	SyncProgress
	// OnLatestDate reports the date of the most recently processed message.
	// This helps show where in the mailbox the sync is currently processing.
	OnLatestDate(date time.Time)
}

SyncProgressWithDate is an optional extension of SyncProgress that provides message date info for better progress context.

type SyncSummary

type SyncSummary struct {
	StartTime        time.Time
	EndTime          time.Time
	Duration         time.Duration
	MessagesFound    int64
	MessagesAdded    int64
	MessagesUpdated  int64
	MessagesSkipped  int64
	BytesDownloaded  int64
	Errors           int64
	FinalHistoryID   uint64
	WasResumed       bool
	ResumedFromToken string
}

SyncSummary contains statistics about a completed sync.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL