mail

package
v0.3.0 Latest Latest
Warning

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

Go to latest
Published: Oct 19, 2026 License: AGPL-3.0 Imports: 9 Imported by: 0

Documentation

Overview

Package mail defines the Mailbox interface every mail provider implements, and the types and errors shared by its callers. The IMAP implementation is in mail/imap and the in-memory fake for tests is in mail/mailtest.

Index

Constants

View Source
const (
	RoleJunk    = `\Junk`
	RoleTrash   = `\Trash`
	RoleArchive = `\Archive`
	RoleSent    = `\Sent`
	RoleDrafts  = `\Drafts`
)

Special-use folder roles (RFC 6154), as stored in folders.special_use.

Variables

View Source
var (
	// ErrAuth means the server refused the credentials. Do not retry; ask the user.
	ErrAuth = errors.New("authentication failed")
	// ErrReconnect means a mailbox that signs in with OAuth (one-click sign-in) can no longer
	// get in: the person revoked MailRules' access, the grant expired, or the server refused
	// the access token. Do not retry; the person must sign in to the provider again.
	ErrReconnect = errors.New("one-click sign-in needs to be renewed")
	// ErrTLS means the TLS handshake or certificate check failed. Do not retry. A
	// certificate the person could accept, or one that replaced the accepted one, is a
	// *CertError, which wraps ErrTLS.
	ErrTLS = errors.New("tls failure")
	// ErrConnection means the connection was lost, reset or timed out. Reconnect.
	ErrConnection = errors.New("connection lost")
	// ErrThrottled means the server is unavailable or rate limiting. Back off for minutes.
	ErrThrottled = errors.New("server unavailable or throttling")
	// ErrUnsupported means the server lacks a capability the operation needs.
	ErrUnsupported = errors.New("unsupported capability")
	// ErrNoFolder means the folder does not exist. Re-run folder discovery.
	ErrNoFolder = errors.New("no such folder")
	// ErrNotFound means the message is not there: wrong UID, or the folder's UIDVALIDITY changed.
	ErrNotFound = errors.New("message not found")
	// ErrNotPublic means the server is, or its name resolves to, an address that is not on
	// the public internet (loopback, a private network, link-local and the like), which the
	// hosted build does not connect to for a server the person named (publicnet). Nothing
	// was dialed. Do not retry; the person must name a server on the public internet. Its
	// text is shown to the person as it is.
	ErrNotPublic = errors.New("the hosted version of MailRules can only connect to mail servers on the public internet")
)

Sentinel errors. Implementations wrap them, so callers test with errors.Is.

View Source
var ErrFingerprint = errors.New("a SHA-256 fingerprint is 64 hex digits, in pairs separated by colons or not")

ErrFingerprint means a fingerprint is not 32 bytes of hex.

Functions

func Fingerprint added in v0.3.0

func Fingerprint(der []byte) string

Fingerprint returns the SHA-256 fingerprint of a DER certificate, as Cert.Fingerprint has it.

func ParseFingerprint added in v0.3.0

func ParseFingerprint(s string) (string, error)

ParseFingerprint reads a SHA-256 fingerprint as people copy it: any case, with or without colons or spaces between the pairs. It returns it as Cert.Fingerprint has it.

Types

type Caps

type Caps struct {
	Move       bool
	UIDPlus    bool
	SpecialUse bool
	Idle       bool
	All        []string // every advertised capability, sorted; stored in accounts.capabilities
}

Caps are the server capabilities the daemon cares about.

func (Caps) CanMove

func (c Caps) CanMove() bool

CanMove reports whether messages can be moved without risking other mail: UID MOVE, or UID COPY followed by a UID EXPUNGE of exactly the copied UID.

type Cert added in v0.3.0

type Cert struct {
	// Fingerprint is the SHA-256 of the certificate (its DER bytes) as upper-case hex pairs
	// separated by colons, the form browsers and `openssl x509 -fingerprint -sha256` show.
	Fingerprint string
	Subject     string
	Issuer      string
	NotBefore   time.Time
	NotAfter    time.Time
}

Cert describes a mail server's certificate as a person checks it before accepting it.

func CertOf added in v0.3.0

func CertOf(c *x509.Certificate) Cert

CertOf describes c.

type CertError added in v0.3.0

type CertError struct {
	Host   string
	Cert   Cert   // what the server presented
	Pinned string // the fingerprint accepted for the account; empty when none was
	Reason string // why the system does not trust Cert; empty when Pinned is set
}

CertError is a server certificate MailRules will not use as it is. With Pinned empty, the system does not trust it (Reason says why) and no certificate was accepted for the account; the person can accept this one by its fingerprint. With Pinned set, a certificate was accepted and the server now presents another. It wraps ErrTLS: retrying cannot fix it, only the person can.

func (*CertError) Error added in v0.3.0

func (e *CertError) Error() string

func (*CertError) Unwrap added in v0.3.0

func (e *CertError) Unwrap() error

type Folder

type Folder struct {
	Name       string
	Delimiter  string // empty when the server has a flat namespace
	SpecialUse string // one of the Role constants, or empty
}

Folder is one selectable folder. Name is the server's own name, hierarchy delimiter included.

type FolderStatus

type FolderStatus struct {
	UIDValidity uint32
	UIDNext     uint32
}

FolderStatus is what a caller needs to baseline a folder before watching it.

type Mailbox

