# 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.

Veröffentlicht am 2026-10-25 · https://ikiform.com/de/guides/send-form-to-email/emailjs-alternative

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.

Diagramm: Wer über einen E-Mail-Schlüssel im Browser senden kann und wer über einen Formular-Endpunkt

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:

|   | EmailJS | Ikiform |
| --- | --- | --- |
| Was der Browser enthält | Service-ID, Template-ID, Public Key | Eine Formular-ID |
| Wer den Empfänger bestimmt | Das Template, das eine Variable lesen kann | Nur deine Dashboard-Einstellungen |
| Spamfilter | Captcha pro Template, wenn du eins hinzufügst | Honeypot, Spam-Bewertung und Rate-Limits bei jedem Formular, plus eine optionale Menschlichkeitsprüfung |
| Deine Nachrichten danach | In deinem Postfach | Im Dashboard, im CSV-Export und in der API sowie in deinem Postfach |
| E-Mail an den Besucher | Ja, mit einem Template für automatische Antworten | Nein |
| Dein eigenes E-Mail-Design | Ja, Templates in deren Editor | Nein, 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`:

```tsx
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`:

```tsx
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](https://ikiform.com/de/docs/guides/react.md) 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](https://ikiform.com/de/docs/webhooks.md). 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](https://ikiform.com/de/guides/send-form-to-email/multiple-recipients.md#route) 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

| Problem | Was zu tun ist |
| --- | --- |
| Reply geht immer noch an Ikiform | Das 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_failed` | Der 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_required` | Die 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 Spam | Die 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

### Ist Ikiform günstiger als EmailJS?

Das hängt von Volumen und Tarif ab, und beides ändert sich mit der Zeit, also vergleiche die aktuellen Preisseiten. Ikiform hat keinen kostenlosen Tarif; abgerechnet wird pro Einsendung, mit Pro oder einmaligen Paketen.

### Ist es sicher, den Public Key von EmailJS offenzulegen?

EmailJS sagt, ein kopierter Schlüssel kann nur deine Templates senden, keine eigenen E-Mails. Die Variablen des Templates füllt aber trotzdem der Aufrufer, und ist der Empfänger eine Variable, wählt er auch den. Beschränke den Schlüssel auf deine Domains und füge ein Captcha hinzu, wenn du ihn weiter nutzt.

### Braucht Ikiform einen API-Schlüssel im Browser?

Nein. Ein Browser-Formular sendet ohne Schlüssel an die URL deines Formulars. Secret Keys gibt es nur für das Senden von deinem eigenen Server, und sie gehören in serverseitige Umgebungsvariablen.

### Kann Ikiform der Person, die das Formular gesendet hat, eine E-Mail schicken?

Nein. Ikiform schickt E-Mails an dich und dein Team, nicht an den Absender, es gibt also keinen Autoresponder und keine Bestätigungs-E-Mail. Sende einen Webhook an dein E-Mail-Tool und verschicke die Bestätigung von dort.

### Kann ich meine EmailJS-Feldnamen behalten?

Ja, bis auf das E-Mail-Feld: Nenne es email, damit Antworten auf Benachrichtigungen an den Besucher gehen. Ikiform speichert jeden Feldnamen, den du sendest.

## Weitere Anleitungen

- [Ein Formular an deine E-Mail senden](https://ikiform.com/de/guides/send-form-to-email.md): Bekomm jede Formulareinsendung ohne Mailserver in dein Postfach: warum mailto: und PHPs mail() scheitern und ein Setup, das Spam filtert und dich antworten lässt.
- [Next.js](https://ikiform.com/de/guides/send-form-to-email/nextjs.md): Zwei Wege, ein Next.js-Formular per Server Action zu mailen: selbst mit Resend oder Nodemailer senden oder an ein Formular-Backend weiterleiten, ehrlich verglichen.
- [Aus deinem Postfach antworten](https://ikiform.com/de/guides/send-form-to-email/reply-to.md): Sorg dafür, dass Antworten auf eine Formular-Benachrichtigung an die Person gehen, die das Formular ausgefüllt hat: welche Feldnamen funktionieren, wann _replyto nötig ist und warum Antworten fehlgehen.
- [Ohne PHP](https://ikiform.com/de/guides/send-form-to-email/without-php.md): Ersetze ein contact.php-Skript, das mail() aufruft, durch ein Formular, das an einen gehosteten Endpunkt sendet, mit deinen Feldnamen, deiner Dankeseite und Antworten.
- [An Gmail](https://ikiform.com/de/guides/send-form-to-email/gmail.md): Bekomm Formulareinsendungen in Gmail ohne mailto:-Links, App-Passwörter oder Apps Script, dann beschrifte sie, halte sie aus dem Spam heraus und antworte aus Gmail.
- [Mehrere Empfänger](https://ikiform.com/de/guides/send-form-to-email/multiple-recipients.md): Mail ein Formular an dein ganzes Team oder leite jede Nachricht nach Thema an die richtige Person, ohne dass das Formular selbst bestimmt, wer die Mail bekommt.
