- Added MailerAttachmentPolicy for validating email attachments against server rules. - Introduced fis.specialties.js for managing hidden maintenance actions via an admin interface. - Created fis.specialties.scss for styling the specialties management UI. - Added German localization for specialties management in fis.specialties_txt_de.js. - Developed CSS for specialties management in fis.specialties.css. - Implemented server-side stored procedure fds__admin_backupAndClearInvoiceFile for backing up and clearing invoice files. - Created fds__invoice_file_backups table to store backup records of invoice files.
5.6 KiB
status, date, applyTo, supersededBy
| status | date | applyTo | supersededBy | |||||
|---|---|---|---|---|---|---|---|---|
| Accepted | 2026-09-17 |
|
0029 — Outbound mail goes through the OCORE_web PWMailer client
Context
Right after 0028
re-implemented the Mailer API's push_com contract by hand in
ProcessWebComService, OCORE_web gained a complete typed client for that
API (OCORE_web/comm/pwmailer/, IPWMailerClientService): every operation,
the JSON/multipart request shapes, attachmentCount, typed receipts,
Basic auth with Key Vault/encrypted token resolution, x-pcw-excd error
headers, retries and timeouts — declared to be kept in sync with the
server-side controllers. Keeping a second implementation of the same
contract in Fuchs would mean two things to update on every API change.
The client validated a message's required fields and empty/extension-less
attachments, but had no hook for application rules (size limits, a
file-type deny-list) — PWMailerMessage is sealed and PushAsync called
Validate directly.
Decision
ProcessWebComServiceis an adapter overIPWMailerClientService. It keeps only what is Fuchs-specific: recipient validation and theFuchs:Email:OverrideRecipientsafety net, the signature, building thePWMailerMessage, interpreting thePWMailerResult, thefds__emaillogaudit row and telemetry. All HTTP transport lives in the OCORE client; the hand-rolledPostPushComAsync/MIME guessing from 0028 is deleted.- Pre-validation is an overridable hook in the client. OCORE_web's
PWMailerClientServicegainsprotected virtual ValidateMessage(PWMailerMessage)(default:PWMailerMessage.Validate), called byPushAsyncandPushBulkAsyncbefore anything is sent. Fuchs overrides it inFuchsMailerClientto add the contract's attachment limits (MailerAttachmentPolicy: count, per-file/per-message size, forbidden extensions, configurable underFuchs:Mailer:Attachments). A refusal is aPWMailerResultwithStatusCode 0/Attempts 0, which the adapter reports as reasonvalidationwith one audit line per rule. - One configuration section.
Fuchs:Mailer(ProcessWebComSettings) remains the source;Program.csmaps it ontoPWMailerClientOptions(BaseUrl + /api/mailer,AccountId → Account,Token,ServerId) and setsTokenSecretName = "Fuchs--Mailer--Token", i.e. the Key Vault secret OCORE secret management already maps intoFuchs:Mailer:Token. No secondPWMailersection, no second secret. - 0028's behavioural rules stand, with one correction: multipart with
attachmentCount, a 200 counts as success only whenattachmentsProcessedequals the number sent,comStackIDis the audit key. 409 is now a success, not aduplicatefailure (this replaces 0028's rule 6): the client documents 409 as "duplicate of a still-pending entry with the same server/reference/content — nothing was enqueued, the earlier entry will be sent". The mail is therefore on its way; the resend paths (req/resend, reminders) reuseinv_<Id>as the reference, and a double click must not surface as a send error. The deduplication is recorded in the audit row. They are enforced partly by the client (shape, count) and partly by the adapter (processed check, audit, 409 mapping). - Two small OCORE_web fixes made in the same change:
PWMailerMessage.Validateno longer demands a subject forcomType "s"(SMS), and a call refused by validation reportsAttempts = 0as its documentation already stated.
Consequences
- Fuchs no longer asserts the wire format in its own tests; that lives in
OCORE_web/OCORE_webTests/comm/PWMailerClientServiceTests.cs. Fuchs tests assert thePWMailerMessagehanded to the client and the interpretation of eachPWMailerResultshape, plusFuchsMailerClient's refusals. - OCORE_web is a submodule owned by another repository: the hook, the SMS
rule and the
Attemptsfix are a commit there and a pointer bump here. - The client is a singleton with a cached token; after a token rotation
IPWMailerClientService.ResetToken()(on the concrete type) is the way to pick it up without a restart — not wired to any admin action yet. - Empty attachments are still refused (now by the client's base rule), so
an upstream render failure never produces a mail without its PDF. The
flip side: a caller attaching an optional file must skip an empty byte
array itself — the reminder's invoice copy (
IntranetController.Reminder.cs) does, otherwise a never-stored invoice file would fail the whole reminder instead of sending it without the copy. - The client's
getCom/getComStatus/abortComand inbox operations are available to Fuchs for free (e.g. a delivery-status column keyed by the auditedcomStackID) — not used yet.
Alternatives considered
- Keep the 0028 hand-rolled transport. Rejected: duplicate contract implementation, and the OCORE client is the one declared to track the server.
- Pre-validate in the adapter only (before
PushAsync). Rejected: the rules belong with the client's own validation so every OCORE consumer can apply them the same way, and the user asked for an overridable hook. - Bind a separate
PWMailerconfig section. Rejected:Fuchs:Maileris already Key Vault-managed and read by the Admin status view; mapping in code avoids a second token secret and drift between two sections.