// Package resenddelivery implements trusted operator resend by clone creation. package resenddelivery import ( "context" "errors" "fmt" "log/slog" "time" "galaxy/mail/internal/domain/attempt" "galaxy/mail/internal/domain/common" deliverydomain "galaxy/mail/internal/domain/delivery" "galaxy/mail/internal/logging" "galaxy/mail/internal/service/acceptgenericdelivery" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" oteltrace "go.opentelemetry.io/otel/trace" ) var ( // ErrNotFound reports that the requested original delivery does not exist. ErrNotFound = errors.New("resend delivery not found") // ErrNotAllowed reports that the original delivery is not in a terminal // state and therefore cannot be cloned for resend. ErrNotAllowed = errors.New("resend delivery not allowed") // ErrServiceUnavailable reports that clone creation could not load or // persist durable state safely. ErrServiceUnavailable = errors.New("resend delivery service unavailable") ) const tracerName = "galaxy/mail/resenddelivery" // Input stores one trusted resend request by original delivery identifier. type Input struct { // DeliveryID stores the original accepted delivery identifier to clone. DeliveryID common.DeliveryID } // Validate reports whether input contains a complete resend target. func (input Input) Validate() error { if err := input.DeliveryID.Validate(); err != nil { return fmt.Errorf("delivery id: %w", err) } return nil } // Result stores the new clone delivery identifier created by resend. type Result struct { // DeliveryID stores the identifier of the newly created clone delivery. DeliveryID common.DeliveryID } // Validate reports whether result contains a usable clone delivery identifier. func (result Result) Validate() error { if err := result.DeliveryID.Validate(); err != nil { return fmt.Errorf("delivery id: %w", err) } return nil } // CreateResendInput stores the durable write set required for one clone-only // resend operation. type CreateResendInput struct { // Delivery stores the new cloned delivery record. Delivery deliverydomain.Delivery // FirstAttempt stores the initial scheduled attempt of the clone. FirstAttempt attempt.Attempt // DeliveryPayload stores the optional cloned raw attachment payload bundle. DeliveryPayload *acceptgenericdelivery.DeliveryPayload } // Validate reports whether input contains a complete resend write set. func (input CreateResendInput) Validate() error { if err := input.Delivery.Validate(); err != nil { return fmt.Errorf("delivery: %w", err) } if input.Delivery.Source != deliverydomain.SourceOperatorResend { return fmt.Errorf("delivery source must be %q", deliverydomain.SourceOperatorResend) } if input.Delivery.Status != deliverydomain.StatusQueued { return fmt.Errorf("delivery status must be %q", deliverydomain.StatusQueued) } if input.Delivery.AttemptCount != 1 { return errors.New("delivery attempt count must equal 1") } if input.Delivery.LastAttemptStatus != "" { return errors.New("delivery last attempt status must be empty") } if input.Delivery.ProviderSummary != "" { return errors.New("delivery provider summary must be empty") } if input.Delivery.SentAt != nil || input.Delivery.SuppressedAt != nil || input.Delivery.FailedAt != nil || input.Delivery.DeadLetteredAt != nil { return errors.New("delivery terminal timestamps must be empty") } if err := input.FirstAttempt.Validate(); err != nil { return fmt.Errorf("first attempt: %w", err) } if input.FirstAttempt.DeliveryID != input.Delivery.DeliveryID { return errors.New("first attempt delivery id must match delivery id") } if input.FirstAttempt.AttemptNo != 1 { return errors.New("first attempt number must equal 1") } if input.FirstAttempt.Status != attempt.StatusScheduled { return fmt.Errorf("first attempt status must be %q", attempt.StatusScheduled) } if input.DeliveryPayload != nil { if err := input.DeliveryPayload.Validate(); err != nil { return fmt.Errorf("delivery payload: %w", err) } if input.DeliveryPayload.DeliveryID != input.Delivery.DeliveryID { return errors.New("delivery payload delivery id must match delivery id") } } return nil } // Store provides the durable delivery state required by clone-only resend. type Store interface { // GetDelivery loads one accepted delivery by its identifier. GetDelivery(context.Context, common.DeliveryID) (deliverydomain.Delivery, bool, error) // GetDeliveryPayload loads the raw attachment payload bundle of deliveryID // when one exists. GetDeliveryPayload(context.Context, common.DeliveryID) (acceptgenericdelivery.DeliveryPayload, bool, error) // CreateResend atomically creates the cloned delivery, its first attempt, // the optional cloned delivery payload, and the related delivery indexes. CreateResend(context.Context, CreateResendInput) error } // DeliveryIDGenerator describes the source of new internal delivery // identifiers. type DeliveryIDGenerator interface { // NewDeliveryID returns one new internal delivery identifier. NewDeliveryID() (common.DeliveryID, error) } // Clock provides the current wall-clock time. type Clock interface { // Now returns the current time. Now() time.Time } // Telemetry records low-cardinality resend metrics. type Telemetry interface { // RecordDeliveryStatusTransition records one durable delivery status // transition. RecordDeliveryStatusTransition(context.Context, string, string) } // Config stores the dependencies used by Service. type Config struct { // Store owns durable resend state. Store Store // DeliveryIDGenerator builds internal clone identifiers. DeliveryIDGenerator DeliveryIDGenerator // Clock provides wall-clock timestamps. Clock Clock // Telemetry records low-cardinality resend metrics. Telemetry Telemetry // TracerProvider constructs the application span recorder used by resend. TracerProvider oteltrace.TracerProvider // Logger writes structured resend logs. Logger *slog.Logger } // Service executes clone-only trusted resend requests. type Service struct { store Store deliveryIDGenerator DeliveryIDGenerator clock Clock telemetry Telemetry tracerProvider oteltrace.TracerProvider logger *slog.Logger } // New constructs Service from cfg. func New(cfg Config) (*Service, error) { switch { case cfg.Store == nil: return nil, errors.New("new resend delivery service: nil store") case cfg.DeliveryIDGenerator == nil: return nil, errors.New("new resend delivery service: nil delivery id generator") case cfg.Clock == nil: return nil, errors.New("new resend delivery service: nil clock") default: tracerProvider := cfg.TracerProvider if tracerProvider == nil { tracerProvider = otel.GetTracerProvider() } logger := cfg.Logger if logger == nil { logger = slog.Default() } return &Service{ store: cfg.Store, deliveryIDGenerator: cfg.DeliveryIDGenerator, clock: cfg.Clock, telemetry: cfg.Telemetry, tracerProvider: tracerProvider, logger: logger.With("component", "resend_delivery"), }, nil } } // Execute clones one terminal delivery into a new queued delivery with a // fresh first attempt. func (service *Service) Execute(ctx context.Context, input Input) (Result, error) { if ctx == nil { return Result{}, errors.New("execute resend delivery: nil context") } if service == nil { return Result{}, errors.New("execute resend delivery: nil service") } if err := input.Validate(); err != nil { return Result{}, fmt.Errorf("execute resend delivery: %w", err) } ctx, span := service.tracerProvider.Tracer(tracerName).Start( ctx, "mail.resend_delivery", oteltrace.WithAttributes(attribute.String("mail.parent_delivery_id", input.DeliveryID.String())), ) defer span.End() original, found, err := service.store.GetDelivery(ctx, input.DeliveryID) switch { case err != nil: return Result{}, fmt.Errorf("%w: load original delivery: %v", ErrServiceUnavailable, err) case !found: return Result{}, ErrNotFound case !original.Status.AllowsResend(): return Result{}, ErrNotAllowed } now := service.clock.Now().UTC().Truncate(time.Millisecond) cloneID, err := service.deliveryIDGenerator.NewDeliveryID() if err != nil { return Result{}, fmt.Errorf("%w: generate delivery id: %v", ErrServiceUnavailable, err) } clone := buildClonedDelivery(original, cloneID, now) firstAttempt := attempt.Attempt{ DeliveryID: cloneID, AttemptNo: 1, ScheduledFor: now, Status: attempt.StatusScheduled, } var clonedPayload *acceptgenericdelivery.DeliveryPayload if len(original.Attachments) > 0 { payload, found, err := service.store.GetDeliveryPayload(ctx, original.DeliveryID) switch { case err != nil: return Result{}, fmt.Errorf("%w: load original delivery payload: %v", ErrServiceUnavailable, err) case !found: return Result{}, fmt.Errorf("%w: missing original delivery payload for %q", ErrServiceUnavailable, original.DeliveryID) default: cloned := cloneDeliveryPayload(payload, cloneID) clonedPayload = &cloned } } createInput := CreateResendInput{ Delivery: clone, FirstAttempt: firstAttempt, DeliveryPayload: clonedPayload, } if err := createInput.Validate(); err != nil { return Result{}, fmt.Errorf("%w: build resend input: %v", ErrServiceUnavailable, err) } if err := service.store.CreateResend(ctx, createInput); err != nil { return Result{}, fmt.Errorf("%w: create resend clone: %v", ErrServiceUnavailable, err) } service.recordStatusTransition(ctx, createInput.Delivery) result := Result{DeliveryID: cloneID} if err := result.Validate(); err != nil { return Result{}, fmt.Errorf("%w: invalid result: %v", ErrServiceUnavailable, err) } span.SetAttributes( attribute.String("mail.delivery_id", cloneID.String()), attribute.String("mail.source", string(createInput.Delivery.Source)), ) logArgs := logging.DeliveryAttrs(createInput.Delivery) logArgs = append(logArgs, "parent_delivery_id", original.DeliveryID.String(), "status", string(createInput.Delivery.Status), ) logArgs = append(logArgs, logging.TraceAttrsFromContext(ctx)...) service.logger.Info("resend clone created", logArgs...) return result, nil } func (service *Service) recordStatusTransition(ctx context.Context, record deliverydomain.Delivery) { if service == nil || service.telemetry == nil { return } service.telemetry.RecordDeliveryStatusTransition(ctx, string(record.Status), string(record.Source)) } func buildClonedDelivery(original deliverydomain.Delivery, cloneID common.DeliveryID, now time.Time) deliverydomain.Delivery { return deliverydomain.Delivery{ DeliveryID: cloneID, ResendParentDeliveryID: original.DeliveryID, Source: deliverydomain.SourceOperatorResend, PayloadMode: original.PayloadMode, TemplateID: original.TemplateID, Envelope: deliverydomain.Envelope{ To: append([]common.Email(nil), original.Envelope.To...), Cc: append([]common.Email(nil), original.Envelope.Cc...), Bcc: append([]common.Email(nil), original.Envelope.Bcc...), ReplyTo: append([]common.Email(nil), original.Envelope.ReplyTo...), }, Content: original.Content, Attachments: append([]common.AttachmentMetadata(nil), original.Attachments...), Locale: original.Locale, LocaleFallbackUsed: original.LocaleFallbackUsed, TemplateVariables: cloneJSONObject(original.TemplateVariables), IdempotencyKey: common.IdempotencyKey("operator:resend:" + original.DeliveryID.String()), Status: deliverydomain.StatusQueued, AttemptCount: 1, CreatedAt: now, UpdatedAt: now, } } func cloneDeliveryPayload(payload acceptgenericdelivery.DeliveryPayload, cloneID common.DeliveryID) acceptgenericdelivery.DeliveryPayload { cloned := acceptgenericdelivery.DeliveryPayload{ DeliveryID: cloneID, Attachments: make([]acceptgenericdelivery.AttachmentPayload, len(payload.Attachments)), } copy(cloned.Attachments, payload.Attachments) return cloned } func cloneJSONObject(value map[string]any) map[string]any { if value == nil { return nil } cloned := make(map[string]any, len(value)) for key, entry := range value { cloned[key] = entry } return cloned }