mail

package
v0.2.19 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 5, 2026 License: MPL-2.0 Imports: 34 Imported by: 0

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

View Source
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, u usage.Sink, log *zap.Logger, cfg Config) *Mailer

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.

func (*Mailer) Send

func (m *Mailer) Send(ctx context.Context, tenant string, in []byte) (event.Payload, error)

Send is the op body. tenant is the PINNED request tenant (resolved by the caller from the trusted context, NOT the mutable envelope) — load-bearing for the campaign key and the From-domain anti-spoof check.

type SubmitFunc

type SubmitFunc func(ctx context.Context, from string, rcpts []string, msg []byte) error

SubmitFunc hands one rendered RFC 5322 message to the relay for `from` → `rcpts` (the envelope recipients: the To recipient plus any Cc/Bcc). Injectable so tests can substitute a fake.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL