Ein Formular an deine E-Mail senden

Ein Formular an deine E-Mail senden6 Min. Lesezeit

Eine EmailJS-Alternative, die Schlüssel vom Client fernhält

Lös ein Kontaktformular von EmailJS: warum E-Mail-Schlüssel im Browser ein Risiko sind, was sich im Code ändert und worauf du verzichtest, etwa die automatische Antwort.

Preet SutharGründer, Ikiform

EmailJS sendet E-Mails aus dem Browser, daher landen deine Service-ID, Template-ID und dein Public Key im JavaScript deiner Seite, wo jeder sie kopieren kann. Ein Formular-Backend braucht keinen Schlüssel im Browser: Das Formular sendet an eine URL, und das Backend filtert Spam, speichert die Nachricht und schickt dir eine E-Mail. Du verlierst die automatische Antwort von EmailJS an den Besucher, die du stattdessen aus deinem eigenen E-Mail-Tool senden würdest.

Was hier anders ist

EmailJS ist aus gutem Grund beliebt: Es sendet echte E-Mails von einer statischen Site ohne Server. Dazu ruft der Browser EmailJS direkt mit drei Werten auf: einer Service-ID, einer Template-ID und einem Public Key. Sie stecken in deinem Bundle, lesbar im Quelltext der Seite oder im Netzwerk-Tab.

Laut der FAQ von EmailJS kann jemand, der sie kopiert, nur deine Templates mit deinem Inhalt senden, keine beliebigen E-Mails. Das stimmt für den festen Text des Templates. Aber die Variablen des Templates füllt der Aufrufer, die kopierten Schlüssel können dein Template also mit beliebigem Namen, beliebiger Nachricht und beliebigen Links senden, so oft dein Kontingent es zulässt. Und wenn das To eines Templates eine Variable ist, wie bei einer automatischen Antwort an die Adresse des Besuchers, bestimmt der Aufrufer auch den Empfänger.

Wer über einen E-Mail-Schlüssel im Browser senden kann und wer über einen Formular-EndpunktE-Mail-SDK im BrowserFormular-Endpunktdeine SeiteService, Templateund Public Keydas Skript vonirgendwemkopiert die dreiWertedein Templatemit deren Text, andessen Todeine Seiteeine Formular-ID,kein GeheimnisIkiformSpam-Score,Rate-Limit,erlaubte Domainsnur deineEmpfängerim DashboardfestgelegtQuelltext ansehensendenPostE-MailWer über einen E-Mail-Schlüssel im Browser senden kann und wer über einen Formular-EndpunktE-Mail-SDK im BrowserFormular-Endpunktdeine SeiteService, Template und PublicKeydas Skript von irgendwemkopiert die drei Wertedein Templatemit deren Text, an dessen Todeine Seiteeine Formular-ID, keinGeheimnisIkiformSpam-Score, Rate-Limit,erlaubte Domainsnur deine Empfängerim Dashboard festgelegtQuelltext ansehensendenPostE-Mail
  1. Mit einem E-Mail-SDK im Browser enthält die Seite alles, was zum Senden nötig ist. Ein Skript an anderer Stelle kann die IDs und den Schlüssel kopieren und dein Template mit eigenem Text senden, bis zu deinem Kontingent.
  2. Kommt der Empfänger des Templates aus einer Variable, bestimmen die kopierten Schlüssel auch, wer es bekommt.
  3. Mit einem Formular-Endpunkt enthält die Seite nur eine Formular-ID. Posts werden auf Spam gefiltert und per Rate-Limit begrenzt, und E-Mails gehen nur an die Empfänger, die du im Dashboard festlegst.
Auch eine Formular-ID ist kein Geheimnis. Der Unterschied ist, was ein Fremder damit tun kann: dein Formular absenden, nicht deine E-Mails verschicken.

