Requirements: Sending Library (Phase 1)
Requirements: Sending Library (Phase 1)
Section titled “Requirements: Sending Library (Phase 1)”Functional requirements for the common-module lib/infra/email package.
Behavioral definitions live in the Design §9;
each requirement cites its design anchor rather than restating it.
Message Model
Section titled “Message Model”- REQ-LIB-001 — The library SHALL provide an
EmailMessagevalue type (from, to, cc, bcc, subject, htmlBody, textBody, replyTo, attachments, custom headers) whose construction is a smart constructor returningResult<EmailMessage>, rejecting header-bound fields that failsanitizeHeader(Design §9.4 — Header hygiene). - REQ-LIB-002 — The library SHALL provide
fromHeader(displayName, address)producing an RFC 5322-compliant From header, behavior-identical to the pre-liftoperationsimplementation (operations/src/main/kotlin/cards/arda/operations/shopaccess/email/service/EmailSendService.kt).
Server Value and Factory
Section titled “Server Value and Factory”- REQ-LIB-003 — The library SHALL provide an
EmailServervalue type (apiBaseUrl defaulting to the Postmark API base, serverToken, messageStream defaulting tooutbound) with aResult-returning smart constructor; the token SHALL be excluded fromtoString(Design §8 — EmailServer). - REQ-LIB-004 — The library SHALL provide
EmailSenderFactory.from(server, retryPolicy, engine): Result<EmailSender>; the factory SHALL carry no knowledge of any specific server identity, domain, or account — theEmailServervalue is its only server input (DQ-005 A′, Design §9.3).
Sending
Section titled “Sending”- REQ-LIB-005 —
EmailSender.send(message)SHALL classify every send into the sealedSendEmailOutcome:Sent(messageId)(2xx),Rejected(errorCode, reason)(4xx except 429, never retried),RateLimited(retryAfter)(429),TransportFailure(AppError)(5xx / transport) — (Design §9.4 — Outcome classification). - REQ-LIB-006 — Transient outcomes SHALL be retried inside the sender per
SendRetryPolicy(default: 3 attempts, exponential backoff 500 ms × 2 capped at 10 s, honoringRetry-After); permanent rejections SHALL short-circuit (Design §9.4 — Retry with backoff). - REQ-LIB-007 — The Postmark implementation SHALL serialize requests
wire-identically to the pre-lift
operationsclient (PostmarkSendEmailRequest: nulls omitted,MessageStreamset, Cc/Bcc semantics preserved) and authenticate per call viaX-Postmark-Server-Tokenfrom theEmailServervalue. - REQ-LIB-008 — The factory SHALL expose optional lifecycle hooks
(
onSendSuccess/onTransientFailure/onPermanentFailure) so consumers can attach metrics and tenant-lifecycle behavior without the library depending on them (Design §8 — PostmarkEmailSender).
Component Config Binding
Section titled “Component Config Binding”- REQ-LIB-009 —
ConfigurationProviderSHALL bind an OPTIONAL top-levelemail {}HOCON block viaEmailConfig.fromConfig(cfg): Result<EmailConfig>: absent block →nullcapability (WARN); present but malformed (including blankserverToken) → boot failure withAppError.GeneralValidation— mirroringglobalDsConfiguration(Design §9.2). - REQ-LIB-010 —
EmailConfigSHALL exposeserver: EmailServerand MAY carry an optional uninterpretedfrom { domain, localPart, displayName }convenience sub-block for consumers (Design §8 — EmailConfig binding).
Conventions (cross-cutting)
Section titled “Conventions (cross-cutting)”- REQ-LIB-011 — All fallible library operations SHALL use the monadic
error channel (
Result+ typedAppErrorleaves; nogetOrNull/throwing in prod paths), and the HTTP layer SHALL be built on the existinghttpClient(logger, engine)seam — no new HTTP stack, no new runtime dependencies.
Copyright: (c) Arda Systems 2025-2026, All rights reserved
Copyright: © Arda Systems 2025-2026, All rights reserved