- 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.
7.3 KiB
status, lastUpdated, applyTo, relatedDecisions
| status | lastUpdated | applyTo | relatedDecisions | |||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Active | 2026-09-17 |
|
|
ProcessWeb Mailer interface (outbound email/SMS)
Summary
All outbound email and SMS leaves Fuchs through IComService →
ProcessWebComService, which is a thin adapter over the OCORE_web
IPWMailerClientService (OCORE_web/comm/pwmailer/). The client owns
the HTTP contract of the ProcessWeb Mailer API; Fuchs owns what is specific
to Fuchs — the recipient-override safety net, the signature, the attachment
limits, the fds__emaillog audit row and telemetry.
The authoritative, server-side contract is the Mailer's own function catalog:
https://processweb.de/api/mailer/fc — subject to change as features are added, but kept backward compatible. The OCORE_web client is kept in sync with it (its header says so and names the server-side files); read both before changing anything here.
How it works
Layering
| Layer | File | Responsibility |
|---|---|---|
| Adapter | Fuchs/Services/ProcessWebComService.cs |
IComService: validate recipient, apply Fuchs:Email:OverrideRecipient, append signature, build a PWMailerMessage, interpret the PWMailerResult, write fds__emaillog, emit metrics |
| Fuchs client | Fuchs/Services/FuchsMailerClient.cs |
PWMailerClientService subclass; overrides ValidateMessage to add the attachment limits (MailerAttachmentPolicy) on top of the contract's base rules |
| OCORE client | OCORE_web/comm/pwmailer/PWMailerClientService*.cs |
endpoints, envelope, JSON vs multipart (unnamed envelope field + one file part per attachment), attachmentCount, Basic auth, token resolution, x-pcw-excd error headers, retries/timeouts, typed results |
Configuration
Fuchs:Mailer (ProcessWebComSettings) stays the single config section. In
Program.cs it is mapped onto PWMailerClientOptions:
BaseUrl(https://processweb.de) +/api/mailer→PWMailerClientOptions.BaseUrlAccountId→Account,ServerId→ServerId,Token→TokenTokenSecretName = "Fuchs--Mailer--Token"— the same Key Vault secret OCORE secret management already maps intoFuchs:Mailer:Token. In production the configuration value is therefore already the plain token; the name only matters when theMANAGED_BY_KEYVAULTplaceholder is still in place (e.g. locally), where the client loads it on demand.Attachments(MaxFileBytes10 MB,MaxMessageBytes20 MB,MaxCount10 — the contract defaults) feedFuchsMailerClient's pre-validation.
Registration: AddPWMailerClient<FuchsMailerClient>() (singleton, named
HttpClient PWMailerClient). ProcessWebComService stays scoped.
Sending an email
SendEmailAsyncvalidates the address, applies the override, appends the signature, and buildsPWMailerMessage { To, Cc, Subject, Body, Reference, ComType = "e", Attachments }.AttachmentCountis leftnull, so the client sends the real count and the Mailer cross-checks it (400 on mismatch).FuchsMailerClient.ValidateMessageruns insidePushAsyncbefore any bytes go out: base rules (server id, recipient, subject, body, empty file, missing extension) plus Fuchs limits (count, per-file size, total size, forbidden extensions —MailerAttachmentPolicy.ForbiddenExtensionsis the contract's quoted subset; the server's list is longer and authoritative). A violation returns a result withStatusCode 0,Attempts 0and the"; "-joined rule messages asError.- The client posts JSON (no attachments) or multipart (with) to
POST /api/mailer/push_com, never retrying a push. - The adapter interprets the
PWMailerResult<PWMailerPushReceipt>:
| Result | SendEmailAsync |
audit fds__emaillog |
fuchs.emails.failed{reason} |
|---|---|---|---|
Success and (no attachments or AttachmentsProcessed == sent) |
true |
guid = comStackID |
— (fuchs.emails.sent) |
Success but AttachmentsProcessed != sent |
false |
counts in log, guid = comStackID |
attachments-mismatch |
IsConflict (409: identical message still pending at the Mailer — it will be sent, nothing new queued) |
true |
"Duplicate of a still-pending entry (409) …" | — (fuchs.emails.sent) |
StatusCode 0, Attempts 0, Exception is ArgumentException (refused by pre-validation) |
false |
one line per rule | validation |
StatusCode 0, Attempts 0, no exception (options unusable / token not resolvable, e.g. Key Vault outage) |
false |
Mailer client not usable: … (Error log) |
client-config |
| any other failure | false |
API error {status}: {error} |
api-error |
| client threw | false |
exception detail | api-error |
reference (inv_<Id> etc.) is the audit key and part of the Mailer's
idempotency key (server + reference + content, while the first entry is still
pending). A resend of the identical mail within that window answers 409 and
is reported as success — the first entry is delivered; nothing is sent
twice. A resend after delivery started is a new entry. Callers keep reusing
inv_<Id> (e.g. req/resend); no per-attempt reference is needed.
SMS and verification
SendSmsAsyncpushesPWMailerMessage { ComType = "s", To = mobile, Body }— the base validation requires no subject forcomType "s".VerifyCredentialsAsync→IPWMailerClientService.VerifyAsync(serverId)(GET /api/mailer/verify[?serverid=…]), mapped toMailerCredentialsCheckResultfor the Admin system-status view.
Observability
fuchs.emails.sent / fuchs.emails.failed{reason} with reasons
invalid-email, disabled, validation, client-config,
attachments-mismatch, api-error; fuchs.email.send.duration{success}. The OCORE
client additionally logs every failed call at Warning and exposes
RequestSending/ResponseReceived/CallCompleted/CallFailed events (not
subscribed by Fuchs today).
Key files
Fuchs/Services/IComService.cs— interface +MailerCredentialsCheckResult.Fuchs/Services/ProcessWebComService.cs— the adapter.Fuchs/Services/FuchsMailerClient.cs—ValidateMessageoverride.Fuchs/Services/MailerAttachmentPolicy.cs— limit rules (pure).Fuchs/Services/ProcessWebComSettings.cs—Fuchs:Mailerincl.Attachments.Fuchs/Program.cs— options mapping +AddPWMailerClient<FuchsMailerClient>().OCORE_web/OCORE_web/comm/pwmailer/— the client (submodule).Fuchs.Tests/ProcessWebComServiceTests.cs,Fuchs.Tests/FuchsMailerClientTests.cs; the wire format is tested inOCORE_web/OCORE_webTests/comm/PWMailerClientServiceTests.cs.