Files
Fuchs_Intranet/Fuchs/Docs/Concepts/processweb-mailer-interface.md
T
Stefan 249a945f5e
Playwright Tests / test (push) Canceled after 0s
feat: Implement admin-only specialties management interface
- 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.
2026-09-17 21:17:23 +02:00

7.3 KiB

status, lastUpdated, applyTo, relatedDecisions
status lastUpdated applyTo relatedDecisions
Active 2026-09-17
Fuchs/Services/IComService.cs
Fuchs/Services/ProcessWebComService.cs
Fuchs/Services/ProcessWebComSettings.cs
Fuchs/Services/FuchsMailerClient.cs
Fuchs/Services/MailerAttachmentPolicy.cs
Fuchs.Tests/ProcessWebComServiceTests.cs
Fuchs.Tests/FuchsMailerClientTests.cs
0028-mailer-push-com-multipart-and-attachment-confirmation.md
0029-outbound-mail-via-ocore-pwmailer-client.md

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.BaseUrl
  • AccountId → Account, ServerId → ServerId, Token → Token
  • TokenSecretName = "Fuchs--Mailer--Token" — the same Key Vault secret OCORE secret management already maps into Fuchs:Mailer:Token. In production the configuration value is therefore already the plain token; the name only matters when the MANAGED_BY_KEYVAULT placeholder is still in place (e.g. locally), where the client loads it on demand.
  • Attachments (MaxFileBytes 10 MB, MaxMessageBytes 20 MB, MaxCount 10 — the contract defaults) feed FuchsMailerClient's pre-validation.

Registration: AddPWMailerClient<FuchsMailerClient>() (singleton, named HttpClient PWMailerClient). ProcessWebComService stays scoped.

Sending an email

  1. SendEmailAsync validates the address, applies the override, appends the signature, and builds PWMailerMessage { To, Cc, Subject, Body, Reference, ComType = "e", Attachments }. AttachmentCount is left null, so the client sends the real count and the Mailer cross-checks it (400 on mismatch).
  2. FuchsMailerClient.ValidateMessage runs inside PushAsync before 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.ForbiddenExtensions is the contract's quoted subset; the server's list is longer and authoritative). A violation returns a result with StatusCode 0, Attempts 0 and the "; "-joined rule messages as Error.
  3. The client posts JSON (no attachments) or multipart (with) to POST /api/mailer/push_com, never retrying a push.
  4. 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

  • SendSmsAsync pushes PWMailerMessage { ComType = "s", To = mobile, Body } — the base validation requires no subject for comType "s".
  • VerifyCredentialsAsync → IPWMailerClientService.VerifyAsync(serverId) (GET /api/mailer/verify[?serverid=…]), mapped to MailerCredentialsCheckResult for 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 — ValidateMessage override.
  • Fuchs/Services/MailerAttachmentPolicy.cs — limit rules (pure).
  • Fuchs/Services/ProcessWebComSettings.cs — Fuchs:Mailer incl. 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 in OCORE_web/OCORE_webTests/comm/PWMailerClientServiceTests.cs.