Newsletter-Anmeldeformular

Newsletter-Anmeldeformular6 Min. Lesezeit

E-Mail-Anmeldeformular auf einer statischen Site, ohne Server

Füge einer statischen Site mit Hugo, Jekyll, Eleventy oder Astro ein Newsletter-Formular hinzu, halte den API-Key des E-Mail-Tools aus der Seite heraus und bestätige im Tool.

Preet SutharGründer, Ikiform

Auf einer statischen Site sendet das Formular aus dem Browser des Lesers direkt an Ikiform, weil es keinen eigenen Server gibt, an den es senden könnte. Halte den API-Key deines E-Mail-Tools komplett aus der Site heraus: Der Browser kennt nur deinen öffentlichen Ikiform-Endpunkt, und ein woanders gehostetes Relay fügt den Abonnenten hinzu. Schreib das Formular einmal als Partial oder Include, übergib pro Platzierung einen anderen source und füge ein kleines Skript für die Version ohne Neuladen hinzu.

Was hier anders ist

Eine statische Site sind Dateien auf einem Host: GitHub Pages, Netlify, Cloudflare Pages, ein S3-Bucket. Nichts, was du schreibst, läuft auf einem Server, wenn ein Leser ein Formular absendet. Das schließt die übliche Abkürzung aus, die API deines E-Mail-Tools aus der Seite aufzurufen, denn alles, was der Browser ausführt, kann jeder lesen.

API-Keys von E-Mail-Tools sind selten darauf beschränkt, einen Abonnenten hinzuzufügen. Ein Key, der einen Abonnenten hinzufügen kann, kann meist auch die ganze Liste lesen, exportieren oder löschen. Steckt er im JavaScript einer statischen Site, wird er an jeden Besucher ausgeliefert und von dort an jeden, der die Entwicklertools öffnet.

Wo der API-Key des E-Mail-Tools auf einer statischen Site liegtKey in der SeiteKey in einem RelaystatischeSeiteAPI-Key inihremJavaScriptAPI desE-Mail-Toolsjeder kannsie aufrufendeineListefür jedenlesbarstatischeSeitenuröffentlicherEndpunktIkiformSpamprüfung,signierterWebhookRelayhält denAPI-KeyE-Mail-Toolfügt denAbonnentenhinzuKeyPostsigniertKeyWo der API-Key des E-Mail-Tools auf einer statischen Site liegtKey in der SeiteKey in einem Relaystatische SeiteAPI-Key in ihrem JavaScriptAPI des E-Mail-Toolsjeder kann sie aufrufendeine Listefür jeden lesbarstatische Seitenur öffentlicher EndpunktIkiformSpamprüfung, signierter WebhookRelayhält den API-KeyE-Mail-Toolfügt den Abonnenten hinzuKeyPostsigniertKey
  1. Mit dem Key in der Seite lädt jeder Besucher ihn herunter, und jeder kann damit die API des E-Mail-Tools aufrufen.
  2. Mit einem Relay kennt die Seite nur deinen Ikiform-Endpunkt, der Formular-Posts annimmt und sonst nichts.
  3. Ikiform filtert Spam und sendet einen signierten Webhook an das Relay.
  4. Das Relay, außerhalb der statischen Site gehostet, hält den API-Key und fügt den Abonnenten hinzu.
Den Formular-Endpunkt kannst du bedenkenlos veröffentlichen. Den API-Key nie.

Das Formular einmal schreiben

Ein Newsletter-Formular taucht in mehreren Templates auf: im Footer, am Ende jedes Beitrags, auf einer Newsletter-Seite. Mach daraus ein Partial, das einen source-Wert nimmt, damit jede Platzierung sich selbst festhält. Das Markup ist in jedem Generator identisch; nur die Template-Syntax für source ändert sich.

<form data-newsletter action="https://ikiform.com/f/frm_xxxxxxxxxxxxxxxx" method="post">
  <input name="email" type="email" required autocomplete="email" placeholder="you@example.com" aria-label="Email" />
  <label><input name="consent" type="checkbox" value="true" required /> Email me the newsletter. I can unsubscribe at any time.</label>
  <input type="hidden" name="source" value="{{ .source | default "footer" }}" />
  <input type="hidden" name="_redirect" value="https://example.com/subscribed/" />
  <input name="_gotcha" type="text" tabindex="-1" autocomplete="off" hidden />
  <button>Subscribe</button>
  <p role="status" hidden></p>
</form>

Setze es dann mit dem Namen der Platzierung in ein Template ein:

{{ partial "newsletter.html" (dict "source" "post") }}
  • In Astro nutzt du <Newsletter source="post" />. Ein statischer Astro-Build kann keine Actions ausführen, also sendet die Komponente wie die anderen direkt an Ikiform.
  • Füge source in Ikiform als Text mit kurzer Maximallänge zur Felddefinition des Formulars hinzu. Sind Felder definiert, wird ein Input, den die Definition nicht listet, mit unknown_field abgelehnt.
  • _redirect muss eine https:-URL auf einer der Allowed domains des Formulars sein. Leg die Seite an: /subscribed/ mit einer Zeile, die Lesern sagt, dass sie in ihren Posteingang schauen sollen.

Ein Skript für jedes Formular auf der Seite

Eine Beitragsseite kann zwei Newsletter-Formulare haben, eins nach dem Artikel und eins im Footer. Dieses Skript übernimmt jedes Formular mit data-newsletter, und wenn eins klappt, ersetzt es alle, damit niemand ein paar Zentimeter tiefer erneut zum Abonnieren aufgefordert wird. Lade es einmal aus deinem Basis-Layout.

