Documentation
¶
Overview ¶
Package messagelifecycle defines the canonical vocabulary and validated in-memory representation of message lifecycle observations.
Index ¶
- Variables
- func Catalog() map[ReasonCode]Definition
- func IsTerminalSubmissionFailure(code ReasonCode) bool
- func SafeAuthenticationEvidence(authentication any) (map[string]any, error)
- func SafeCorrelationIDs(correlationIDs map[string]string) map[string]string
- func SafeDiagnostic(value string) string
- func SendSuppressionDedupeKey(jobID int64, attempt int, recipient string) string
- type AppendInput
- type Definition
- type EventSnapshot
- type MessageLifecycleTransition
- func AppendTx(ctx context.Context, tx pgx.Tx, input AppendInput) (MessageLifecycleTransition, error)
- func MergeTransitions(persisted, reconstructed []MessageLifecycleTransition) []MessageLifecycleTransition
- func NewTransition(input AppendInput) (MessageLifecycleTransition, error)
- func Reconstruct(snapshot Snapshot) []MessageLifecycleTransition
- type Outcome
- type ReasonCode
- type RecipientSnapshot
- type Snapshot
- type Stage
- type Store
- type SuppressionSnapshot
Constants ¶
This section is empty.
Variables ¶
var ErrDedupeConflict = errors.New("message lifecycle dedupe conflict")
ErrDedupeConflict means a producer reused a message-local dedupe key for a semantically different lifecycle observation.
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.
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 ¶
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 ¶
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 ¶
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.
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 ¶
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 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 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 Store ¶
type Store struct {
// contains filtered or unexported fields
}
Store reads canonical and conservatively reconstructed message lifecycle facts.
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.