Kontaktformular auf GitHub Pages

GitHub Pages stellt ausschließlich statische Dateien wie HTML, CSS und JavaScript bereit. Ein Kontaktformular kann daher nicht direkt über GitHub Pages E-Mails versenden oder Formulardaten serverseitig verarbeiten.

Für ein Kontaktformular wird deshalb ein externer Formular-Dienst benötigt. Eine einfache und für eine statische Website gut geeignete Lösung ist Formspree.

Der wesentliche Vorteil: Die private Empfängeradresse muss weder im HTML noch im JavaScript der Website stehen.

Grundprinzip

Das Formular auf der Website sendet die eingegebenen Daten nicht direkt an eine E-Mail-Adresse, sondern an einen Formspree-Endpunkt.

Website
   ↓
Kontaktformular
   ↓
Formspree
   ↓
private E-Mail-Adresse

Im öffentlich sichtbaren HTML steht dabei lediglich eine Formular-ID, beispielsweise:

https://formspree.io/f/abcdwxyz

Die dahinter hinterlegte Empfängeradresse bleibt für Besucher der Website verborgen.

Einrichtung bei Formspree

  1. Bei Formspree ein Konto erstellen.
  2. Ein neues Formular anlegen.
  3. Die gewünschte private Empfängeradresse hinterlegen und bestätigen.
  4. Den bereitgestellten Formular-Endpunkt kopieren.
  5. Die Formular-ID anschließend in der eigenen contact.html eintragen.

Der relevante HTML-Code sieht beispielsweise so aus:

<form
  action="https://formspree.io/f/DEINE_FORM_ID"
  method="POST"
>

DEINE_FORM_ID wird durch die von Formspree bereitgestellte ID ersetzt.

Die Formular-ID selbst darf öffentlich sein. Sie ist nicht mit der privaten E-Mail-Adresse gleichzusetzen.

↑ top

Beispiel für eine contact.html

Das folgende Formular kann direkt in eine statische GitHub-Pages-Seite eingebaut werden.

<section class="card" aria-labelledby="contact-form-title">

  <h2 class="card-title" id="contact-form-title">
    Send a message
  </h2>

  <form
    class="contact-form"
    action="https://formspree.io/f/DEINE_FORM_ID"
    method="POST"
  >

    <div class="form-field">
      <label for="contact-name">Name</label>

      <input
        type="text"
        id="contact-name"
        name="name"
        autocomplete="name"
        maxlength="100"
        required
      >
    </div>


    <div class="form-field">
      <label for="contact-email">Email address</label>

      <input
        type="email"
        id="contact-email"
        name="email"
        autocomplete="email"
        maxlength="254"
        required
      >

      <p class="form-help">
        Your address is used only so that I can reply to your message.
      </p>
    </div>


    <div class="form-field">
      <label for="contact-subject">Subject</label>

      <input
        type="text"
        id="contact-subject"
        name="subject"
        maxlength="150"
        required
      >
    </div>


    <div class="form-field">
      <label for="contact-message">Message</label>

      <textarea
        id="contact-message"
        name="message"
        rows="9"
        minlength="10"
        maxlength="5000"
        required
      ></textarea>
    </div>


    <div class="form-honeypot" aria-hidden="true">
      <label for="contact-website">
        Leave this field empty
      </label>

      <input
        type="text"
        id="contact-website"
        name="_gotcha"
        tabindex="-1"
        autocomplete="off"
      >
    </div>


    <p class="form-privacy">
      The information entered here is transmitted through Formspree
      for the purpose of processing and answering the message.
      Further information can be found in the
      <a href="privacy.html">privacy notice</a>.
    </p>


    <button class="form-submit" type="submit">
      Send message
    </button>

  </form>

</section>

Wichtig ist insbesondere das name-Attribut jedes Formularfeldes. Nur Felder mit einem solchen Attribut werden beim Absenden übertragen.

↑ top

CSS für das Kontaktformular

Das Formular kann mit den bereits vorhandenen CSS-Variablen und Card-Stilen der Website gestaltet werden.

/* =========================================================
   CONTACT FORM
   ========================================================= */

.contact-form {
  display: grid;
  gap: 1.25rem;
  margin-top: 1.25rem;
}

.form-field {
  display: grid;
  gap: 0.4rem;
}

.form-field label {
  font-weight: 650;
}

.form-field input,
.form-field textarea {
  width: 100%;
  padding: 0.75rem 0.85rem;

  font: inherit;
  line-height: 1.5;

  color: var(--text);
  background: var(--surface);

  border: 1px solid var(--border);
  border-radius: 10px;

  transition:
    border-color 120ms ease,
    box-shadow 120ms ease;
}

.form-field textarea {
  min-height: 180px;
  resize: vertical;
}

.form-field input:hover,
.form-field textarea:hover {
  border-color: rgba(31, 35, 40, 0.35);
}

.form-field input:focus,
.form-field textarea:focus {
  outline: none;

  border-color: var(--link);
  box-shadow: 0 0 0 3px rgba(11, 95, 255, 0.14);
}

