Product Requirements · F-100 · Vacancy-Application-Form
⚠ Superseded am 2026-05-29. Dieses PRD beschreibt den Pfad-C+-Stand (Email-Inbox-Bridge). Mit der Gewährung des Personio-Multiposting-Scopes (P2503-191) wurde die Architektur auf Pfad A (direkter API-POST) umgestellt. Aktueller Stand: 2026-05-29-vacancy-application-personio-api-post-prd.html (parallel zu ADR-0047→0050).

Vacancy-Application-Form — Drupal-natives Bewerbungsformular mit Personio-Inbox-Bridge

TL;DR — Karriere-Interessent:innen sollen sich ohne Medienbruch direkt aus einer SOLCOM-Stellenausschreibung bewerben können. Die Lösung ist eine Drupal-Webform unter einer Sub-Route pro Stelle, die Submissions via Email an die Personio-Inbox des SOLCOM-Tenants schickt und per Personio-Webhook deterministisch bestätigt. Architektur ist auf Pfad C+ (Email-Inbox-Import + Webhook-ACK) fixiert; der zunächst gewünschte direkte HTTP-POST gegen die Personio Recruiting API ist mit SOLCOMs Standard-Recruiting-Credentials nicht erreichbar (Multiposting-Partner-Scope-Gate, 17 Live-Probes durchgeführt). Bei Versand-Failure hält eine DLQ-Admin-Oberfläche HR-seitige Recovery vor.
superseded target-release 2026-07-08 (MVP go-live) P2503-90

Problem Statement

SOLCOM betreibt heute Stellenausschreibungen auf karriere.solcom.de — einer Personio-Hosted-Page unter solcom-gmbh.jobs.personio.de. Karriere-Interessent:innen, die über die Drupal-Site auf eine Vacancy-Detailseite kommen (/karriere/<slug>), werden beim Klick auf den Apply-CTA zur Personio-Hosted-Page redirected. Das hat drei Auswirkungen:

Aus Sicht der Bewerber:in ist der einfache Wunsch: „Ich klicke auf ‘Jetzt bewerben’, fülle ein Formular aus, lade meinen CV hoch, klicke auf Submit, und bekomme eine Bestätigung. Ich möchte nicht in ein anderes Tool wechseln und mich nicht erklären müssen, warum die Bewerbungsseite anders aussieht als die Stellenseite, von der ich komme.“

Aus Sicht von HR ist der einfache Wunsch: „Jede Bewerbung muss zuverlässig im Personio-Recruiting-Backend ankommen. Wenn etwas schiefgeht, müssen wir das sehen und manuell nachholen können, ohne dass die Bewerbung verloren geht.“

Solution

Drupal bekommt eine native Bewerbungsseite pro Stelle unter /karriere/<slug>/bewerben. Die Seite rendert einen Stellenkontext-Header und eine geteilte Webform-Entity vacancy_application, die die Felder der heutigen Personio-Hosted-Form 1:1 spiegelt. Vacancy-Kontext (Personio-Job-ID, Stellentitel, Vacancy-Node-ID) wird per Route-Parameter aufgelöst und als Hidden-Fields in die Form injiziert. Submissions werden in eine Drupal-Queue geschoben; ein QueueWorker baut daraus eine strukturierte SMTP-Mail mit den Bewerbungs-Files als Attachments und versendet sie an die Personio-Inbox des SOLCOM-Tenants. Personio importiert die Mail, legt die Application im Recruiting-Backend an und schickt ihren üblichen Webhook application.created zurück an Drupal — das ist der deterministische ACK.

1 · Drupal-Webform sammelt Bewerbung Sub-Route + Vacancy-Context-Injection

Vacancy-Detail-Seite verlinkt mit Apply-CTA auf /karriere/<slug>/bewerben. Die Sub-Route ist an die existierende pathauto-Konvention /karriere/[node:title] angelehnt. Beim Render wird die Vacancy-Node geladen und Personio-Job-ID, Stellentitel und Node-ID als Hidden-Fields in die Webform injiziert. Die Form übernimmt Layout und Feld-Inventur 1:1 vom Personio-Hosted-Apply-Pattern (CV-Upload als Pflichtfeld, Anschreiben optional, Datenschutz-Checkbox, Honeypot).

2 · Submit-Handler speichert Submission und queued den Versand private://applications/<uuid>/ + solcom_personio_application_queue

