imap

package
v1.0.2 Latest Latest
Warning

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

Go to latest
Published: Mar 13, 2026 License: MIT Imports: 27 Imported by: 0

Documentation

Overview

Package imap provides IMAP client with connection pooling, rate limiting, and retry.

Index

Constants

This section is empty.

Variables

View Source
var ErrPoolClosed = errors.New("pool is closed")

ErrPoolClosed is returned when Get is called on a closed pool.

View Source
var ErrTokenUnchanged = errors.New("OAuth2 token unchanged after refresh")

ErrTokenUnchanged indicates token refresh returned the same token.

Functions

This section is empty.

Types

type Client

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

Client wraps an IMAP connection with rate limiting and retry.

func (*Client) AccountID

func (c *Client) AccountID() string

AccountID returns the account ID for this client.

func (*Client) Close

func (c *Client) Close() error

Close closes the IMAP connection.

func (*Client) CopyMessage

func (c *Client) CopyMessage(
	ctx context.Context,
	mailbox string,
	uid uint32,
	destination string,
) error

CopyMessage copies a message to a different mailbox.

func (*Client) CreateFolder

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

CreateFolder creates a new mailbox.

func (*Client) DeleteDraft

func (c *Client) DeleteDraft(ctx context.Context, uid uint32) error

DeleteDraft deletes a draft after sending.

func (*Client) DeleteMessage

func (c *Client) DeleteMessage(
	ctx context.Context,
	mailbox string,
	uid uint32,
	permanent bool,
) error

DeleteMessage deletes a message (moves to Trash or expunges).

func (*Client) GetAttachment

func (c *Client) GetAttachment(
	ctx context.Context,
	mailbox string,
	uid uint32,
	index int,
) ([]byte, string, error)

GetAttachment downloads a specific attachment by index, returning raw bytes and filename.

func (*Client) GetAttachments

func (c *Client) GetAttachments(
	ctx context.Context,
	mailbox string,
	uid uint32,
) ([]models.AttachmentInfo, error)

GetAttachments returns attachment metadata for a message by parsing its BODYSTRUCTURE.

func (*Client) GetDraft

func (c *Client) GetDraft(ctx context.Context, uid uint32) ([]byte, error)

GetDraft retrieves a draft message.

func (*Client) GetMessage

func (c *Client) GetMessage(
	ctx context.Context,
	mailbox string,
	uid uint32,
) (*models.Email, error)

GetMessage retrieves a single message by UID.

func (*Client) IsConnected

func (c *Client) IsConnected() bool

IsConnected checks if the connection is still alive.

func (*Client) LastActivity

func (c *Client) LastActivity() time.Time

LastActivity returns the time of the last IMAP activity.

func (*Client) ListFolders

func (c *Client) ListFolders(ctx context.Context) ([]models.Folder, error)

ListFolders returns all mailboxes for the account. Uses STATUS-only (no Select) for O(n) roundtrips instead of O(2n).

func (*Client) ListMessages

func (c *Client) ListMessages(
	ctx context.Context,
	mailbox string,
	limit, offset int,
	includeBody bool,
) ([]models.Email, error)

ListMessages returns messages from a mailbox.

func (*Client) ListUnread

func (c *Client) ListUnread(
	ctx context.Context,
	mailbox string,
	limit int,
	includeBody bool,
) ([]models.Email, error)

ListUnread returns unread messages from a mailbox.

func (*Client) MarkRead

func (c *Client) MarkRead(
	ctx context.Context,
	mailbox string,
	uid uint32,
	read bool,
) error

MarkRead marks a message as read or unread.

func (*Client) MoveMessage

func (c *Client) MoveMessage(
	ctx context.Context,
	mailbox string,
	uid uint32,
	destination string,
) error

MoveMessage moves a message to a different mailbox.

func (*Client) RateLimitInfo

func (c *Client) RateLimitInfo() (int, int, time.Time)

RateLimitInfo returns current rate limit state.

func (*Client) SaveDraft

func (c *Client) SaveDraft(ctx context.Context, literal []byte) (uint32, error)

SaveDraft saves a message as a draft.

func (*Client) Search

func (c *Client) Search(
	ctx context.Context,
	mailbox, query, from, to, since, before string,
	limit int,
	includeBody bool,
) ([]models.Email, error)

Search searches for messages matching criteria.

func (*Client) SearchByMessageID

func (c *Client) SearchByMessageID(
	ctx context.Context,
	mailbox, messageID string,
) ([]models.Email, error)

SearchByMessageID searches for messages whose Message-ID or References header contains the given message ID. Results are deduplicated and sorted by date ascending.

func (*Client) SetFlag

func (c *Client) SetFlag(
	ctx context.Context,
	mailbox string,
	uid uint32,
	flagged bool,
) error

