Zum Inhalt springen

Formular einbinden

So bindet man ein Formular einer statischen Seite an das Form Gateway an — ohne eigenes Backend, ohne Build-Schritt, mit einer einzigen <script>-Zeile.

Das Snippet (client/form-gateway.js) ist dependency-freies Vanilla-JS (~1,6 KB gzip, minifiziert ausgeliefert) und übernimmt den kompletten sicheren Ablauf: Challenge holen → Proof-of-Work im Web Worker lösen → Formulardaten absenden → Erfolg/Fehler inline anzeigen. Die Seite braucht selbst kein Geheimnis — der Site-Key ist eine öffentliche Kennung (siehe Konzept §2).

Zum Ausprobieren: examples/astro-demo ist eine lauffähige statische Astro-Seite, die den Einbau und alle Feld-Möglichkeiten mit Live-Formularen zeigt.


Im Admin-UI des Gateways (Menü Mandanten → Neuer Mandant):

  • Empfänger-Adresse — wohin die Formularmails gehen (kommt nie aus dem Formular).
  • Erlaubte Origins — die Domains, von denen Formulare kommen dürfen, eine pro Zeile, mit Schema und ohne Pfad, z. B. https://www.example.de. Für lokale Tests auch http://127.0.0.1:8099 o. ä. eintragen.
  • optional PoW-Bits, Spam-Schwelle, Tageskontingent.

Nach dem Speichern zeigt die Liste den Site-Key (site_…). Den braucht das Formular. Außerdem muss mindestens eine SMTP-Route existieren (Menü SMTP-Routen) — üblicherweise die geteilte Default-Route über euren Mailserver.


<form data-gateway-site="site_DEIN_SITE_KEY"
data-gateway-url="https://forms.example.de">
<input name="name" required>
<input name="_gw_reply" type="email" placeholder="Ihre E-Mail">
<textarea name="nachricht" required></textarea>
<button type="submit">Senden</button>
</form>
<script src="https://forms.example.de/form-gateway.js" defer></script>

Das war alles. data-gateway-url ist die Basis-URL eurer Gateway-Installation; das Snippet wird von dort geladen und spricht dieselbe Adresse an. Jedes <form> mit data-gateway-site auf der Seite wird automatisch verdrahtet — auch mehrere.

Ein vollständiges Beispiel liegt unter client/example.html.


AttributPflichtBedeutung
data-gateway-sitejaDer Site-Key des Mandanten (site_…)
data-gateway-urljaBasis-URL der Gateway-Installation (ohne Pfad)
data-gateway-formneinFormular-Kennung, landet als Betreff-Zusatz und im Admin (z. B. kontakt, bewerbung)
data-gateway-sendingneinText während des Sendens (Default: „Wird gesendet …”)
data-gateway-successneinErfolgsmeldung (Default: „Vielen Dank! Ihre Nachricht wurde gesendet.”)
data-gateway-errorneinFehlermeldung (Default: „Senden fehlgeschlagen …“)

Es gibt kein Schema im Gateway: jedes Feld mit name wird übertragen und landet im Mail-Text — ein neues Feld braucht nur HTML.

Feldnamen dürfen nur a-z, A-Z, 0-9, _ und - enthalten (max. 64 Zeichen). Ein ungültiger Name (Umlaut, Leerzeichen) lässt die komplette Absendung scheitern, er wird nicht übersprungen — Umlaute gehören ins <label>, nicht in den Feldnamen.

Mehrfachwerte: Felder mit gleichem name (Checkbox-Gruppen, <select multiple>) werden mit , zusammengefasst → web, hosting. Eine nicht angehakte Checkbox fehlt komplett (normales HTML-Verhalten). <input type="file"> wird ignoriert — das Gateway versendet keine Anhänge.

Im Mail-Text stehen die Felder alphabetisch nach Feldnamen sortiert, nicht in Formular-Reihenfolge. Wer eine feste Reihenfolge braucht, stellt eine Zahl voran (01_name, 02_firma).

Alle name-Felder außer den reservierten landen im Mail-Text. Reserviert:

FeldnameZweck
_gw_replyAntwortadresse des Einsenders → wird Reply-To der Mail (validiert). Optional, aber empfohlen.
_gw_formAlternative zu data-gateway-form, falls die Kennung aus dem Formular kommen soll.
_gw_hpHoneypot — fügt das Snippet selbst unsichtbar ein, nicht von Hand anlegen.