.form-help,
.form-privacy {
  margin: 0;
  color: var(--muted);
  font-size: 0.9rem;
}

.form-privacy {
  max-width: 70ch;
}

.form-honeypot {
  position: absolute;
  left: -10000px;

  width: 1px;
  height: 1px;

  overflow: hidden;
}

.form-submit {
  justify-self: start;

  padding: 0.7rem 1.15rem;

  font: inherit;
  font-weight: 650;

  color: #ffffff;
  background: var(--text);

  border: 1px solid var(--text);
  border-radius: 10px;

  cursor: pointer;

  transition:
    transform 120ms ease,
    opacity 120ms ease;
}

.form-submit:hover {
  opacity: 0.88;
  transform: translateY(-1px);
}

.form-submit:active {
  transform: translateY(0);
}

.form-submit:focus-visible {
  outline: 3px solid rgba(11, 95, 255, 0.45);
  outline-offset: 3px;
}

↑ top

Spam-Schutz

Öffentliche Kontaktformulare werden früher oder später von Bots gefunden. Ein gewisser Spam-Schutz sollte daher von Beginn an eingeplant werden.

Honeypot

Eine einfache Maßnahme ist ein unsichtbares zusätzliches Formularfeld:

<input
  type="text"
  name="_gotcha"
  tabindex="-1"
  autocomplete="off"
>

Normale Besucher sehen dieses Feld nicht und lassen es leer. Viele einfache Bots füllen hingegen automatisch sämtliche Formularfelder aus. Formspree kann solche Einsendungen anschließend als Spam erkennen.

Das Honeypot-Feld sollte nicht einfach mit display: none entfernt werden, sondern beispielsweise außerhalb des sichtbaren Bereichs positioniert bleiben.

Weitere Maßnahmen

Cloudflare Turnstile oder ähnliche Systeme sind bei einer rein statischen Website nicht automatisch eine bessere Lösung. Die eigentliche Token-Prüfung sollte serverseitig erfolgen. Ein lediglich eingeblendetes Widget ohne sichere Validierung wäre eher Kulisse als Schutz.

↑ top

Was bedeutet „E-Mail-Adresse verborgen“?

Bei Verwendung eines Formular-Backends wie Formspree muss die private Empfängeradresse nicht öffentlich auf der Website erscheinen.

Der Formular-Dienst selbst kennt die Empfängeradresse selbstverständlich, da er die eingegangenen Nachrichten zustellen muss.

Wichtig beim Antworten

Wird auf eine Kontaktanfrage direkt aus dem privaten E-Mail-Konto geantwortet, sieht der Empfänger anschließend normalerweise die Absenderadresse.

Wer die private Adresse auch beim Antworten verborgen halten möchte, sollte daher eine eigene öffentliche Kontaktadresse oder einen Alias verwenden.

contact@deine-domain.example

Diese Adresse kann beispielsweise an das eigentliche private Postfach weiterleiten. Damit bleibt das persönliche Konto sowohl auf der Website als auch in der späteren Kommunikation im Hintergrund.

↑ top

Datenschutz

Über ein Kontaktformular werden personenbezogene Daten verarbeitet. Dazu gehören insbesondere:

Da die Formulardaten über einen externen Dienst verarbeitet werden, sollte dies in der Datenschutzerklärung der Website berücksichtigt werden.

Sinnvoll ist außerdem ein kurzer Hinweis unmittelbar beim Formular:

<p class="form-privacy">
  The information entered here is transmitted through Formspree
  for the purpose of processing and answering the message.
  Further information can be found in the
  <a href="privacy.html">privacy notice</a>.
</p>

Die Datenschutzerklärung sollte insbesondere erläutern:

Eine verpflichtende Checkbox mit „Ich stimme der Verarbeitung zu“ sollte nicht reflexartig eingebaut werden. Für die Bearbeitung einer freiwilligen Kontaktanfrage ist eine Einwilligung nicht zwingend in jedem Fall die sachlich passende Rechtsgrundlage.

↑ top

Empfohlene Umsetzung

Für eine statische GitHub-Pages-Website bietet sich folgende Architektur an:

contact.html
    │
    ├── Name
    ├── E-Mail
    ├── Betreff
    ├── Nachricht
    └── Honeypot
          │
          ▼
      Formspree
          │
          ▼
      Kontakt-Alias
          │
          ▼
      privates Postfach

Diese Lösung bleibt technisch schlank und passt gut zu einer Website ohne Frameworks, Build-System oder eigenes Backend.

Für eine kleine persönliche Projekt- und Dokumentationsseite ist ein eigenes serverseitiges Kontakt-Backend in der Regel unnötig. Ein externer Formular-Endpunkt erfüllt denselben Zweck mit deutlich weniger Wartungsaufwand.

Kurzfassung: GitHub Pages rendert das Formular, Formspree verarbeitet die Nachricht, und ein separater Kontakt-Alias schützt die eigentliche private E-Mail-Adresse.

↑ top