client

package
v1.1.0 Latest Latest
Warning

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

Go to latest
Published: May 18, 2026 License: MIT Imports: 17 Imported by: 0

Documentation

Overview

Package client wraps go-imap with reconnect logic and higher-level helpers.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Client

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

Client wraps a go-imap client with reconnect/retry logic.

The underlying *imapclient.Client is held in an atomic.Pointer so that Reconnect can swap it without racing with the watcher goroutine spawned by withCancel. All IMAP operations go through safeCall, which receives the current client as an argument; on a transient error it transparently reconnects and retries the closure once with the fresh client.

func New

func New(ctx context.Context, addr, username, password string, opts Options) (*Client, error)

New establishes a connection and logs into the IMAP server.

ctx is used only for the initial dial and login; once Client is returned, reconnects are driven by the internal cancellation channel (closed by Cancel) so that long-running consumers do not need to keep the original context alive.

func (*Client) AppendMessage

func (c *Client) AppendMessage(ctx context.Context, folder string, msg *imap.Message) error

AppendMessage uploads a single message to the destination folder.

The body is streamed straight from the *imap.Message (which already holds it in memory) into the APPEND literal — no extra io.ReadAll buffer. The trade-off is that the literal is not replayable: a transient network error mid-APPEND cannot be retried, because the body has been partially sent. That's acceptable for our flow: the next sync run is idempotent (the Message-Id diff will pick up the message again), and avoiding the second in-memory copy halves peak RAM with multi-MB attachments and 10 workers.

func (*Client) Cancel added in v1.0.4

func (c *Client) Cancel()

Cancel marks the client as canceled and terminates the underlying connection. It is safe to call multiple times.

func (*Client) CreateMailbox

func (c *Client) CreateMailbox(ctx context.Context, name string) (bool, error)

CreateMailbox ensures the destination folder (and any missing parents) exist on the server. Returns true when a new mailbox was created on this call, false when it already existed.

func (*Client) FetchMessageIDSet added in v1.1.0

func (c *Client) FetchMessageIDSet(ctx context.Context, folder string) (map[string]struct{}, error)

FetchMessageIDSet returns the set of Message-Ids in folder, dropping the UIDs. Convenience wrapper around FetchMessageMap for the destination side of a sync where UIDs are irrelevant.

func (*Client) FetchMessageMap added in v1.1.0

func (c *Client) FetchMessageMap(ctx context.Context, folder string) (map[string]uint32, uint64, error)

FetchMessageMap returns Message-Id → UID for every message in folder, plus the sum of RFC822.SIZE across all messages.

One pass over the folder yields all three pieces. Messages without a usable Message-Id are counted and reported once via the progress writer — without that header the diff has no key to match on, so they cannot be tracked across servers and will be silently skipped. The folder-wide size total is used by callers (sync preview) to estimate transfer volume; messages without a Message-Id still contribute to the total because they exist on the wire.

The returned map is suitable both as the source side of a Message-Id diff and (with UIDs ignored) as the destination side; callers that only need the keys can use FetchMessageIDSet for a slightly thinner allocation.

func (*Client) GetDelimiter added in v1.0.1

func (c *Client) GetDelimiter() string

GetDelimiter returns the cached hierarchy delimiter for this server.

func (*Client) ListMailboxes

func (c *Client) ListMailboxes(ctx context.Context) ([]*MailboxInfo, error)

ListMailboxes fetches all folders plus lightweight statistics for each.

func (*Client) ListSubfolders added in v1.0.4

func (c *Client) ListSubfolders(ctx context.Context, folder, delimiter string) ([]string, error)

ListSubfolders returns subfolders of folder, derived from the cached mailbox list. delimiter is used to compute the prefix; when empty the client's cached server delimiter is substituted.

func (*Client) Logout added in v1.1.0

func (c *Client) Logout() error

Logout terminates the IMAP session.

func (*Client) MailboxExists added in v1.1.0

func (c *Client) MailboxExists(ctx context.Context, name string) (bool, error)

MailboxExists reports whether a mailbox with the given name exists on the server. Backed by the per-Client mailbox cache so that the many existence checks done at planning time amortize to a single LIST.

func (*Client) SetPrefix