Submit-Handler speichert die Submission inklusive Files tempär in einem privaten Dateisystem-Pfad und enqueued ein Queue-Item mit Referenz auf die Submission-ID. Pipe-Through ist durch die Queue-Entscheidung explizit nicht möglich — der Worker ist zeitlich vom Submit entkoppelt, also brauchen wir persistente Files zwischen Submit und Worker-Run.

3 · QueueWorker baut SMTP-Mail und schickt an Personio-Inbox cron / drush queue:run, Tracking-Hash im Subject

Der Worker lädt die Submission und ihre Files, ruft den PersonioMailBuilder (deep module), der einen strukturierten Key-Value-Text-Body, die Files als Attachments und ein Subject mit Tracking-Hash erzeugt: Bewerbung: <Stellentitel> (Personio-Job-ID <id>) · <Bewerbername> · trace:<hash>. Der MailManager schickt die Mail an die im Drupal-State hinterlegte Personio-Inbox-Adresse. Bei Versand-Failure zählt der Worker einen Custom-Retry-Counter hoch (max 5 Versuche, Backoff 1 min/5 min/15 min/1 h/6 h).

4 · Personio importiert Mail in den Recruiting-Posteingang Personio Inbox-Import (offiziell unterstützter Pfad)

Personio jedem Tenant eine eingehende Mail-Adresse mit Auto-Parsing der eingehenden Bewerbungs-Mails (Subject + Body → Application-Felder, Attachments → CV+Dokumente). Die SOLCOM-Personio-Admin muss diese Inbox einmalig konfigurieren und sicherstellen, dass das Mail-Parsing die strukturierten Felder erkennt. Bewerber:in bekommt anschließend die übliche Personio-Bestätigungsmail.

5 · Personio-Webhook signalisiert ACK an Drupal application.created → /personio/webhook/application-created

Personio feuert nach erfolgreichem Mail-Import den Webhook application.created mit der neu erzeugten Application-ID im Payload. Drupal validiert die HMAC-Signatur gegen ein Shared-Secret, matcht via Tracking-Hash oder Bewerber-Email auf die ursprüngliche Submission und setzt den Submission-Status auf acked. Personio-Application-ID wird in Submission-Meta gespeichert.

6 · Conditional-Retention purgt nur bei ACK Files + PII raus, sonst persistent als HR-Recovery-Layer

Bei eingegangenem ACK werden Submission-PII-Felder und Files aus Drupal entfernt (DSGVO-minimal). Bleibt der ACK nach 24 h aus, läuft ein Cron-Job, der HR via Mail informiert. Bei DLQ-Erschöpfung (alle 5 Retry-Versuche fehlgeschlagen) bekommt HR ebenfalls Mail mit Submission-Details und Files-Pfad; die Submission und ihre Files bleiben dann persistent in Drupal als HR-Recovery-Layer.

Pfad A (direkter HTTP-POST gegen POST /v1/recruiting/applications) bleibt als Future-Refactor-Option offen, falls Personio nach Klärung mit dem Account-Manager den Multiposting-Partner-Scope für SOLCOM aktiviert. Der QueueWorker-Body wäre dann ein 1-PR-Refactor von SMTP auf HTTP — die Webform-Architektur, Sub-Route, File-Storage und Queue-Schale bleiben unverändert.

User Stories

User-Stories nach Persona gruppiert. Stakeholder-Outcome-Personas (Karriere-Interessent:in, HR-Stakeholder:in, Redakteur:in / PO) führen die Liste; Tech-Constraint-Personas (Site-Builder, Entwickler:in) wandern in die Implementation Decisions, weil ihre Bedürfnisse keine Stakeholder-Outcomes sondern Architektur-Constraints sind.

Karriere-Interessent:in

  1. Als Karriere-Interessent:in möchte ich von einer SOLCOM-Stellenausschreibung direkt zum Bewerbungsformular gelangen, damit der Bewerbungsprozess einfach und ohne Medienbruch abläuft.
  2. Als Karriere-Interessent:in möchte ich auf der Bewerbungsseite den Stellentitel sehen, damit ich sicher bin, dass ich mich auf die richtige Stelle bewerbe.
  3. Als Karriere-Interessent:in möchte ich dieselben Felder vorfinden wie auf karriere.solcom.de, damit ich keine erhöhte Hürde wahrnehme und meine Bewerbung schnell abschicken kann.
  4. Als Karriere-Interessent:in möchte ich meinen Lebenslauf als PDF (oder Word) hochladen können, damit ich keine Datei umkonvertieren muss.
  5. Als Karriere-Interessent:in möchte ich optional ein Anschreiben und weitere Anhänge mitschicken können, damit ich meine Bewerbung vollständig präsentieren kann.
  6. Als Karriere-Interessent:in möchte ich die Datenschutzhinweise sichtbar verlinkt bekommen und meine Zustimmung explizit bestätigen, damit ich verstehe, was mit meinen Daten passiert.
  7. Als Karriere-Interessent:in möchte ich nach Submit eine SOLCOM-Bestätigungsseite sehen, damit ich weiß, dass meine Bewerbung erfolgreich abgeschickt wurde.
  8. Als Karriere-Interessent:in möchte ich kurz darauf eine Bestätigungsmail bekommen, damit ich den Prozess als abgeschlossen wahrnehme.
  9. Als Karriere-Interessent:in möchte ich die Bewerbungsseite per Lesezeichen erreichen, mit Browser-Back zurück zur Stellenseite navigieren und den Link teilen können, damit der Bewerbungs-Flow wie eine normale Website funktioniert.