EmailJS hat Einstellungen, die das eingrenzen, etwa die Begrenzung, welche Domains deinen Schlüssel nutzen dürfen, und ein Captcha für ein Template. Prüfe, welche dein Tarif enthält, und aktiviere sie, wenn du bleibst. Der Vergleich unten betrifft das Design, kein Urteil über den Dienst:

ItemEmailJSIkiform
Was der Browser enthältService-ID, Template-ID, Public KeyEine Formular-ID
Wer den Empfänger bestimmtDas Template, das eine Variable lesen kannNur deine Dashboard-Einstellungen
SpamfilterCaptcha pro Template, wenn du eins hinzufügstHoneypot, Spam-Bewertung und Rate-Limits bei jedem Formular, plus eine optionale Menschlichkeitsprüfung
Deine Nachrichten danachIn deinem PostfachIm Dashboard, im CSV-Export und in der API sowie in deinem Postfach
E-Mail an den BesucherJa, mit einem Template für automatische AntwortenNein
Dein eigenes E-Mail-DesignJa, Templates in deren EditorNein, ein festes Layout mit dem Betreff New submission: <form name>

Wenn es dir um die automatische Antwort oder eine gebrandete E-Mail geht, passt EmailJS oder dein eigener Server mit einer E-Mail-API besser. Wenn du die Nachricht sicher in deinem Postfach und irgendwo aufbewahrt haben willst, passt ein Formular-Backend.

Das Formular umziehen

Ein typisches EmailJS-Formular in React, basierend auf deren Kontaktformular-Tutorial:

ContactForm.tsx, vorher
import emailjs from "@emailjs/browser";
import { type FormEvent, useRef, useState } from "react";

export function ContactForm() {
  const form = useRef<HTMLFormElement>(null);
  const [status, setStatus] = useState("");

  async function onSubmit(event: FormEvent<HTMLFormElement>) {
    event.preventDefault();
    if (!form.current) return;
    try {
      await emailjs.sendForm("service_abc123", "template_xyz789", form.current, {
        publicKey: "aBcDeFgHiJkLmNoPq",
      });
      setStatus("Message sent.");
    } catch {
      setStatus("Couldn't send your message.");
    }
  }

  return (
    <form ref={form} onSubmit={onSubmit}>
      <input name="user_name" required />
      <input name="user_email" type="email" required />
      <textarea name="message" required />
      <button>Send</button>
      <p role="status">{status}</p>
    </form>
  );
}

Dasselbe Formular, das mit submit() aus @ikiform/sdk an Ikiform sendet:

ContactForm.tsx, nachher
import { submit } from "@ikiform/sdk";
import { type FormEvent, useState } from "react";

const FORM_ID = "frm_xxxxxxxxxxxxxxxx";

export function ContactForm() {
  const [status, setStatus] = useState<"idle" | "sending" | "sent">("idle");
  const [fields, setFields] = useState<Record<string, string[]>>({});
  const [error, setError] = useState("");

  async function onSubmit(event: FormEvent<HTMLFormElement>) {
    event.preventDefault();
    setStatus("sending");
    setFields({});
    setError("");
    const result = await submit(FORM_ID, new FormData(event.currentTarget));
    if (result.ok) {
      setStatus("sent");
      return;
    }
    setFields(result.error.fields ?? {});
    setError(result.error.code === "network_error" ? "You're offline. Try again." : result.error.message);
    setStatus("idle");
  }

  if (status === "sent") return <p role="status">Message sent. We'll reply by email.</p>;

  return (
    <form onSubmit={onSubmit}>
      <label htmlFor="user_name">Name</label>
      <input id="user_name" name="user_name" required />
      <label htmlFor="email">Email</label>
      <input id="email" name="email" type="email" required />
      {fields.email ? <p role="alert">{fields.email[0]}</p> : null}
      <label htmlFor="message">Message</label>
      <textarea id="message" name="message" required />
      {fields.message ? <p role="alert">{fields.message[0]}</p> : null}
      <input name="_gotcha" type="text" tabIndex={-1} autoComplete="off" hidden />
      {error && !Object.keys(fields).length ? <p role="alert">{error}</p> : null}
      <button disabled={status === "sending"}>{status === "sending" ? "Sending" : "Send"}</button>
    </form>
  );
}
  • @emailjs/browser und die drei IDs fallen weg. Deinstalliere das Paket und lösche das EmailJS-Template oder den Service, sobald das neue Formular funktioniert, damit gecachte Kopien deiner Seite nicht mehr senden.
  • user_email wird zu email. EmailJS sind die Namen egal, aber Ikiform nutzt das Feld namens email als Reply-To. Die anderen Namen können bleiben, weil Benachrichtigungen die Felder so auflisten, wie sie ankommen.
  • submit() wirft nie. Es gibt den Fehler-Umschlag zurück, sodass Feldfehler aus der Definition des Formulars unter ihrem Eingabefeld erscheinen.
  • Der Honeypot ist neu. EmailJS hatte keinen; dieses versteckte Eingabefeld fängt die einfachsten Bots ohne jede Einrichtung ab.
  • Füge deine Domain unter Allowed domains hinzu, damit Posts von anderen Sites als Spam markiert werden.