func (c *Client) SetPrefix(p string)

SetPrefix configures the log prefix used in progress messages.

func (*Client) SetProgressTracker

func (c *Client) SetProgressTracker(t ProgressTracker)

SetProgressTracker sets an optional progress tracker for updating progress. Safe for concurrent use; see SetProgressWriter.

func (*Client) SetProgressWriter

func (c *Client) SetProgressWriter(pw ProgressWriter)

SetProgressWriter sets the progress writer for logging. Safe to call from any goroutine, including while another goroutine is using the writer.

func (*Client) StreamMessagesByUIDs added in v1.1.0

func (c *Client) StreamMessagesByUIDs(ctx context.Context, folder string, uids []uint32, onMessage func(*imap.Message) error) error

StreamMessagesByUIDs fetches the bodies for the given UIDs in batches and invokes onMessage for each as it arrives. The caller is expected to feed in UIDs already filtered against the destination — typically the result of a Message-Id diff produced from two FetchMessageMap calls.

Streaming avoids materializing every body into memory at once; large mailboxes can produce many GB of cumulative body data.

If onMessage returns an error, the channel from the in-flight batch is drained (so the producer goroutine exits cleanly) and the error is returned without scheduling further batches.

type ErrClass added in v1.1.0

type ErrClass int

ErrClass categorizes IMAP errors so that the reconnect machinery can pick a strategy: retry quickly, give up, or back off for a long time.

Classification is heuristic — IMAP responses are textual and there is no standard error code, so we match on error strings emitted by go-imap and the most common server-side messages (Gmail in particular).

const (
	// ClassUnknown is the zero value: the error has not been classified.
	// Caller should treat it as non-retryable to avoid loops on novel errors.
	ClassUnknown ErrClass = iota
	// ClassTransient indicates a network-level failure (EOF, closed conn,
	// timeout). Reconnect-and-retry is appropriate.
	ClassTransient
	// ClassPermanent indicates an authoritative refusal (auth failure, bad
	// command, missing mailbox). Retry will not help.
	ClassPermanent
	// ClassThrottled indicates a server-signaled rate limit or quota hit.
	// Reconnecting immediately would compound the problem; back off.
	ClassThrottled
)

func (ErrClass) String added in v1.1.0

func (c ErrClass) String() string

String makes the constants self-describing in logs.

type MailboxInfo

type MailboxInfo struct {
	Name     string
	Messages uint32
	Size     uint64
}

MailboxInfo describes message counts and sizes for a single folder.

type Options added in v1.1.0

type Options struct {
	TLSConfig    *tls.Config
	ReadLimiter  *rate.Limiter
	WriteLimiter *rate.Limiter
	DialTimeout  time.Duration
	UseTLS       bool
	Verbose      bool
}

Options carries the optional knobs for New. Zero-value is fine for plain TLS connections without throttling.

ReadLimiter and WriteLimiter, when non-nil, are typically shared across every Client that talks to the same account so that the byte budget is a global cap, not a per-connection cap.

type ProgressTracker

type ProgressTracker interface {
	UpdateMessage(msg string)
	UpdateTotal(total int64)
	Increment(value int64)
	MarkAsErrored()
}

ProgressTracker is a minimal interface for updating tracker messages.

type ProgressWriter

type ProgressWriter interface {
	Log(msg string, a ...any)
}

ProgressWriter is a minimal interface for progress tracking and logging. This avoids circular dependency with the progress package.

type Provider added in v1.1.0

type Provider struct {
	Name           string
	Notes          string
	DownBPS        int // recommended ceiling, bytes/sec
	UpBPS          int
	DailyDownMB    int
	DailyUpMB      int
	MaxConnections int
}

Provider describes a known IMAP service and the practical limits clients should respect to avoid hitting server-side throttling.

Numbers come from imapsync's empirical recommendations (FAQ.Gmail.txt) and from the official Workspace bandwidth documentation. They are guidance, not hard constants — a server may tighten or relax them at any time.

func DetectProvider added in v1.1.0

func DetectProvider(serverAddr string) (Provider, bool)

DetectProvider returns Provider data for serverAddr if its host is known. serverAddr may include a port (host:port).

Jump to

Keyboard shortcuts

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