Documentation
¶
Overview ¶
Package mail implements the bundled txco://sendmail op: it reads the `_sendmail` envelope contract a rule assembled, renders an email (a shell template wrapping the body — the bundled default, or a caller-supplied `_sendmail.templates.html`), enforces the per-tenant campaign at-most-once guard, verifies the From domain, submits to a relay, and emits a usage line. The common case is to/subject/body/from; attachments and policy caps are later phases (see internal docs/todo-sendmail.md).
Index ¶
Constants ¶
This section is empty.
Variables ¶
var ErrNoRelay = errors.New("sendmail: no mail relay configured (set --mail-relay-addr / TXCO_MAIL_RELAY_ADDR)")
ErrNoRelay is returned by the op when no relay is configured.
Functions ¶
This section is empty.
Types ¶
type Config ¶
type Config struct {
RelayAddr string
RelayTLS string // "none" | "starttls"
DialTimeout time.Duration
MaxRecipients int
RateLimits string // per-tenant send caps, e.g. "100/2m,200/4h"; empty disables
}
Config carries the relay + limits resolved from chassis config.
type Mailer ¶
type Mailer struct {
// contains filtered or unexported fields
}
Mailer is the txco://sendmail handler with its injected deps. db is the REAL, writable runtime *sql.DB (stable — dbcache only swaps its in-memory snapshot, not this handle), used for the campaign claim and the From-domain check. usage is nil-safe. submit is injectable (real SMTP in prod, a fake in tests).
func NewMailer ¶
NewMailer builds a Mailer. db must be the real runtime DB (e.g. pu.RuntimeDB); usage/log may be nil.
func (*Mailer) Relay ¶ added in v0.2.19
func (m *Mailer) Relay(ctx context.Context, tenant, source string, in []byte) (event.Payload, error)
Relay is the txco://relay op body — the `.forward` primitive. Unlike Send it does NOT re-compose or re-DKIM-sign: it ships the ORIGINAL RFC 5322 bytes (`_relay.raw`, base64, straight from `@lmtp.msg.raw`) to a new recipient untouched, so the sender's surviving DKIM signature keeps DMARC aligned to their domain and attachments are preserved. It just rewrites the SMTP envelope: MAIL FROM = `_relay.envelope_from` (a return-path the tenant owns, so SPF aligns and bounces route somewhere catchable), RCPT = `_relay.to`.
source is the request's PINNED originating inlet (processor.SourceScope) — the trusted answer to "which inlet started this", captured at first Run, NOT the mutable `_txc.src` envelope field. Because relay ships arbitrary bytes out under a verified return-path (far more dangerous than sendmail, which forces a verified From + signs), it MUST run only from the inbound-mail path: a coerced web/other pipeline could otherwise relay attacker-supplied bytes as a spam cannon riding the domain's reputation. So the first thing Relay does is refuse unless source == "lmtp".
tenant is the PINNED request tenant (processor.TenantScope), used for the defense-in-depth envelope_from-domain-verified check and the campaign guard.