Du nutzt die Menschlichkeitsprüfung? Setze Spam check des Formulars auf Ikiform, ruf dann vor submit challenge(FORM_ID) auf und übergib das Token als turnstileToken. Die React-Anleitung zeigt das.

Wenn du die automatische Antwort genutzt hast

Ikiform schickt der Person, die das Formular ausfüllt, keine E-Mail. Benachrichtigungs-E-Mails gehen nur an dich und dein Team, es gibt also keinen Autoresponder, keine Bestätigungs-E-Mail und kein Double-Opt-in. Verschicke sie aus deinem E-Mail-Tool, ausgelöst durch einen Webhook. Ein Versanddienst, den du von einem Server aus steuerst, etwa Resend oder Postmark, kann sie bei jedem submission.created-Webhook senden, mit der Adresse des Besuchers aus der vertrauenswürdigen Payload statt aus dem Browser. Die Anleitung zu mehreren Empfängern hat einen Handler, den du anpassen kannst.

Frag dich, ob du sie brauchst. Die Dankesnachricht eines Kontaktformulars auf der Seite sagt dem Besucher, dass es geklappt hat, und eine echte Antwort von dir eine Stunde später schlägt jedes Template.

Prüfen, ob es funktioniert

  1. Füge unter Allowed domains des Formulars die Domain deiner Site hinzu, plus localhost, während du entwickelst. Lass das Feld leer, um Posts von überall anzunehmen.
  2. Durchsuche deine gebaute Site nach emailjs und deiner alten Service-ID. Nichts sollte gefunden werden.
  3. Füll das Formular auf deiner Seite aus und sende es ab. Du landest auf deiner Dankeseite oder der gehosteten von Ikiform.
  4. Prüfe das Postfach jedes Benachrichtigungsempfängers. Jeder bekommt eine E-Mail mit den Feldern, und Reply geht an den Absender, wenn das Formular ein email-Feld hat.
  5. Sende einen Post mit ausgefülltem Honeypot, _gotcha=bot. Er bekommt trotzdem eine Erfolgsantwort, aber die Einsendung wird als Spam gespeichert und löst weder E-Mail noch Webhook aus.

Wenn etwas schiefgeht

ProblemWas zu tun ist
Reply geht immer noch an IkiformDas Feld heißt noch user_email. Benenne es in email um oder in eins von email, email_address, your_email, reply_to, contact_email.
422 validation_failedDer Post passt nicht zur Felddefinition des Formulars. error.fields nennt jedes Feld und den Grund. Gleiche den name des Eingabefelds an die Feld-ID an.
403 challenge_requiredDie Spamprüfung des Formulars steht auf Ikiform, und ein fetch-Post hatte kein Token. Hol dir vor jedem Absenden eins mit challenge(formId) aus @ikiform/sdk oder schalte die Prüfung aus.
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.

Fragen

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