static/newsletter.js
for (const form of document.querySelectorAll("form[data-newsletter]")) {
  const status = form.querySelector('[role="status"]');
  const button = form.querySelector("button");

  form.addEventListener("submit", async (event) => {
    event.preventDefault();
    button.disabled = true;
    status.hidden = false;
    status.textContent = "Subscribing";
    try {
      const response = await fetch(form.action, {
        method: "POST",
        headers: { Accept: "application/json" },
        body: new FormData(form),
      });
      const result = await response.json();
      if (result.ok) {
        for (const other of document.querySelectorAll("form[data-newsletter]")) {
          other.replaceChildren(Object.assign(document.createElement("p"), {
            textContent: "Almost done. Check your inbox for a confirmation link.",
          }));
        }
        return;
      }
      if (response.status === 429 && response.headers.has("Retry-After")) {
        status.textContent = "Too many tries from this network. Wait a minute and try again.";
      } else {
        const errors = result.error.fields ?? {};
        status.textContent = errors.email?.[0] ?? errors.consent?.[0] ?? result.error.message;
      }
    } catch {
      form.submit();
      return;
    }
    button.disabled = false;
  });
}
  • Scheitert fetch, etwa hinter einem strengen Netzwerkfilter, fällt das Skript auf form.submit() zurück, das auf dem einfachen Weg sendet und auf /subscribed/ landet.
  • Ein 429 bekommt eine eigene Meldung. Leser in einem Büro- oder Campus-Netz teilen sich eine IP und können das Limit pro Besucher gemeinsam erreichen.
  • Das Formular funktioniert mit ausgeschaltetem JavaScript. Das Skript ändert nur, wie die Antwort angezeigt wird.

Leser mit ausgeschaltetem JavaScript landen auf /subscribed/, also lass diese Seite das tun, was das Skript für alle anderen tut: Sag, dass eine Bestätigungs-E-Mail unterwegs ist, nenne den Absender, nach dem sie suchen sollen, erwähne den Spam-Ordner und verlinke zurück zum Blog. Es ist eine gewöhnliche Seite in deinem Generator, gebaut wie jede andere, und auch der Ort, auf den du danach den Bestätigungslink des E-Mail-Tools richten kannst.

Wo das Relay lebt

Das Relay braucht eine öffentliche HTTPS-URL, die Code ausführt. Dein statischer Host bietet vielleicht eine an, vielleicht nicht:

Statischer HostOptionen für das Relay
GitHub Pages, S3Keine Functions. Nutze einen Cloudflare Worker auf einer eigenen Domain oder ein Automatisierungstool
NetlifyEine Netlify Function auf der Domain deiner Site oder eine der Optionen oben
Cloudflare PagesEin separater Worker auf einer eigenen Domain. Ikiform lehnt pages.dev- und workers.dev-URLs ab
VercelEine Funktion auf deiner Domain. Statische Ausgabe kann neben einer API-Route liegen

Der Relay-Code für jedes E-Mail-Tool steht auf den Seiten zu Kit, MailerLite und beehiiv. Ganz ohne Code richtest du den Webhook auf Zapier oder n8n aus und nutzt deren Aktion für dein E-Mail-Tool. Die Webhook-Doku listet die URL-Regeln.

Statische Sites haben oft Deploy-Previews auf einer separaten Domain. Posts von einer Domain, die nicht in Allowed domains steht, werden als Spam markiert oder abgelehnt, also füge die Preview-Domain während des Testens hinzu oder teste auf der Live-Domain und localhost.

Wenn deine Site eine Content Security Policy sendet

Viele statische Hosts lassen dich Header aus einer Datei setzen, etwa _headers auf Netlify und Cloudflare Pages. Sendet deiner eine Content-Security-Policy, blockiert der Browser das Formular, außer die Policy erlaubt Ikiform. form-action deckt den einfachen Post ab und connect-src das fetch des Skripts:

_headers
Content-Security-Policy: default-src 'self'; form-action 'self' https://ikiform.com; connect-src 'self' https://ikiform.com

Ein blockierter Post erscheint als CSP-Fehler in der Browser-Konsole und erreicht Ikiform nie, also erscheint auch im Dashboard nichts.

Prüfen, ob es funktioniert

  1. Füge deine Live-Domain und localhost unter Allowed domains hinzu.
  2. Baue die Site und öffne eine Beitragsseite. Beide Formulare sind da, jedes mit seinem eigenen source-Wert im Seitenquelltext.
  3. Abonniere über das Beitragsformular mit einer Adresse, die dir gehört. Beide Formulare wechseln zur Zeile „Schau in deinen Posteingang“.
  4. Schalte JavaScript aus und abonniere über den Footer. Du landest auf /subscribed/.
  5. Öffne Submissions im Dashboard. Die beiden Anmeldungen zeigen post und footer als source.
  6. Durchsuche die gebaute Site nach dem API-Key deines E-Mail-Tools. Er ist nicht da.

Stolperfallen

ProblemWas zu tun ist
Nichts im Dashboard und ein CSP-Fehler in der KonsoleFüge https://ikiform.com zu form-action und connect-src in deiner Content-Security-Policy hinzu.
422 mit unknown_field für sourceFüge source zur Felddefinition des Formulars hinzu oder entferne den Hidden Input.
403 origin_not_allowed, oder jeder Post landet im SpamDie Domain der Seite steht nicht unter Allowed domains des Formulars. Füge sie hinzu, plus localhost für lokale Tests. Subdomains einer eingetragenen Domain sind erlaubt.
Der Browser landet auf der Dankeseite von Ikiform statt auf deiner_redirect wird ignoriert, außer seine Domain steht unter Allowed domains und es ist eine https:-URL. Füge die Domain hinzu oder setze stattdessen Redirect after submit in den Einstellungen des Formulars.

Fragen

Hängst du bei einem Schritt fest oder willst eine Anleitung für deinen Fall? Schreib an hello@ikiform.com.