agentbbs/internal/mailbox/types.go
Anthony Ettinger e165ecee26 Add AgentMail: paid-member mailbox reader (TUI + bot mode) on the BBS
internal/mailbox is a transport-agnostic mail client for Founding Lifetime
members: read, search, compose, send, reply, flag, delete.

- types/transport/client: paid-gated facade returning JSON-serializable
  structs (same shapes as @logicsrc/plugin-agentmail)
- memory.go: in-memory transport (tests/dev/reference) + tests
- imap.go/smtp.go: real backend — go-imap/v2 (Dovecot IMAP, master-user
  login) + net/smtp submission via the co-located relay; go-message parses
  bodies/attachments
- reader_tui.go: Bubble Tea reader for humans (list/read/flag/delete)
- bot.go: JSON in/out mode for agents (ssh mail@host list|read|send|…)
- wired as the paid "Mail" hub entry and the ssh mail@ route + auth.MailNames

Connects to the self-hosted Mailu stack at mail.profullstack.com /
smtp.profullstack.com. Rebased onto main; build + vet + tests clean (Go 1.26).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 16:49:04 +00:00

99 lines
2.8 KiB
Go

// Package mailbox is the BBS-side mail client for Founding Lifetime members: 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. It
// talks to the self-hosted Mailu stack (Dovecot IMAP + Postfix submission) at
// mail.profullstack.com / smtp.profullstack.com.
//
// The TS counterpart is @logicsrc/plugin-agentmail; the domain shapes here are
// deliberately the same so tooling can move between them.
package mailbox
import "time"
// Address is a parsed mail address.
type Address struct {
Name string `json:"name,omitempty"`
Address string `json:"address"`
}
// Mailbox is an IMAP folder with unread/total counts.
type Mailbox struct {
Name string `json:"name"`
Path string `json:"path"`
Unseen int `json:"unseen"`
Total int `json:"total"`
}
// MessageSummary is a lightweight row for list/search views.
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"`
}
// Attachment is attachment metadata (bytes fetched separately).
type Attachment struct {
Filename string `json:"filename"`
ContentType string `json:"contentType"`
Size int `json:"size"`
}
// Message is a fully fetched 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"`
}
// Draft is an outgoing message.
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"`
}
// ListOptions controls a mailbox listing.
type ListOptions struct {
Mailbox string
Limit int
}
// SearchOptions controls a search.
type SearchOptions struct {
Mailbox string // empty = all mailboxes
Query string
Limit int
}
// FlagChange is a partial flag update; nil fields are left unchanged.
type FlagChange struct {
Seen *bool
Flagged *bool
}
// SendResult reports a sent message's id.
type SendResult struct {
MessageID string `json:"messageId"`
}
const (
// Inbox is the default mailbox.
Inbox = "INBOX"
// Sent is where sent mail is stored.
Sent = "Sent"
)