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 ¶
func NewMailer(db *sql.DB, dialect registry.Dialect, snap func() *sql.DB, u usage.Sink, log *zap.Logger, cfg Config) *Mailer
NewMailer builds a Mailer. db must be the real runtime DB (e.g. pu.RuntimeDB) and dialect its DB dialect (pu.RuntimeDialect) so the runtime queries rebind for Postgres. snap is the dbcache mirror snapshot getter (pu.Dbc.Snapshot) used for the per-send verification READS — pass the method value, never a captured *sql.DB (Reload swaps the handle); nil falls back to db. 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.