HR-Stakeholder:in

  1. Als HR-Stakeholder:in möchte ich, dass jede über die SOLCOM-Karriereseite abgeschickte Bewerbung im Personio-Recruiting-Backend ankommt, damit ich Bewerbungen wie bisher in Personio bearbeiten kann.
  2. Als HR-Stakeholder:in möchte ich die Bewerbungs-Files (CV, Anschreiben) als Attachments in Personio sehen, damit ich nicht in zwei Tools nach den Dokumenten suchen muss.
  3. Als HR-Stakeholder:in möchte ich, dass das System bei Versand-Fehlern automatisch mehrere Wiederhol-Versuche macht, damit flüchtige Störungen sich selbst auflösen.
  4. Als HR-Stakeholder:in möchte ich per Mail benachrichtigt werden, wenn eine Bewerbung trotz aller Wiederhol-Versuche nicht zugestellt werden konnte, damit ich manuell aktiv werden kann.
  5. Als HR-Stakeholder:in möchte ich in einer eigenen Recovery-Oberfläche fehlgeschlagene Bewerbungen sehen, Files herunterladen, erneut versenden oder „manuell erledigt“ markieren können, damit ich keine Entwickler-Hilfe brauche.
  6. Als HR-Stakeholder:in möchte ich, dass Bewerbungs-Daten und Files nur so lange in Drupal liegen wie nötig — nach erfolgreicher Personio-Ankunft sollen sie automatisch gelöscht werden, damit wir DSGVO-konform mit minimaler Datenhaltung arbeiten.
  7. Als HR-Stakeholder:in möchte ich benachrichtigt werden, wenn eine Bewerbung länger als 24 h ohne Personio-Bestätigung bleibt, damit ich prüfen kann, ob Personio die Mail empfangen hat oder das Import-Mapping in Personio gebrochen ist.
  8. Als HR-Stakeholder:in möchte ich die Recovery-Oberfläche über die HR-Rolle nutzen können, ohne dass mir Drupal-Admin-Vollzugriff gegeben werden muss, damit Berechtigungen sauber abgegrenzt bleiben.

Redakteur:in / PO

  1. Als Redakteur:in möchte ich keinen Mehraufwand pro neuer Stelle haben — das Bewerbungsformular soll automatisch für jede neue Vacancy verfügbar sein, damit ich neue Stellen wie bisher anlegen kann.
  2. Als PO möchte ich eine klare Tracking-URL pro Bewerbungsseite haben (/karriere/<slug>/bewerben), damit Analytics und Conversion-Tracking pro Stelle ausgewertet werden können.
  3. Als PO möchte ich, dass die Bewerbungsseite in SOLCOM-Markenwelt erscheint (Lime-Akzent, Montserrat, ScalaSans, Sticky-Apply-Bar-Kontext), damit die Marken-Konsistenz über den gesamten Karriere-Funnel erhalten bleibt.
  4. Als PO möchte ich, dass im Notfall (z. B. wenn der Personio-Inbox-Import auf SOLCOM-Seite strukturell scheitert) der Apply-CTA wieder auf die Personio-Hosted-Page redirecten kann, damit Bewerbungen weiter angenommen werden, während die Störung behoben wird.

Implementation Decisions

Module & deep-module-Extraktion

Custom-Drupal-Modul solcom_vacancy_application kapselt den gesamten Apply-Flow. Drei Deep Modules (kleines API-Surface, viel interne Logik, isoliert testbar) werden extrahiert, plus eine flache Service-Klasse und die Drupal-Plugin-/Controller-Schicht:

SchichtKlasseVerantwortlichkeit
DeepPersonioApiClientPersonio-v2-API-Wrapper für Webhook-Subscription und Smoke-Test-Read-back. Token-Cache via Drupal-State, Retry-on-401 mit Re-Auth, JSON-Parse. Einziger Swap-Point für Future-Refactor zu Pfad A (Multiposting-Scope) — ein Method-Body wechselt, Pipeline bleibt.
DeepPersonioMailBuilderErzeugt aus WebformSubmission-Entity + Files-Liste ein fertig komponiertes Email-Objekt mit strukturiertem Text-Body, Attachments und Subject mit Tracking-Hash. Tracking-Hash-Generierung ist private Methode mit deterministischer SHA-256-Ableitung aus Submission-UUID + Shared-Secret. Kein Drupal-Side-Effect.
DeepPersonioWebhookSignatureVerifierHMAC-basierte Signatur-Prüfung für eingehende Personio-Webhook-Requests. Input: Raw-Request-Body + Signatur-Header + Shared-Secret. Output: bool. Reine Funktion, keine I/O.
ShallowApplicationLifecycleServiceWrappt Submission-Storage-Operationen entlang der Lifecycle: Lookup via Tracking-Hash oder Email, ACK-Mark, PII+Files-Purge nach ACK. Wenig interne Logik, viel Drupal-API-Adapter.
PluginPersonioQueueHandlerWebform-Submit-Handler-Plugin. Speichert Files in private://applications/<submission-uuid>/ und enqueued ein Queue-Item mit Submission-ID.
PluginPersonioApplicationWorkerDrupal-QueueWorker-Plugin. Lädt Submission, ruft PersonioMailBuilder, sendet via MailManager. Verwaltet Custom-Retry-Counter (5 Versuche, Backoff-Sequence) und triggert DLQ-Notification an HR.
ControllerVacancyApplicationFormControllerSub-Route /karriere/<slug>/bewerben. Lädt Vacancy-Node, rendert Stellenkontext-Header + Webform vacancy_application mit injizierten Hidden-Fields.
ControllerPersonioWebhookControllerEndpoint /personio/webhook/application-created. Verifiziert Signatur via PersonioWebhookSignatureVerifier, ruft ApplicationLifecycleService::ack.
ControllerDlqAdminControllerAdmin-Route /admin/karriere/dlq. Listing failed Submissions + Files-Download + Re-Queue-Action + „Manuell erledigt“-Mark. Permission manage karriere dlq.
Drushsolcom:personio:webhook-subscribeOne-Time-Setup. POST /v2/webhooks/subscriptions über PersonioApiClient, persistiert Subscription-ID in Drupal-State.
Drushsolcom:personio:smoke-testProduction-Health-Check. Erzeugt markierte Test-Application gegen Dummy-Stelle 2635453, durchläuft Submit-Flow, verifiziert via Personio v2 Read-back.
Hookhook_cronACK-Timeout-Cron: prüft Submissions im Status sent_pending_ack älter 24 h, schickt HR-Mail.

Webform-Topologie

Form-Placement & Routing

File-Upload-Profil

DSGVO-Profil & Conditional-Retention

Queue-Mechanismus & Retry-Strategie

Personio-Mail-Format

Webhook-Subscription & ACK-Handling

Credentials & Storage

Spam-Protection

DLQ-Admin-UI

Testing Decisions

Test-Strategy ist Full-Suite — Deep-Module-Tests für interne Logik, Plugin-Tests für Worker- und Handler-Lifecycle, Functional-Tests für externe Boundaries, plus Drush-Smoke-Test als Production-Health-Check. Tests prüfen externes Verhalten, nicht Implementation-Details.