SetFlag sets or clears the flagged status.

type Connector

type Connector interface {
	// Login performs IMAP LOGIN command (password authentication).
	Login(username, password string) error

	// Authenticate performs SASL authentication (e.g., XOAUTH2).
	Authenticate(saslClient sasl.Client) error

	// Select selects a mailbox.
	Select(mailbox string, opts *imap.SelectOptions) (*imap.SelectData, error)

	// List lists mailboxes matching a pattern and collects all results.
	List(ref, pattern string, opts *imap.ListOptions) ([]*imap.ListData, error)

	// Status gets mailbox status.
	Status(mailbox string, opts *imap.StatusOptions) (*imap.StatusData, error)

	// Fetch fetches messages and collects all results into buffers.
	Fetch(seqSet imap.NumSet, opts *imap.FetchOptions) ([]*imapclient.FetchMessageBuffer, error)

	// Search searches for messages matching criteria.
	Search(criteria *imap.SearchCriteria, opts *imap.SearchOptions) (*imap.SearchData, error)

	// Move moves messages to a destination mailbox.
	Move(uidSet imap.UIDSet, dest string) error

	// Copy copies messages to a destination mailbox.
	Copy(uidSet imap.UIDSet, dest string) error

	// Store modifies message flags, draining all results.
	Store(seqSet imap.NumSet, flags *imap.StoreFlags, opts *imap.StoreOptions) error

	// Expunge permanently removes messages marked as deleted.
	Expunge() error

	// Append appends a message literal to a mailbox.
	Append(mailbox string, literal []byte, opts *imap.AppendOptions) (*imap.AppendData, error)

	// Create creates a new mailbox.
	Create(name string, opts *imap.CreateOptions) error

	// Close closes the underlying connection.
	Close() error
}

Connector abstracts the imapclient.Client methods used by Client. This enables mock injection for unit testing without a live IMAP server.

Design decision: Uses a high-level interface that collapses the two-step `cmd := conn.Method(); result, err := cmd.Wait()` pattern into single calls. This is necessary because *imapclient.XxxCommand types require an active connection for their Wait()/Next()/Collect() methods to function — they cannot be meaningfully constructed in test code.

type Operations

type Operations interface {
	ListFolders(ctx context.Context, accountID string) ([]models.Folder, error)
	GetFolderByRole(ctx context.Context, accountID string, role models.FolderRole) (string, error)
	ListMessages(
		ctx context.Context, accountID, folder string,
		limit, offset int, includeBody bool,
	) ([]models.Email, error)
	ListUnread(
		ctx context.Context, accountID, folder string,
		limit int, includeBody bool,
	) ([]models.Email, error)
	Search(
		ctx context.Context,
		accountID, mailbox, query, from, to, since, before string,
		limit int, includeBody bool,
	) ([]models.Email, error)
	GetMessage(ctx context.Context, accountID, folder string, uid uint32) (*models.Email, error)
	SearchByMessageID(ctx context.Context, accountID, folder, messageID string) ([]models.Email, error)
	GetAttachments(ctx context.Context, accountID, folder string, uid uint32) ([]models.AttachmentInfo, error)
	GetAttachment(ctx context.Context, accountID, folder string, uid uint32, index int) ([]byte, string, error)
	MoveMessage(ctx context.Context, accountID, folder string, uid uint32, dest string) error
	CopyMessage(ctx context.Context, accountID, folder string, uid uint32, dest string) error
	DeleteMessage(
		ctx context.Context, accountID, folder string,
		uid uint32, permanent bool,
	) error
	MarkRead(ctx context.Context, accountID, folder string, uid uint32, read bool) error
	SetFlag(ctx context.Context, accountID, folder string, uid uint32, flagged bool) error
	CreateFolder(ctx context.Context, accountID, name string) error
	SaveDraft(ctx context.Context, accountID string, msg []byte) (uint32, error)
	GetDraft(ctx context.Context, accountID string, uid uint32) ([]byte, error)
	DeleteDraft(ctx context.Context, accountID string, uid uint32) error
	AccountStatus() []models.AccountStatus
	DefaultAccountID() string
}

Operations defines the IMAP operations interface. Tool handlers accept this interface instead of concrete *Pool, enabling mock injection for unit tests.

type Pool

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

Pool manages IMAP connections for multiple accounts.

Every public method that performs I/O calls wg.Add(1)/defer wg.Done() to track in-flight operations. Close() sets the closing flag and waits on wg before tearing down connections — this ensures clean shutdown without interrupting active IMAP commands mid-operation.

func NewPool

func NewPool(cfg *config.Config) *Pool

NewPool creates a new IMAP connection pool.

func (*Pool) AccountStatus

func (p *Pool) AccountStatus() []models.AccountStatus

