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 ¶
const ( RoleJunk = `\Junk` RoleTrash = `\Trash` RoleArchive = `\Archive` RoleSent = `\Sent` RoleDrafts = `\Drafts` )
Special-use folder roles (RFC 6154), as stored in folders.special_use.
Variables ¶
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.
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
Fingerprint returns the SHA-256 fingerprint of a DER certificate, as Cert.Fingerprint has it.
func ParseFingerprint ¶ added in v0.3.0
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.
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.
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.
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 ¶
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.
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. |