messagelifecycle

package
v1.4.1 Latest Latest
Warning

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

Go to latest
Published: Jul 26, 2026 License: Apache-2.0 Imports: 17 Imported by: 0

Documentation

Overview

Package messagelifecycle defines the canonical vocabulary and validated in-memory representation of message lifecycle observations.

Index

Constants

This section is empty.

Variables

View Source
var ErrDedupeConflict = errors.New("message lifecycle dedupe conflict")

ErrDedupeConflict means a producer reused a message-local dedupe key for a semantically different lifecycle observation.

View Source
var ErrDirectionMismatch = errors.New("message lifecycle direction does not match message")

ErrDirectionMismatch means a producer attempted to append a lifecycle fact whose direction contradicts the owning message.

View Source
var ErrMessageNotFound = errors.New("message lifecycle message not found")

ErrMessageNotFound intentionally covers both absent and foreign messages.

Functions

func Catalog

func Catalog() map[ReasonCode]Definition

Catalog returns a copy of the canonical reason-code catalog.

func IsTerminalSubmissionFailure

func IsTerminalSubmissionFailure(code ReasonCode) bool

IsTerminalSubmissionFailure reports whether code is a catalog-owned submission/failed observation suitable for the durable message failure rollup. Arbitrary database text must never reach AppendTx through this path.

func SafeAuthenticationEvidence

func SafeAuthenticationEvidence(authentication any) (map[string]any, error)

SafeAuthenticationEvidence returns canonical authentication evidence that is safe to append even when diagnostic fields originated in untrusted SMTP/DNS input. In-bounds authentication JSON is preserved exactly. Oversized optional diagnostics are omitted, oversized nullable identifiers become null, and excess DKIM observations are dropped from the tail deterministically until the complete evidence fits the canonical aggregate limit.

The input is intentionally any rather than emailauth.Authentication so this package remains the contract-owning leaf and does not depend on an auth producer package.

func SafeCorrelationIDs

func SafeCorrelationIDs(correlationIDs map[string]string) map[string]string

SafeCorrelationIDs keeps independently valid optional correlations and drops unknown, empty, or oversized values. Producers use it for identifiers derived from remote input so an unsafe optional correlation can never reject the lifecycle fact it merely annotates.

func SafeDiagnostic

func SafeDiagnostic(value string) string

SafeDiagnostic bounds optional untrusted diagnostics to the canonical per-string limit without splitting a UTF-8 encoding. Invalid input bytes are discarded so optional evidence can never reject the lifecycle observation.

func SendSuppressionDedupeKey

func SendSuppressionDedupeKey(jobID int64, attempt int, recipient string) string

SendSuppressionDedupeKey is the stable message-local identity for a recipient suppression observed by one outbound River attempt.

Types

type AppendInput

type AppendInput struct {
	MessageID      string
	DedupeKey      string
	Direction      string
	Recipient      string
	ReasonCode     ReasonCode
	Evidence       map[string]any
	CorrelationIDs map[string]string
	OccurredAt     time.Time
}

AppendInput contains the producer-controlled fields used to construct a transition. DedupeKey is validated here and retained by the persistence operation introduced with the lifecycle store.

type Definition

type Definition struct {
	Stage     Stage
	Outcome   Outcome
	Retryable bool
}

Definition is the fixed meaning of a reason code.

func Lookup

func Lookup(reason ReasonCode) (Definition, bool)

Lookup returns the immutable definition for reason.

type EventSnapshot

type EventSnapshot struct {
	ID        string
	Type      string
	Envelope  json.RawMessage
	CreatedAt time.Time
}

type MessageLifecycleTransition