Grenzen der Eingabe-Härtung: max. 30 Felder, 5.000 Zeichen pro Feld, 20.000 Zeichen gesamt; Zeilenumbrüche in Kopf-Feldern und ungültige Reply-To-Adressen werden abgewiesen.


Das Snippet hängt unter das Formular ein <p class="gw-status"> und setzt darauf ein data-state-Attribut (sending | success | error). Damit lässt sich der Status frei stylen:

.gw-status { margin-top: 1rem; font-weight: 600; }
.gw-status[data-state="success"] { color: #087443; }
.gw-status[data-state="error"] { color: #b3261e; }
.gw-status[data-state="sending"] { color: #555; }

Die Meldungstexte selbst kommen aus den data-gateway-*-Attributen (§3) — so bleibt die Sprache in eurer Hand.


Ohne JavaScript bleibt das <form> ein normales Formular. Wer einen Fallback ohne JS will, setzt am <form> z. B. ein action="mailto:kontakt@example.de" — läuft JS, fängt das Snippet das Submit ab (preventDefault) und nutzt das Gateway; ohne JS greift der mailto-Fallback des Browsers.

Die Challenge wird beim ersten Fokus ins Formular vorab geholt (das startet zugleich die Mindest-Ausfüllzeit-Uhr) — der Absende-Klick fühlt sich dadurch schneller an.


Standard ist, das Snippet direkt vom Gateway zu laden (<script src="https://forms.example.de/form-gateway.js">) — so bekommen alle Seiten automatisch dieselbe, aktuelle Version. Wer es lieber selbst ausliefert (eigenes CDN, striktere CSP), lädt einmalig die Datei von …/form-gateway.js (minifiziert) bzw. …/form-gateway.src.js (lesbar) und legt sie neben die eigenen Assets. Funktional identisch — aber dann liegt es an einem selbst, Snippet-Updates nachzuziehen.

Caching: Das Gateway liefert das Snippet mit Cache-Control: public, max-age=300, must-revalidate und einem ETag aus. Kundenseiten haben einen Fix also nach spätestens fünf Minuten; die Revalidierung kostet nur ein 304 ohne Body. Bewusst kurz gewählt: das Snippet ist sicherheitsrelevant (PoW, Feld-Einsammlung) und hat keine Version in der URL — ein langes max-age wäre eine Falle.

CSP-Hinweis: Das Snippet startet den Proof-of-Work in einem Web Worker aus einem blob:-URL. Bei strenger Content-Security-Policy müssen script-src (bzw. worker-src) blob: erlauben, und connect-src muss die Gateway-URL zulassen. Ohne Worker fällt das Snippet automatisch auf Lösen im Hauptthread zurück.


SymptomUrsache / Lösung
Netzwerk-Fehler beim Challenge-Abruf, Formular meldet sofort FehlerOrigin nicht in der Allowlist des Mandanten. Exakt mit Schema und ohne Pfad eintragen (https://www.example.de). Der Browser-Origin-Header muss passen.
„Senden fehlgeschlagen” trotz korrektem OriginMandant deaktiviert (Kill-Switch), Tageskontingent erschöpft oder Rate-Limit — der genaue Grund steht im Admin/Log (die Public API antwortet bewusst wortkarg).
Absenden dauert spürbar langePoW-Bits zu hoch. Der Browser hasht via WebCrypto einzeln; 12 Bits ≈ 1 s, 18 Bits ≈ 40 s. Im Mandanten die PoW-Bits senken (Empfehlung 8–14).
Nachricht kommt nicht an, im Admin steht „failed: keine smtp-route”Es gibt keine (Default-)SMTP-Route. Im Admin unter SMTP-Routen anlegen und mit dem Test-Versand-Button prüfen.
Mail landet in QuarantäneSpam-Score über der Schwelle. Im Mandanten-Detail freigeben oder verwerfen; ggf. Schwelle anpassen.

Für tiefere Analyse …/form-gateway.src.js laden (unminifiziert) und die Browser-Devtools (Netzwerk-Tab: challenge, submit) nutzen.


  • Es speichert kein Geheimnis — der Site-Key ist öffentlich; die Sicherheit liegt in der serverseitigen Prüf-Pipeline (architecture.md, „Der Submit-Pfad”).
  • Es setzt keine Cookies und trackt nichts.
  • Es lädt keine Drittressourcen — alles läuft gegen eure Gateway-Installation.