AccountStatus returns connection status for all accounts.

func (*Pool) Close

func (p *Pool) Close(ctx context.Context)

Close gracefully shuts down the pool. It rejects new Get calls, waits for in-flight operations to complete (or until ctx is cancelled), then closes all connections. On timeout, connections are force-closed to unblock hung operations.

func (*Pool) CopyMessage

func (p *Pool) CopyMessage(
	ctx context.Context,
	accountID, folder string,
	uid uint32,
	dest string,
) error

CopyMessage copies a message to a different mailbox for the given account.

func (*Pool) CreateFolder

func (p *Pool) CreateFolder(ctx context.Context, accountID, name string) error

CreateFolder creates a new mailbox for the given account.

func (*Pool) DefaultAccountID

func (p *Pool) DefaultAccountID() string

DefaultAccountID returns the configured default account ID.

func (*Pool) DeleteDraft

func (p *Pool) DeleteDraft(ctx context.Context, accountID string, uid uint32) error

DeleteDraft deletes a draft for the given account.

func (*Pool) DeleteMessage

func (p *Pool) DeleteMessage(
	ctx context.Context,
	accountID, folder string,
	uid uint32,
	permanent bool,
) error

DeleteMessage deletes a message for the given account.

func (*Pool) Get

func (p *Pool) Get(ctx context.Context, accountID string) (*Client, error)

Get returns an IMAP client for the given account, creating one if needed. The pool lock is NOT held during client creation (network I/O), so concurrent requests for different accounts do not block each other. Returns ErrPoolClosed if the pool is shutting down.

func (*Pool) GetAttachment

func (p *Pool) GetAttachment(
	ctx context.Context,
	accountID, folder string,
	uid uint32,
	index int,
) ([]byte, string, error)

GetAttachment downloads a specific attachment by index, returning raw bytes and filename.

func (*Pool) GetAttachments

func (p *Pool) GetAttachments(
	ctx context.Context,
	accountID, folder string,
	uid uint32,
) ([]models.AttachmentInfo, error)

GetAttachments returns attachment metadata for a message.

func (*Pool) GetDraft

func (p *Pool) GetDraft(ctx context.Context, accountID string, uid uint32) ([]byte, error)

GetDraft retrieves a draft message for the given account.

func (*Pool) GetFolderByRole

func (p *Pool) GetFolderByRole(
	ctx context.Context,
	accountID string,
	role models.FolderRole,
) (string, error)

GetFolderByRole resolves a folder role to a mailbox name for the given account.

func (*Pool) GetMessage

func (p *Pool) GetMessage(
	ctx context.Context,
	accountID, folder string,
	uid uint32,
) (*models.Email, error)

GetMessage retrieves a single message by UID for the given account.

func (*Pool) ListFolders

func (p *Pool) ListFolders(ctx context.Context, accountID string) ([]models.Folder, error)

ListFolders returns all mailboxes for the given account.

func (*Pool) ListMessages

func (p *Pool) ListMessages(
	ctx context.Context,
	accountID, folder string,
	limit, offset int,
	includeBody bool,
) ([]models.Email, error)

ListMessages returns messages from a mailbox for the given account.

func (*Pool) ListUnread

func (p *Pool) ListUnread(
	ctx context.Context,
	accountID, folder string,
	limit int,
	includeBody bool,
) ([]models.Email, error)

ListUnread returns unread messages from a mailbox for the given account.

func (*Pool) MarkRead

func (p *Pool) MarkRead(
	ctx context.Context,
	accountID, folder string,
	uid uint32,
	read bool,
) error

MarkRead marks a message as read or unread for the given account.

func (*Pool) MoveMessage

func (p *Pool) MoveMessage(
	ctx context.Context,
	accountID, folder string,
	uid uint32,
	dest string,
) error

MoveMessage moves a message to a different mailbox for the given account.

func (*Pool) SaveDraft

func (p *Pool) SaveDraft(ctx context.Context, accountID string, msg []byte) (uint32, error)

SaveDraft saves a message as a draft for the given account.

func (*Pool) Search

func (p *Pool) Search(
	ctx context.Context,
	accountID, mailbox, query, from, to, since, before string,
	limit int,
	includeBody bool,
) ([]models.Email, error)

Search searches for messages matching criteria for the given account.

func (*Pool) SearchByMessageID

func (p *Pool) SearchByMessageID(
	ctx context.Context,
	accountID, folder, messageID string,
) ([]models.Email, error)

SearchByMessageID searches for messages by Message-ID or References header.

func (*Pool) SetFlag

func (p *Pool) SetFlag(
	ctx context.Context,
	accountID, folder string,
	uid uint32,
	flagged bool,
) error

SetFlag sets or clears the flagged status for the given account.

Jump to

Keyboard shortcuts

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