type MessageLifecycleTransition struct {
	ID                 string            `json:"id"`
	MessageID          string            `json:"message_id"`
	DedupeKey          string            `json:"-"`
	SourceTransitionID string            `json:"-"`
	Direction          string            `json:"direction" enum:"inbound,outbound"`
	Recipient          string            `json:"recipient,omitempty" nullable:"true"`
	Stage              Stage             `json:"stage" enum:"accepted,authentication,review,suppression,queued,submission,delivery,complaint"`
	Outcome            Outcome           `` /* 145-byte string literal not displayed */
	ReasonCode         ReasonCode        `` /* 842-byte string literal not displayed */
	Retryable          bool              `json:"retryable"`
	Evidence           map[string]any    `json:"evidence"`
	CorrelationIDs     map[string]string `json:"correlation_ids"`
	OccurredAt         time.Time         `json:"occurred_at"`
	Reconstructed      bool              `json:"reconstructed"`
}

MessageLifecycleTransition is one validated canonical lifecycle observation.

func AppendTx

func AppendTx(ctx context.Context, tx pgx.Tx, input AppendInput) (MessageLifecycleTransition, error)

AppendTx validates and appends one lifecycle observation in the caller's transaction. Identical retries return the original stored transition.

func MergeTransitions

func MergeTransitions(persisted, reconstructed []MessageLifecycleTransition) []MessageLifecycleTransition

MergeTransitions retains every persisted observation and suppresses only a reconstructed candidate with the same source observation identity.

func NewTransition

func NewTransition(input AppendInput) (MessageLifecycleTransition, error)

NewTransition validates input and derives the semantic tuple from the closed reason catalog. The returned unpersisted transition has an empty ID; the lifecycle store assigns its stable mlt_ identifier when appending it.

func Reconstruct

func Reconstruct(snapshot Snapshot) []MessageLifecycleTransition

Reconstruct derives only facts proven by the supplied durable snapshot.

type Outcome

type Outcome string

Outcome identifies what e2a observed at a lifecycle stage.

const (
	OutcomeAccepted      Outcome = "accepted"
	OutcomePassed        Outcome = "passed"
	OutcomeFailed        Outcome = "failed"
	OutcomeIndeterminate Outcome = "indeterminate"
	OutcomePending       Outcome = "pending"
	OutcomeApproved      Outcome = "approved"
	OutcomeRejected      Outcome = "rejected"
	OutcomeBlocked       Outcome = "blocked"
	OutcomeApplied       Outcome = "applied"
	OutcomeEnqueued      Outcome = "enqueued"
	OutcomeDeferred      Outcome = "deferred"
	OutcomeDelivered     Outcome = "delivered"
	OutcomeBounced       Outcome = "bounced"
	OutcomeReported      Outcome = "reported"
)

type ReasonCode

type ReasonCode string

ReasonCode is a stable machine-readable lifecycle observation.