type Mailbox interface {
	Capabilities() Caps
	// Folders lists selectable folders with their special-use role.
	Folders(ctx context.Context) ([]Folder, error)
	// EnsureFolder creates the folder, and any missing parents, if it does not exist.
	EnsureFolder(ctx context.Context, name string) error
	// Status returns the folder's UIDVALIDITY and UIDNEXT. Callers compare UIDValidity with
	// the stored one and, when it differs (or on first connect), baseline lastUID to
	// UIDNext-1 so a whole mailbox is never re-sorted.
	Status(ctx context.Context, folder string) (FolderStatus, error)
	// Watch blocks until ctx is done. It sends every message newer than lastUID,
	// first as catch-up, then as arrivals are reported, in UID order. It reconnects on
	// its own and returns early only for errors that retrying cannot fix (ErrAuth, ErrTLS).
	Watch(ctx context.Context, folder string, lastUID uint32, out chan<- NewMail) error
	// Fetch reads headers and at most maxBody bytes of body without marking the message read.
	Fetch(ctx context.Context, ref MsgRef, maxBody int) (*message.Raw, error)
	// FetchMany reads several messages of one folder in one request, each like Fetch. The
	// result lines up with refs; a message that is gone is nil. refs must all name the same
	// folder and UIDVALIDITY.
	FetchMany(ctx context.Context, refs []MsgRef, maxBody int) ([]*message.Raw, error)
	// FetchSince lists messages received on or after since's date, oldest first.
	// A positive limit keeps only the newest limit messages. matched is how many messages
	// the folder holds in that range before the limit was applied, so a caller can say
	// "the newest 2,000 of 4,310"; the search that finds them already knows it.
	FetchSince(ctx context.Context, folder string, since time.Time, limit int) (refs []MsgRef, matched int, err error)
	// Move moves one message and returns where it now lives.
	Move(ctx context.Context, ref MsgRef, dest string) (MsgRef, error)
	// MoveMany moves several messages of one folder to dest with as few commands as the
	// server allows, and returns where each now lives, lined up with refs. A message it did
	// not move is a zero MsgRef: it is not there, the folder's UIDVALIDITY changed, or (on a
	// server without UIDPLUS) it could not be found in dest afterwards; Move on its own says
	// which. refs must all name the same folder and UIDVALIDITY.
	MoveMany(ctx context.Context, refs []MsgRef, dest string) ([]MsgRef, error)
	SetFlags(ctx context.Context, ref MsgRef, add, remove []string) error
	Flags(ctx context.Context, ref MsgRef) ([]string, error)
	// FindByMessageID returns the newest message in folder with that Message-ID, or ErrNotFound.
	FindByMessageID(ctx context.Context, folder, messageID string) (MsgRef, error)
	// Present reports which of uids are still in folder, with one search that reads nothing
	// of the messages and changes nothing. A message marked \Deleted counts as gone. When
	// the folder's UIDVALIDITY is no longer uidValidity the UIDs name other messages or
	// none, and it fails with ErrNotFound.
	Present(ctx context.Context, folder string, uidValidity uint32, uids []uint32) ([]uint32, error)
	// SentRecipients reads the To and Cc addresses of the newest limit messages in folder
	// (headers only), one entry per address with the latest time it was written to.
	SentRecipients(ctx context.Context, folder string, limit int) ([]Recipient, error)
	// Append adds raw, a whole RFC 5322 message, to folder with flags and date as its
	// internal date, and returns where it now is: from the server's APPENDUID (UIDPLUS), or
	// else by a search for raw's Message-ID, which it must then have. It is how MailRules
	// saves a draft reply; nothing else is ever added to a mailbox.
	Append(ctx context.Context, folder string, raw []byte, flags []string, date time.Time) (MsgRef, error)
	// Delete removes the one message at ref for good: UID STORE +FLAGS (\Deleted), then UID
	// EXPUNGE of that UID alone, never a bare EXPUNGE, which would also remove mail other
	// clients had marked deleted. A server without UIDPLUS gets ErrUnsupported and nothing
	// is changed. Only the undo of a draft MailRules appended calls it, on exactly that UID.
	Delete(ctx context.Context, ref MsgRef) error
	Close() error
}

Mailbox is one account on one mail server. Implementations are safe for concurrent use.

type MsgRef

type MsgRef struct {
	AccountID   int64
	Folder      string
	UIDValidity uint32
	UID         uint32
}

MsgRef addresses one message. A UID is only meaningful together with its UIDValidity.

type NewMail

type NewMail struct{ Ref MsgRef }

NewMail is what Watch reports for each arrival.

type Recipient

type Recipient struct {
	Address string // lower-cased
	SentAt  time.Time
}

Recipient is an address the user sent mail to, read from a Sent folder.

Directories

Path Synopsis
Package imap implements mail.Mailbox over IMAP with go-imap v2.
Package imap implements mail.Mailbox over IMAP with go-imap v2.
imaptest
Package imaptest runs go-imap's in-memory IMAP server over TLS for integration tests.
Package imaptest runs go-imap's in-memory IMAP server over TLS for integration tests.
Package mailtest is an in-memory mail.Mailbox for tests of the packages that sit on top of the connector.
Package mailtest is an in-memory mail.Mailbox for tests of the packages that sit on top of the connector.
Package presets holds per-provider connection defaults: host, port, TLS mode, folder-name fallbacks for servers without SPECIAL-USE, and known quirks.
Package presets holds per-provider connection defaults: host, port, TLS mode, folder-name fallbacks for servers without SPECIAL-USE, and known quirks.
Package publicnet keeps the hosted build's connections to mail servers on the public internet.
Package publicnet keeps the hosted build's connections to mail servers on the public internet.

Jump to

Keyboard shortcuts

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