email

package
v0.1.3 Latest Latest
Warning

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

Go to latest
Published: Oct 1, 2026 License: Apache-2.0 Imports: 32 Imported by: 0

README

Shared email capability

email.NewMailer(options...) is a composable library. The host supplies the SMTP transport, sender and operator recipients, runtime provenance, receipt retention, clock, and optional Prometheus registry. Callers supply only an email.v1.EmailMessage; the Mailer adds a small text and HTML footer labelled Spine provenance.

Delivery and receipt evidence

Send writes an UNKNOWN pre-attempt receipt before network I/O and replaces it with the final outcome. A sink failure before transport is FAILED and safe to retry. A crash after remote acceptance but before final replacement leaves UNKNOWN; blindly retrying can duplicate mail. ACCEPTED means SMTP accepted DATA, not inbox delivery or reading.

FileReceiptSink stores one owner-only (0600) deterministic protobuf file at <private-directory>/<receipt_id>.pb, atomically replacing the same path. The schema is proto/candace/email/v1/email.proto; files contain the bounded EmailReceipt only, never message bodies, SMTP passwords, senders, or recipients. Credential-bearing and userinfo URLs are removed before retention. The sink does not prune records: the host owns retention and must keep the directory private (0700).

Instrumentation contract

The optional metrics are:

  • csf_email_send_total{outcome}
  • csf_email_send_duration_seconds{outcome}

The only outcome label values are accepted, failed, and unknown. Raw SMTP errors, message text, addresses, and session identifiers never appear in metric labels. The root composition owns the dashboard/panel contract in csf/observability/email-dashboard.json; host configuration is documented in csf/docs/operator_email.md. This library exports no server or panel. OpenTelemetry span email.send is emitted through the installed global tracer provider, or is a no-op when none is installed.

Documentation

Overview

Package email provides a composable, host-configured email capability.

The host owns transport credentials, sender and recipients, provenance, and durable receipt retention. Callers provide only bounded message content. Mailer starts no goroutines and opens no network connection until Send.

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrDeliveryFailed means the transport rejected the message before acceptance.
	ErrDeliveryFailed = errors.New("email: delivery failed")
	// ErrDeliveryUnknown means interruption left remote acceptance ambiguous.
	ErrDeliveryUnknown = errors.New("email: delivery outcome is unknown")
	// ErrInvalidTransportOutcome means a transport returned an unusable outcome.
	ErrInvalidTransportOutcome = errors.New("email: transport returned an invalid outcome")
)
View Source
var (
	// ErrNoTransport means no delivery transport was configured.
	ErrNoTransport = errors.New("email: transport is required")
	// ErrNoProvenance means no runtime provenance source was configured.
	ErrNoProvenance = errors.New("email: provenance source is required")
	// ErrNoReceiptSink means no receipt evidence sink was configured.
	ErrNoReceiptSink = errors.New("email: receipt sink is required")
	// ErrNoSender means the host did not configure one valid sender.
	ErrNoSender = errors.New("email: sender is required")
	// ErrNoRecipients means the host did not configure any valid recipients.
	ErrNoRecipients = errors.New("email: at least one recipient is required")
	// ErrHeaderInjection means an address contains a line break.
	ErrHeaderInjection = errors.New("email: address contains a CR or LF")
)
View Source
var (
	// ErrSTARTTLSRequired means the server offered no encrypted upgrade.
	ErrSTARTTLSRequired = errors.New("email: smtp server does not advertise STARTTLS")
	// ErrAuthUnsupported means credentials were configured but encrypted SMTP
	// offered no AUTH extension.
	ErrAuthUnsupported = errors.New("email: smtp server does not advertise AUTH")
)

Functions

This section is empty.

Types

type FileReceiptSink

type FileReceiptSink struct {
	// contains filtered or unexported fields
}

