Documentation
¶
Overview ¶
Package mailbox is the BBS-side mail client for members (a free benefit of membership): a transport-agnostic core (read, search, compose, send, flag, delete) with a Bubble Tea TUI for humans and a line-oriented JSON mode for agents/bots. Addresses are <name>@bbs.profullstack.com; it talks to the self-hosted Mailu stack (Dovecot IMAP + Postfix submission) hosted on mail.profullstack.com.
The TS counterpart is @logicsrc/plugin-agentmail; the domain shapes here are deliberately the same so tooling can move between them.
Index ¶
- Constants
- Variables
- func FormatAddress(a Address) string
- func RunBot(ctx context.Context, c *Client, args []string, in io.Reader, out io.Writer) error
- func RunReader(s ssh.Session, c *Client) error
- func Snippet(body string, max int) string
- func ValidEmail(v string) bool
- func VerifyLogin(addr, user, pass string, plaintext bool) error
- type Address
- type Attachment
- type Client
- func (c *Client) Address() string
- func (c *Client) Close() error
- func (c *Client) Delete(ctx context.Context, mailbox string, uid uint32) error
- func (c *Client) Flag(ctx context.Context, mailbox string, uid uint32, flagged bool) error
- func (c *Client) List(ctx context.Context, mailbox string, limit int) ([]MessageSummary, error)
- func (c *Client) Mailboxes(ctx context.Context) ([]Mailbox, error)
- func (c *Client) MarkSeen(ctx context.Context, mailbox string, uid uint32, seen bool) error
- func (c *Client) Read(ctx context.Context, mailbox string, uid uint32, peek bool) (Message, bool, error)
- func (c *Client) Reply(ctx context.Context, orig Message, text string, replyAll bool) (SendResult, error)
- func (c *Client) Search(ctx context.Context, query, mailbox string, limit int) ([]MessageSummary, error)
- func (c *Client) Send(ctx context.Context, d Draft) (SendResult, error)
- type Draft
- type FlagChange
- type IMAPConfig
- type Identity
- type ListOptions
- type Mailbox
- type MemoryTransport
- func (m *MemoryTransport) Add(msg Message) Message
- func (m *MemoryTransport) Close() error
- func (m *MemoryTransport) DeleteMessage(_ context.Context, mailbox string, uid uint32) error
- func (m *MemoryTransport) ListMailboxes(_ context.Context) ([]Mailbox, error)
- func (m *MemoryTransport) ListMessages(_ context.Context, opts ListOptions) ([]MessageSummary, error)
- func (m *MemoryTransport) ReadMessage(_ context.Context, mailbox string, uid uint32) (Message, bool, error)
- func (m *MemoryTransport) Search(_ context.Context, opts SearchOptions) ([]MessageSummary, error)
- func (m *MemoryTransport) Send(_ context.Context, from string, d Draft) (SendResult, error)
- func (m *MemoryTransport) SetFlags(_ context.Context, mailbox string, uid uint32, fc FlagChange) error
- type Message
- type MessageSummary
- type SearchOptions
- type SendResult
- type Transport
Constants ¶
const ( // Inbox is the default mailbox. Inbox = "INBOX" // Sent is where sent mail is stored. Sent = "Sent" )
const BotUsage = `` /* 685-byte string literal not displayed */
BotUsage is printed when a bot/agent invokes mail with no/unknown command.
Variables ¶
var ErrNotFound = errors.New("mailbox: message not found")
ErrNotFound is returned when a uid/mailbox cannot be resolved.
var ErrNotMember = errors.New("AgentMail is a member feature — register first: ssh join@bbs.profullstack.com")
ErrNotMember is returned when a caller without a registered handle attempts a mail action. AgentMail is open to every verified member.
Functions ¶
func FormatAddress ¶
FormatAddress renders an Address back to "Name <addr>" (or just the address).
func RunBot ¶
RunBot executes one non-interactive command for agents/bots, writing JSON to out. It returns an error (also emitted as {"error":...}) on failure.
func RunReader ¶
RunReader runs the interactive mail TUI for a member on the session. Used by the hub "Mail" entry and the ssh mail@ route (with a PTY).
func ValidEmail ¶
ValidEmail is a loose RFC5322-ish check: one @, a dotted domain, no spaces.
func VerifyLogin ¶
VerifyLogin checks a username/password against the IMAP backend by logging in and immediately logging out. It returns nil only when the credentials are accepted. The web file browser uses this to authenticate members with their webmail (Mailu/Dovecot) password — the same credential Roundcube uses.
Types ¶
type Address ¶
Address is a parsed mail address.
func ParseAddress ¶
ParseAddress parses "Name <a@b>" or a bare "a@b".
type Attachment ¶
type Attachment struct {
Filename string `json:"filename"`
ContentType string `json:"contentType"`
Size int `json:"size"`
}
Attachment is attachment metadata (bytes fetched separately).
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client is the ergonomic, member-gated facade the TUI and bot mode use. Every method returns plain structs, so the same calls serve humans and agents.
func NewClient ¶
NewClient builds a member-gated client. domain is the email address domain (e.g. bbs.profullstack.com); pageSize defaults to 50 when <= 0.
func (*Client) Address ¶
Address is the member's own mailbox address, e.g. alice@bbs.profullstack.com.
func (*Client) Read ¶
func (c *Client) Read(ctx context.Context, mailbox string, uid uint32, peek bool) (Message, bool, error)
Read fetches a full message, marking it seen unless peek is true.
func (*Client) Reply ¶
func (c *Client) Reply(ctx context.Context, orig Message, text string, replyAll bool) (SendResult, error)
Reply addresses the original sender (and, when replyAll, the other recipients minus the member), prefixes "Re:", threads via In-Reply-To, and sends.
type Draft ¶
type Draft struct {
To []Address `json:"to"`
CC []Address `json:"cc,omitempty"`
BCC []Address `json:"bcc,omitempty"`
Subject string `json:"subject"`
Text string `json:"text"`
InReplyTo string `json:"inReplyTo,omitempty"`
}
Draft is an outgoing message.
func NormalizeDraft ¶
NormalizeDraft trims the subject, drops empty recipient lists, and rejects a draft with no valid recipient.
type FlagChange ¶
FlagChange is a partial flag update; nil fields are left unchanged.
type IMAPConfig ¶
type IMAPConfig struct {
IMAPAddr string // host:port, e.g. mail.profullstack.com:993 (implicit TLS)
SMTPAddr string // host:port, e.g. smtp.profullstack.com:587 (STARTTLS)
Username string
Password string
// SMTPUser/SMTPPass default to Username/Password when empty.
SMTPUser string
SMTPPass string
// SMTPServerName is the TLS server name verified during STARTTLS. Set it when
// the dial host differs from the certificate name — e.g. dialing the trusted
// local relay at 127.0.0.1:25 whose cert is mail.<host>. Empty = use the dial
// host (the net/smtp default).
SMTPServerName string
// Plaintext dials IMAP without TLS. Used only for a co-located backend over
// loopback (the Mailu gateway hitting Dovecot directly on 127.0.0.1, bypassing
// the front's auth proxy so master-user login works) — the password never
// leaves the host. Never enable it for a remote server.
Plaintext bool
}
IMAPConfig connects the adapter to the mail backend (Mailu: Dovecot IMAP + Postfix submission). Username/Password are the resolved login (which may be a Dovecot master-user login like "alice*gateway").
type Identity ¶
type Identity struct {
Name string // local-part / handle, e.g. "alice"
Paid bool // Founding Lifetime Member (informational; does not gate mail)
}
Identity is the acting member. AgentMail is a free benefit of membership, so having a registered handle is the only requirement; Paid is retained for tier-aware features (e.g. quotas) but no longer gates access.
type ListOptions ¶
ListOptions controls a mailbox listing.
type Mailbox ¶
type Mailbox struct {
Name string `json:"name"`
Path string `json:"path"`
Unseen int `json:"unseen"`
Total int `json:"total"`
}
Mailbox is an IMAP folder with unread/total counts.
type MemoryTransport ¶
type MemoryTransport struct {
// contains filtered or unexported fields
}
MemoryTransport is a complete, dependency-free Transport for tests, local development, and as the reference for what the IMAP/SMTP adapter must do.
func NewMemoryTransport ¶
func NewMemoryTransport() *MemoryTransport
NewMemoryTransport returns an empty in-memory transport.
func (*MemoryTransport) Add ¶
func (m *MemoryTransport) Add(msg Message) Message
Add inserts a message, filling defaults, and returns the stored copy.
func (*MemoryTransport) Close ¶
func (m *MemoryTransport) Close() error
func (*MemoryTransport) DeleteMessage ¶
func (*MemoryTransport) ListMailboxes ¶
func (m *MemoryTransport) ListMailboxes(_ context.Context) ([]Mailbox, error)
func (*MemoryTransport) ListMessages ¶
func (m *MemoryTransport) ListMessages(_ context.Context, opts ListOptions) ([]MessageSummary, error)
func (*MemoryTransport) ReadMessage ¶
func (*MemoryTransport) Search ¶
func (m *MemoryTransport) Search(_ context.Context, opts SearchOptions) ([]MessageSummary, error)
func (*MemoryTransport) Send ¶
func (m *MemoryTransport) Send(_ context.Context, from string, d Draft) (SendResult, error)
func (*MemoryTransport) SetFlags ¶
func (m *MemoryTransport) SetFlags(_ context.Context, mailbox string, uid uint32, fc FlagChange) error
type Message ¶
type Message struct {
MessageSummary
CC []Address `json:"cc,omitempty"`
ReplyTo *Address `json:"replyTo,omitempty"`
MessageID string `json:"messageId"`
References []string `json:"references,omitempty"`
Text string `json:"text"`
HTML string `json:"html,omitempty"`
Attachments []Attachment `json:"attachments,omitempty"`
}
Message is a fully fetched message.
type MessageSummary ¶
type MessageSummary struct {
UID uint32 `json:"uid"`
Mailbox string `json:"mailbox"`
From Address `json:"from"`
To []Address `json:"to"`
Subject string `json:"subject"`
Date time.Time `json:"date"`
Seen bool `json:"seen"`
Flagged bool `json:"flagged"`
HasAttachments bool `json:"hasAttachments"`
Snippet string `json:"snippet"`
}
MessageSummary is a lightweight row for list/search views.
type SearchOptions ¶
SearchOptions controls a search.
type SendResult ¶
type SendResult struct {
MessageID string `json:"messageId"`
}
SendResult reports a sent message's id.
type Transport ¶
type Transport interface {
ListMailboxes(ctx context.Context) ([]Mailbox, error)
ListMessages(ctx context.Context, opts ListOptions) ([]MessageSummary, error)
// ReadMessage returns (Message, false, nil) when the uid is unknown.
ReadMessage(ctx context.Context, mailbox string, uid uint32) (Message, bool, error)
Search(ctx context.Context, opts SearchOptions) ([]MessageSummary, error)
// Send delivers an already-validated draft, stamped from `from`.
Send(ctx context.Context, from string, d Draft) (SendResult, error)
SetFlags(ctx context.Context, mailbox string, uid uint32, fc FlagChange) error
DeleteMessage(ctx context.Context, mailbox string, uid uint32) error
Close() error
}
Transport is the low-level mailbox backend. The TUI, the bot mode, and the Client all talk to this interface, so an in-memory fake (tests/dev) and the real IMAP/SMTP adapter are interchangeable.
func NewIMAPTransport ¶
func NewIMAPTransport(cfg IMAPConfig) (Transport, error)
NewIMAPTransport dials the IMAP server, logs in, and returns a Transport.