ModulTest-TypWas getestet wird
PersonioApiClientKernel + Guzzle-MockToken-Cache-Verhalten, Retry-on-401 mit Re-Auth, JSON-Parse, Error-Mapping. Snapshot-Tests für Request-Payload-Shape (Webhook-Subscribe, Application-Read-back).
PersonioMailBuilderKernel + SnapshotEmail-Body-Snapshot pro Field-Profile (mit/ohne Anschreiben, mit/ohne weitere Anhänge), Subject-Format mit Tracking-Hash, Attachments-Liste. Hash-Generierung deterministisch mit Fixed-Seed.
PersonioWebhookSignatureVerifierUnitValid + invalid + replayed Signaturen, Missing-Header, abgelaufener Timestamp. Drei-vier Assertions, schneller Lauf.
PersonioApplicationWorkerKernel mit MocksWorker-Lifecycle: Item picken → MailBuilder rufen → MailManager.send rufen. Retry-Counter-Increment bei MailManager-Exception. DLQ-Notification nach 5 Versuchen.
PersonioWebhookControllerFunctionalEnd-to-End mit synthetisch-signiertem Webhook-Payload: HTTP-POST → Submission-Status acked, Files purged, Personio-App-ID in Submission-Meta. Plus Negative-Cases: invalid signature → HTTP 403, kein State-Change.
DlqAdminControllerFunctionalAdmin-User mit Permission sieht Listing, Re-Queue-Action funktioniert (Item landet wieder in Queue), „Manuell erledigt“-Mark setzt Status. Ohne Permission HTTP 403.
VacancyApplicationFormControllerFunctionalSub-Route rendert mit Hidden-Fields (Job-ID, Stellentitel, Vacancy-Node-ID). Submit erzeugt Submission + Queue-Item + Files in private://. Navigation auf Danke-Seite.
solcom:personio:smoke-testProduction-Health-CheckDrush-Command erzeugt markierte Test-Application gegen Dummy-Stelle 2635453, durchläuft Submit-Flow, verifiziert via Personio v2 Read-back. Eigener Test-Lauf außerhalb der CI-Suite. Cleanup-Option im Command oder manuell im Personio-Recruiting-Backend.

Prior Art

Out of Scope

Further Notes

Spike-Evidence (17 Live-Probes, 2026-05-25)

Vor Architektur-Lock wurde der Personio-API-Surface systematisch geprobed. Zusammenfassung im Story-Hub-Abschnitt #spike-result in F-100. Kern: v2-Recruiting ist vollständig read-only (kein POST für Application-Create), POST /v1/recruiting/applications existiert (OPTIONS-Discovery bestätigt), ist aber für Standard-Recruiting-Credentials gated (HTTP 401 statt 404). Daraus: Pfad A scheidet für MVP aus, Pfad C+ (Email-Inbox-Import + Webhook-ACK) ist der einzige durchgängig erreichbare Mechanismus.

Credential-Hygiene

Während der Grilling-Session wurden Live-Personio-Credentials (Client-ID + API-Key im papi--Format) im Chat geteilt. Credential-Rotation ist Sub-Task (siehe P2503-90.7): vor Production-Deploy müssen die Credentials in Personio neu generiert und im Drupal-Key-Storage (Provider env) abgelegt werden. Die in der Grilling-Session offen liegenden Werte gelten als kompromittiert und dürfen nicht in Production verwendet werden.

Abhängigkeiten

Sub-Task-Drafts (User-Approval pro Ticket)

Geplante Decomposition in 10 Sub-Tasks. Jeder Sub-Task wird einzeln in Jira angelegt; das tatsächliche Posten ist Stakeholder-Aktion (User-Approval, kein autonomes Posten).

  1. P2503-90.1 — Webform-Entity + Sub-Route + Vacancy-Kontext-Injection (UI-only, kein Submit)
  2. P2503-90.2 — Submit-Handler + Queue + private://-Storage
  3. P2503-90.3 — QueueWorker + PersonioMailBuilder + Personio-Inbox-Versand
  4. P2503-90.4 — Webhook-Subscription + Webhook-Receiver + ACK-Handling + Conditional-Retention
  5. P2503-90.5 — DLQ-Admin-UI
  6. P2503-90.6 — solcom:personio:smoke-test Drush-Command + Kernel-Tests + Mock-Coverage
  7. P2503-90.7 — Personio-API-Credentials rotieren und im Drupal-Key-Storage einpflegen (Sicherheits-Sub-Task)
  8. P2503-90.8 — Auftragsverarbeitungs-Vertrag mit Personio prüfen (HR/Legal-Sub-Task)
  9. P2503-90.9 — Personio-Inbox-Adresse + Custom-Apply-URL pro Job im SOLCOM-Tenant konfigurieren (Personio-Admin-Sub-Task)
  10. P2503-90.10 — Personio-Account-Manager kontaktieren, Multiposting-Partner-Scope klären (parallel, Outcome bestimmt Future-Refactor-Option Pfad A)

Wiki-Promote (geplant)

Nach Implementation-Start wird docs/wiki/vacancy-personio-integration.html als aggregierter Anker promoted: Vacancy-Mirror (ADR-0035) + Apply-Flow (P2503-90) + IT-Landscape-Edge E15 + Spike-Result. Erfüllt das 3-Strand-Threshold-Kriterium der html-doc-authoring-Skill-Konvention.