Zum Hauptinhalt springen

ePost-Versand (Letter Submissions)

Zweck

LetterSubmission modelliert den kompletten Versand-Lifecycle eines Briefs über die DocuGuide-ePost-Schnittstelle. Pro Brief kann es mehrere Submissions geben (z. B. wenn ein Versand fehlschlägt und neu probiert wird) — eine davon ist die aktive Submission (Letter.activeSubmissionId).

Was die Submission verfolgt:

  • Versand-Status (queued → submitted → in_progress → ready_to_send → sent / failed / cancelled)
  • Einschreiben-Variante (none / einwurf_einschreiben / einschreiben / einschreiben_rueckschein)
  • Druck-Optionen (Farbe, Duplexdruck)
  • Tracking-Events der Deutschen Post (bei Einschreiben)
  • Zustellungs-Zeitpunkt und Rückschein-Eingang (bei Einschreiben mit Rückschein)
  • Fehler-Details bei DocuGuide-Problemen

Voraussetzungen

- Berechtigung `view:LetterSubmission`, `create:LetterSubmission` und `do:SubmitLetter`. - Ein vorhandener [Brief](/letters) im Status `draft` mit Empfänger-Adresse. - Mandanten-Konfiguration mit gültigen DocuGuide-Credentials (vendorID, EKP, secret, password) — eingerichtet durch Admin. - Optional: weitere [Dokumente](/documents) als Attachment, die mitversandt werden sollen.

Datenmodell

FeldPflichtTypWirkung
letterIdjaUUID → LetterReferenz auf den zu versendenden Brief (1:N — pro Brief mehrere Versuche möglich)
statusautoENUMqueued / submitted / in_progress / ready_to_send / sent / failed / cancelled
epostLetterIDautoStringDie von DocuGuide nach Submit zurückgegebene ID — Pflicht für Status-Polling
epostStatusCodeautoIntegerDocuGuide-Statuscode: 1 Eingangsprüfung / 2 Bearbeitung / 3 Versand-bereit / 4 Versendet / 99 Fehler
isColorneinBooleanFarbdruck. Default false. Aufpreis bei true.
isDuplexneinBooleanDuplex-Druck (beidseitig). Default true.
testFlagneinBooleanTest-Versand (Default aus EPOST_TEST_MODE-env). Bei true wird nicht physisch versendet — nur API-Roundtrip getestet.
registeredLetterneinENUMEinschreiben-Variante (siehe Pricing-Tabelle unten)
attachmentDocumentIds[]neinUUID[] → DocumentZusätzliche PDFs, die hinten an den Brief gemerged werden
pdfHashautoString (SHA-256)Hash über das finale PDF — Replay-Schutz, identisches Re-Submit erkannt
errorMessageautoStringUser-freundliche Fehlermeldung
errorDetailautoJSONRohe DocuGuide-API-Antwort bei Fehler
submittedAtautoDateTimeWann an DocuGuide abgeschickt
lastPolledAtautoDateTimeLetzter Status-Poll
lastTrackingPolledAtautoDateTimeLetzter Tracking-Poll (nur bei Einschreiben)
trackingEvents[]autoJSON[]Rohe Tracking-Events der Deutschen Post
deliveredAtautoDateTimeZustellung (nur Einschreiben)
returnReceiptAtautoDateTimeRückschein-Eingang (nur Einschreiben mit Rückschein)
finalizedAtautoDateTimeWann der Status final wurde (sent / failed / cancelled)

Status-Lebenszyklus

Terminale Status: sent, failed, cancelled — sobald erreicht, wird finalizedAt gesetzt und der Brief ist nicht mehr locked.

DocuGuide-Integration

Authentifizierung

  • POST /api/Login mit vendorID, ekp (Postkundennummer), secret, password
  • JWT-Token gültig 60 Minuten — in Redis gecacht mit TTL 55 Minuten
  • Automatischer Refresh bei 401-Response

Submit-Aufruf

epost-submit-worker.ts (BullMQ, Concurrency 2, Retries 3× mit exponential backoff 5s/10s/20s) sendet:

POST /api/Letter
{
"fileName": "Brief-12345.pdf",
"data": "<base64-encoded-PDF/A-1b>",
"isColor": false,
"isDuplex": true,
"testFlag": false,
"registeredLetter": "Einschreiben Rückschein",
"custom1": "<letter-id>",
"addressLine1": "Max Mustermann",
"zipCode": "10115",
"city": "Berlin",
"country": "DE"
}

DocuGuide antwortet mit { letterID: "DOC-..." } — landet in epostLetterID.

Status-Polling

Periodischer Job ruft GET /api/Letter/:epostLetterID und mappt:

DocuGuide-CodeBedeutungLetterSubmission.status
1Eingangsprüfung läuftsubmitted
2In Bearbeitungin_progress
3Druck fertig, versandfertigready_to_send
4Physisch versendetsent
99Fehlerfailed

Tracking (nur Einschreiben)

Wenn registeredLetternone: zusätzlicher Tracking-Poll getLetterTracking()trackingEvents[], deliveredAt, returnReceiptAt.

Pricing

Die Aufschläge pro Einschreiben-Variante (zusätzlich zum DocuGuide-Grundpreis, netto):

VarianteAufschlagWann nutzen?
none (Standard-Brief)0,00 €Normale Korrespondenz, Standard-Post
einwurf_einschreiben+2,35 €Nachweis, dass Brief eingeworfen wurde — z. B. Kündigungen mit Frist
einschreiben (Standard)+2,65 €Nachweis der Übergabe — Empfänger oder Bevollmächtigter quittiert
einschreiben_rueckschein+4,85 €Wie Einschreiben + Rückschein an Absender — z. B. juristische Schriftstücke, gerichtsverwertbar

epostPricing.service.ts rechnet das im Submit-Dialog inline aus, damit der User vor dem Klick weiß, was er bezahlt.

Submit-Dialog (FE)

SpeamPostSubmitDialog.tsx — der zentrale UX-Schritt vor dem Versand:

  1. Druck-Optionen — Toggle isColor, Toggle isDuplex
  2. Einschreiben-Variante — Radio über die vier Optionen (mit Aufpreis-Anzeige)
  3. Attachments — Picker für Document-Einträge, die hinten an den Brief gemerged werden
  4. Inline-Pricing — Aufpreis-Summe, basierend auf den gewählten Optionen
  5. Submit-Button — feuert POST /letters/:id/submit, schließt den Dialog mit 202 Accepted-Toast

Anschließend wird der Brief locked und ist nicht mehr editierbar. Die SpeamPostSection-Komponente am Brief-Detail zeigt live den Submission-Status mit Spinner / Badge / Tracking-Panel.

Storno

POST /letters/:id/submissions/:submitId/cancel — funktioniert nur vor terminalem Status. Konkret:

  • queuedcancelled
  • submittedcancelled (DocuGuide kennt den Brief schon, aber er ist noch nicht im Druck)
  • in_progress / ready_to_send — DocuGuide druckt bereits, kein Storno mehr möglich
  • sent — physisch versendet, definitiv weg

Bei erfolgreichem Cancel wird finalizedAt gesetzt, der Brief verliert das Lock — eine neue Submission kann gestartet werden.

Replay-Schutz

pdfHash (SHA-256 über das fertige PDF) verhindert versehentliches Doppel-Versenden. Wenn der gleiche Brief mit identischem Inhalt zweimal submitted wird, erkennt der Service das und gibt eine Warnung statt eines weiteren Submits.

API/Schnittstellen

MethodeEndpointZweckCASL
POST/api/letters/:id/submitVersand starten (202 Accepted, async Worker)do SubmitLetter
GET/api/letters/:id/submissionsSubmission-Historieview LetterSubmission
GET/api/letters/:id/submissions/:submitIdDetail einer Submission inkl. Trackingview LetterSubmission
POST/api/letters/:id/submissions/:submitId/cancelAbbrechen (vor terminalem Status)do SubmitLetter

Verknüpfungen

Häufige Fehler und Lösungen

FehlerLösung
Submit endet sofort in failedDocuGuide-Token abgelaufen oder Credentials fehlen. errorDetail enthält die API-Antwort — bei 401 Admin informieren.
Submit-Button ist disabledBrief hat keinen Empfänger oder Adress-Snapshot ist unvollständig (parentZip leer). Brief-Detail prüfen.
Versand hängt auf submittedDocuGuide-Status-Polling wirft Fehler — Worker-Log prüfen. Manueller Status-Pull über Detail-Seite.
Tracking-Events erscheinen nichtNur bei Einschreiben-Varianten. Bei registeredLetter = none gibt es keine Sendungsverfolgung.
Cancel ist disabledSubmission ist bereits im Druck (in_progress / ready_to_send) oder versendet (sent). Nicht stornierbar.
Pricing-Aufpreis stimmt nichtWerte in epostPricing.service.ts — bei DocuGuide-Tarifänderung aktualisieren.

Versionshinweise

  • 2026-05-26 (Welle 148): Initiale Veröffentlichung. Quelle: BE-Commits 5de6fbb3 (DocuGuide-Integration), b8096e48 (Response-Shape-Fix), 34487c76 (Tracking + Mail-Scan-Integration). FE-Commits d5bdc79f (FE-Integration), aefa838f (Lock-Banner-Fix), f73f6286 (Einschreiben-Dialog). Worker epost-submit-worker.ts (Concurrency 2, Retries 3×).