const (
	ReasonAcceptanceInboundSMTP             ReasonCode = "acceptance.inbound_smtp"
	ReasonAcceptanceOutboundAPI             ReasonCode = "acceptance.outbound_api"
	ReasonAcceptanceLocalLoopback           ReasonCode = "acceptance.local_loopback"
	ReasonAuthenticationDMARCPass           ReasonCode = "authentication.dmarc_pass"
	ReasonAuthenticationDMARCFail           ReasonCode = "authentication.dmarc_fail"
	ReasonAuthenticationDMARCNone           ReasonCode = "authentication.dmarc_none"
	ReasonAuthenticationDMARCTemporaryError ReasonCode = "authentication.dmarc_temporary_error"
	ReasonAuthenticationDMARCPermanentError ReasonCode = "authentication.dmarc_permanent_error"
	ReasonReviewHoldCreated                 ReasonCode = "review.hold_created"
	ReasonReviewApproved                    ReasonCode = "review.approved"
	ReasonReviewRejected                    ReasonCode = "review.rejected"
	ReasonReviewExpiredApproved             ReasonCode = "review.expired_approved"
	ReasonReviewExpiredRejected             ReasonCode = "review.expired_rejected"
	ReasonSuppressionRecipientBlocked       ReasonCode = "suppression.recipient_blocked"
	ReasonSuppressionHardBounceApplied      ReasonCode = "suppression.hard_bounce_applied"
	ReasonSuppressionComplaintApplied       ReasonCode = "suppression.complaint_applied"
	ReasonQueueInboundProcessing            ReasonCode = "queue.inbound_processing"
	ReasonQueueOutboundSubmission           ReasonCode = "queue.outbound_submission"
	ReasonSubmissionUpstreamAccepted        ReasonCode = "submission.upstream_accepted"
	ReasonSubmissionLocalLoopbackAccepted   ReasonCode = "submission.local_loopback_accepted"
	ReasonSubmissionTemporaryFailure        ReasonCode = "submission.temporary_failure"
	ReasonSubmissionProviderRejected        ReasonCode = "submission.provider_rejected"
	ReasonSubmissionLocalRetriesExhausted   ReasonCode = "submission.local_retries_exhausted"
	ReasonSubmissionCancelled               ReasonCode = "submission.cancelled"
	ReasonDeliveryRecipientServerAccepted   ReasonCode = "delivery.recipient_server_accepted"
	ReasonDeliveryTemporaryDelay            ReasonCode = "delivery.temporary_delay"
	ReasonDeliveryPermanentBounce           ReasonCode = "delivery.permanent_bounce"
	ReasonDeliveryTransientBounce           ReasonCode = "delivery.transient_bounce"
	ReasonDeliveryUndeterminedBounce        ReasonCode = "delivery.undetermined_bounce"
	ReasonComplaintRecipientReported        ReasonCode = "complaint.recipient_reported"
)

func AuthenticationReason

func AuthenticationReason(status string) (ReasonCode, error)

AuthenticationReason maps a normalized DMARC status to its canonical reason.

func BounceReason

func BounceReason(bounceType string) ReasonCode

BounceReason maps a normalized provider bounce type to its canonical reason. Unknown values use the provider normalizer's undetermined catch-all.

type RecipientSnapshot

type RecipientSnapshot struct {
	ID        string
	Address   string
	Status    string
	Detail    string
	UpdatedAt time.Time
}

type Snapshot

type Snapshot struct {
	MessageID             string
	AgentID               string
	UserID                string
	Direction             string
	Method                string
	CreatedAt             time.Time
	Authentication        json.RawMessage
	Status                string
	ApprovalExpiresAt     *time.Time
	ReviewedAt            *time.Time
	SendJobID             *int64
	JobCreatedAt          *time.Time
	ProviderAcceptedAt    *time.Time
	ProviderMessageID     string
	EmailMessageID        string
	DeliveryStatus        string
	DeliveryFailureSource string
	Recipients            []RecipientSnapshot
	Suppressions          []SuppressionSnapshot
	Events                []EventSnapshot
}

Snapshot is the bounded durable state used to reconstruct one message.

type Stage

type Stage string

Stage identifies the boundary at which e2a made an observation.

const (
	StageAccepted       Stage = "accepted"
	StageAuthentication Stage = "authentication"
	StageReview         Stage = "review"
	StageSuppression    Stage = "suppression"
	StageQueued         Stage = "queued"
	StageSubmission     Stage = "submission"
	StageDelivery       Stage = "delivery"
	StageComplaint      Stage = "complaint"
)

type Store

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

Store reads canonical and conservatively reconstructed message lifecycle facts.

func NewStore

func NewStore(pool *pgxpool.Pool) *Store

func (*Store) ListForMessage

func (s *Store) ListForMessage(ctx context.Context, messageID, agentID string) ([]MessageLifecycleTransition, error)

ListForMessage returns the lifecycle for one message owned by agentID. Reconstruction is read-only and is never persisted by this method.

type SuppressionSnapshot

type SuppressionSnapshot struct {
	ID              string
	Address         string
	Source          string
	SourceMessageID string
	CreatedAt       time.Time
}

Jump to

Keyboard shortcuts

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