FileReceiptSink stores one deterministic protobuf file per receipt ID. A final record atomically replaces the pre-send UNKNOWN record at the same path. It never stores message bodies, SMTP credentials, senders, or recipients.

func NewFileReceiptSink

func NewFileReceiptSink(directory string) (*FileReceiptSink, error)

NewFileReceiptSink prepares a private receipt directory. Existing directories must already deny group and other access.

func (*FileReceiptSink) Record

func (sink *FileReceiptSink) Record(ctx context.Context, receipt *emailv1.EmailReceipt) error

Record validates and atomically persists receipt as deterministic protobuf.

type IProvenanceSource

type IProvenanceSource interface {
	Snapshot(ctx context.Context) (*provenancev1.ReceiptMetadata, error)
}

IProvenanceSource observes the runtime facts attached to one send attempt. Mailer clones the returned message before assigning receipt-owned fields.

type IReceiptSink

type IReceiptSink interface {
	Record(ctx context.Context, receipt *emailv1.EmailReceipt) error
}

IReceiptSink records bounded receipt evidence. Implementations must treat a later record with the same receipt ID as the final replacement for the pre-send UNKNOWN attempt.

type ITransport

type ITransport interface {
	Send(ctx context.Context, message []byte) (emailv1.DeliveryOutcome, error)
}

ITransport delivers one complete RFC 5322 message. ACCEPTED means only that the transport accepted the message; it does not prove inbox delivery.

type Mailer

type Mailer struct {
	// contains filtered or unexported fields
}

Mailer renders, records, and delivers bounded email messages. Its fields are immutable after construction, and Send starts no background goroutines.

func NewMailer

func NewMailer(options ...Option) (*Mailer, error)

NewMailer validates all required host dependencies before constructing a Mailer.

func (*Mailer) Send

func (mailer *Mailer) Send(ctx context.Context, message *emailv1.EmailMessage) (*emailv1.EmailReceipt, error)

Send records an UNKNOWN attempt before network I/O, then replaces it with the final bounded outcome. A process crash between those records deliberately leaves UNKNOWN because blind retry could duplicate an accepted message.

type Option

type Option func(config *mailerConfig) error

Option configures a Mailer before construction.

func WithAddresses

func WithAddresses(from string, to []string) Option

WithAddresses configures the host-owned envelope and visible address headers.

func WithClock

func WithClock(now func() time.Time) Option

WithClock replaces the real clock, primarily for deterministic verification.

func WithMetrics

func WithMetrics(registerer prometheus.Registerer) Option

WithMetrics registers bounded send metrics with registerer.

func WithProvenance

func WithProvenance(source IProvenanceSource) Option

WithProvenance configures the trusted runtime observation boundary.

func WithReceiptSink

func WithReceiptSink(sink IReceiptSink) Option

WithReceiptSink configures receipt evidence retention.

func WithTransport

func WithTransport(transport ITransport) Option

WithTransport configures the delivery boundary.

type SMTPConfig

type SMTPConfig struct {
	Host     string
	Port     int
	Username string
	Password string
}

SMTPConfig configures a real SMTP transport. The Mailer, not the transport, owns sender and recipients; the transport reads their validated MIME headers.

type SMTPTransport

type SMTPTransport struct {
	// contains filtered or unexported fields
}

SMTPTransport performs one mandatory-STARTTLS SMTP conversation per Send.

func NewSMTPTransport

func NewSMTPTransport(config SMTPConfig) *SMTPTransport

NewSMTPTransport returns a real SMTP transport. Configuration is validated before the first network operation in Send.

func (*SMTPTransport) Send

func (transport *SMTPTransport) Send(ctx context.Context, message []byte) (emailv1.DeliveryOutcome, error)

Send delivers message with STARTTLS mandatory. Failure while closing DATA is UNKNOWN because the server may have accepted the message. Once DATA closes successfully, a later QUIT failure does not revoke ACCEPTED.

Jump to

Keyboard shortcuts

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