Anleitungen16 Min. Lesezeit
Newsletter-Anmeldeformular ohne das Embed von Mailchimp
Baue ein Newsletter-Anmeldeformular mit eigenem Markup, speichere die Einwilligung, blocke Bot-Anmeldungen und gib jeden Abonnenten an Kit, MailerLite, beehiiv oder ein anderes Tool weiter.
Schreib das Anmeldeformular in deinem eigenen HTML und lass es an ein Formular-Backend senden, statt den Embed-Code deines E-Mail-Anbieters einzufügen. Das Backend prüft die Adresse und die Einwilligungs-Checkbox, filtert Bots und sendet einen signierten Webhook, und ein kleines Relay legt den Abonnenten über die API deines E-Mail-Tools an. Das E-Mail-Tool sendet dann die Bestätigungs-E-Mail, den Newsletter und die Abmeldelinks. Ein späterer Toolwechsel ändert also das Relay, nicht jedes Formular.
Jedes E-Mail-Tool bietet dir ein Embed: einen Block generiertes Markup, den du in deine Seite einfügst. Das funktioniert, aber das Formular gehört dann dem Tool. Es sendet direkt an diesen Anbieter, bringt dessen Markup und oft dessen Styles und Skripte mit, und an dem Tag, an dem du zu einem anderen Tool wechselst, suchst du jede Kopie davon auf deiner Site zusammen. Diese Anleitung baut ein Anmeldeformular, das dir gehört, in dem Markup und Framework, das du schon nutzt, und verbindet es mit dem Tool, das deinen Newsletter versendet.
Der Weg einer Anmeldung
Hier gibt es zwei Aufgaben, und sie gehören an verschiedene Orte. Eine Anmeldung entgegenzunehmen ist Sache des Formulars: Adresse prüfen, Einwilligungs-Checkbox prüfen, Bots fernhalten, festhalten, wann es passiert ist. Einen Newsletter zu versenden ist Sache des E-Mail-Tools: Adresse bestätigen, Ausgaben senden, Abmeldungen und Bounces verarbeiten. Ikiform übernimmt das Erste. Dein E-Mail-Tool das Zweite. Ein signierter Webhook verbindet beide.
- Ein Leser füllt das Anmeldeformular auf deiner Seite aus, und es sendet an deinen Ikiform-Endpunkt.
- Ikiform prüft die Adresse, die erforderliche Einwilligungs-Checkbox, den Honeypot, den Spam-Score und das Rate-Limit.
- Die angenommene Anmeldung wird mit Zeitstempel, Origin und Referrer im Dashboard gespeichert.
- Ikiform kann dich per E-Mail über jede Anmeldung informieren. Dem Leser schreibt es nie.
- Ikiform sendet einen signierten submission.created-Webhook.
- Ein Relay prüft die Signatur und trägt den Leser in dein E-Mail-Tool ein, das die Bestätigung und später jede Ausgabe sendet.
Vier Wege, Newsletter-Anmeldungen zu sammeln
| Ansatz | Stark bei | Nachteile |
|---|---|---|
| Das Embed des E-Mail-Tools, von Mailchimp, Kit, MailerLite oder beehiiv | Nichts zu betreiben. Bestätigung, Tagging und Willkommens-E-Mail werden in den Einstellungen des Tools eingerichtet. | Markup und Verhalten gehören dem Tool. Mailchimp weist darauf hin, dass seine eingebetteten Formulare die Styles deiner Site erben und auf deiner Seite daher anders aussehen können, und wer für ein bestehendes Formular reCAPTCHA einschaltet, muss neuen Code generieren und einfügen. Jede Kopie ist an einen Anbieter gebunden. |
| Eine eigene Serverroute, die die API des Tools aufruft | Volle Kontrolle über Formular und Request, und der API-Key bleibt auf deinem Server. | Validierung, Botfilter, Rate-Limits und einen Nachweis der Einwilligung baust du selbst, und du brauchst einen Server, auch wenn die Site statisch ist. |
| Ein gehosteter Formular-Baukasten plus eine Automatisierung, etwa Typeform mit Zapier | Kein Code, und das Automatisierungstool kennt jeden E-Mail-Anbieter. | Ein iframe, das nicht zu deiner Seite passt, plus zwei Abos. Webhooks und Branding hängen vom Tarif ab. |
| Ein Formular-Backend wie Ikiform plus ein Relay | Dein eigenes Markup auf jeder Site, Spamfilter, ein gespeicherter Einwilligungsnachweis und signierte Webhooks, ohne Server für das Formular. | Ikiform schickt dem Leser keine E-Mail. Bestätigung und jeder Newsletter kommen aus deinem E-Mail-Tool, über ein kleines Relay oder eine Automatisierung. |
Mit einer Site, einem Tool und dessen Embed zufrieden? Dann bleib dabei. Bau dein eigenes Formular, wenn es zu deinem Design passen muss, sich über mehrere Sites erstreckt oder das Tool überdauern soll.
Das Formular
Ein gutes Newsletter-Formular ist klein: eine E-Mail, optional ein Vorname für die Anrede und eine Einwilligungs-Checkbox, deren Label das Versprechen ist, das du gibst. Ein verstecktes source-Input hält fest, wo auf der Site die Anmeldung stattfand. Wähle deinen Stack; die Auswahl gilt für jedes Codebeispiel in diesen Anleitungen.
Füge das Formular in deinen Footer, deine Sidebar oder ans Ende eines Beitrags ein. Es sendet auf die einfache Art und landet auf deiner eigenen Dankeseite, funktioniert also ohne JavaScript.
<form id="newsletter" action="https://ikiform.com/f/frm_xxxxxxxxxxxxxxxx" method="post">
<label for="nl-email">Email</label>
<input id="nl-email" name="email" type="email" required autocomplete="email" placeholder="you@example.com" />
<label for="nl-name">First name (optional)</label>
<input id="nl-name" name="first_name" type="text" maxlength="80" autocomplete="given-name" />
<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="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 id="nl-status" role="status" hidden></p>
</form>Damit der Leser beim Artikel bleibt, füge dieses Skript nach dem Formular ein. Es sendet mit fetch, ersetzt das Formular durch eine Zeile, die ihn bittet, in sein Postfach zu schauen, und zeigt die Meldung des Servers bei der E-Mail oder der Checkbox an.
const form = document.querySelector("#newsletter");
const status = document.querySelector("#nl-status");
const button = form.querySelector("button");
form.addEventListener("submit", async (event) => {
event.preventDefault();
const body = new FormData(form);
button.disabled = true;
status.hidden = false;
status.textContent = "Subscribing";
try {
const response = await fetch(form.action, { method: "POST", headers: { Accept: "application/json" }, body });
const result = await response.json();
if (result.ok) {
form.replaceChildren(status);
status.textContent = "Almost done. Open the email we sent you and confirm your address.";
return;
}
const fields = result.error.fields ?? {};
status.textContent = fields.email?.[0] ?? fields.consent?.[0] ?? result.error.message;
} catch {
status.textContent = "Couldn't reach the server. Try again.";
}
button.disabled = false;
});Eine Server Action leitet die Anmeldung mit deinem Secret Key weiter. Sie gibt Fehler pro Feld zurück, sodass die Meldung zur E-Mail unter dem E-Mail-Input und die zur Einwilligung unter der Checkbox steht. Die Komponente nimmt ein source-Prop, damit Footer und eine Box im Artikel unterschiedliche Werte speichern.
"use server";
import { headers } from "next/headers";
export interface SignupState {
done: boolean;
errors: { email?: string; consent?: string; form?: string };
email: string;
}
export async function subscribe(_previous: SignupState, formData: FormData): Promise<SignupState> {
const email = String(formData.get("email") ?? "").trim();
const consent = formData.get("consent") === "true";
if (!consent) return { done: false, errors: { consent: "Tick the box so we know you want the emails." }, email };
const response = await fetch("https://ikiform.com/f/frm_xxxxxxxxxxxxxxxx", {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
Authorization: `Bearer ${process.env.IKIFORM_SECRET_KEY}`,
Origin: (await headers()).get("origin") ?? "",
},
body: JSON.stringify({
email,
first_name: String(formData.get("first_name") ?? "").trim() || undefined,
consent,
source: String(formData.get("source") ?? "footer"),
_gotcha: String(formData.get("_gotcha") ?? ""),
}),
});
const result = await response.json().catch(() => null);
if (result?.ok) return { done: true, errors: {}, email };
const fields = result?.error?.fields ?? {};
return {
done: false,
errors: {
email: fields.email?.[0],
consent: fields.consent?.[0],
form: fields.email || fields.consent ? undefined : (result?.error?.message ?? "Couldn't subscribe. Try again."),
},
email,
};
}"use client";
import { useActionState } from "react";
import { type SignupState, subscribe } from "./actions";
const initial: SignupState = { done: false, errors: {}, email: "" };
export function NewsletterForm({ source = "footer" }: { source?: string }) {
const [state, action, pending] = useActionState(subscribe, initial);
if (state.done) return <p role="status">Almost done. Open the email we sent you and confirm your address.</p>;
return (
<form action={action} noValidate>
<label htmlFor="nl-email">Email</label>
<input
id="nl-email"
name="email"
type="email"
required
autoComplete="email"
defaultValue={state.email}
aria-invalid={Boolean(state.errors.email)}
aria-describedby="nl-email-error"
/>
<p id="nl-email-error">{state.errors.email}</p>
<label htmlFor="nl-name">First name (optional)</label>
<input id="nl-name" name="first_name" type="text" maxLength={80} autoComplete="given-name" />
<label>
<input name="consent" type="checkbox" value="true" aria-describedby="nl-consent-error" />
Email me the newsletter. I can unsubscribe at any time.
</label>
<p id="nl-consent-error">{state.errors.consent}</p>
<input type="hidden" name="source" value={source} />
<input name="_gotcha" type="text" tabIndex={-1} autoComplete="off" hidden />
{state.errors.form ? <p role="alert">{state.errors.form}</p> : null}
<button disabled={pending}>{pending ? "Subscribing" : "Subscribe"}</button>
</form>
);
}Mehr zu Next.js? Lies Newsletter-Anmeldeformular mit Next.js bauen.
Eine Astro Action mit accept: "form" liest die Checkbox als Boolean und die E-Mail mit z.email(). isInputError unterscheidet eine ungültige Adresse von einem abgelehnten Post. Formular-Actions brauchen eine On-Demand-Seite, also füge einen Adapter hinzu und setze export const prerender = false in jeder Seite, die die Komponente rendert; in einer Komponente bewirkt der Export nichts.
import { ActionError, defineAction } from "astro:actions";
import { getSecret } from "astro:env/server";
import { z } from "astro/zod";
export const server = {
subscribe: defineAction({
accept: "form",
input: z.object({
email: z.email(),
first_name: z.string().max(80).nullish(),
consent: z.boolean(),
source: z.string().default("footer"),
_gotcha: z.string().nullish(),
}),
handler: async (input, context) => {
const origin = context.request.headers.get("origin");
if (!input.consent) {
throw new ActionError({ code: "BAD_REQUEST", message: "Tick the box so we know you want the emails." });
}
const response = await fetch("https://ikiform.com/f/frm_xxxxxxxxxxxxxxxx", {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
Authorization: `Bearer ${getSecret("IKIFORM_SECRET_KEY")}`,
...(origin ? { Origin: origin } : {}),
},
body: JSON.stringify({ ...input, first_name: input.first_name ?? undefined }),
});
const result = await response.json();
if (!result.ok) {
throw new ActionError({
code: "BAD_REQUEST",
message: result.error.fields?.email?.[0] ?? result.error.message,
});
}
return { subscribed: true };
},
}),
};---
import { actions, isInputError } from "astro:actions";
const result = Astro.getActionResult(actions.subscribe);
const fields = isInputError(result?.error) ? result.error.fields : {};
---
{result?.data?.subscribed ? (
<p role="status">Almost done. Open the email we sent you and confirm your address.</p>
) : (
<form method="POST" action={actions.subscribe}>
<label for="nl-email">Email</label>
<input id="nl-email" name="email" type="email" required autocomplete="email" />
{fields.email && <p>Enter a valid email address.</p>}
<label for="nl-name">First name (optional)</label>
<input id="nl-name" name="first_name" type="text" maxlength="80" />
<label>
<input name="consent" type="checkbox" required />
Email me the newsletter. I can unsubscribe at any time.
</label>
<input type="hidden" name="source" value="footer" />
<input name="_gotcha" type="text" tabindex="-1" autocomplete="off" hidden />
{result?.error && !isInputError(result.error) && <p role="alert">{result.error.message}</p>}
<button>Subscribe</button>
</form>
)}Mehr zu Astro? Lies Newsletter-Anmeldeformular mit Astro bauen.
Eine createServerFn-POST-Funktion wandelt die FormData in .validator() in JSON um und gibt Feldfehler als Daten zurück, statt zu werfen, damit die Komponente jede Meldung neben ihr Input setzen kann.
import { createServerFn, useServerFn } from "@tanstack/react-start";
import { getRequestHeader } from "@tanstack/react-start/server";
import { type FormEvent, useState } from "react";
type Errors = { email?: string; consent?: string; form?: string };
const subscribe = createServerFn({ method: "POST" })
.validator((data: unknown) => {
if (!(data instanceof FormData)) throw new Error("Expected form data");
return {
email: String(data.get("email") ?? "").trim(),
first_name: String(data.get("first_name") ?? "").trim() || undefined,
consent: data.get("consent") === "true",
source: String(data.get("source") ?? "footer"),
_gotcha: String(data.get("_gotcha") ?? ""),
};
})
.handler(async ({ data }): Promise<{ ok: boolean; errors: Errors }> => {
const origin = getRequestHeader("origin");
const response = await fetch("https://ikiform.com/f/frm_xxxxxxxxxxxxxxxx", {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
Authorization: `Bearer ${process.env.IKIFORM_SECRET_KEY}`,
...(origin ? { Origin: origin } : {}),
},
body: JSON.stringify(data),
});
const result = await response.json();
if (result.ok) return { ok: true, errors: {} };
const fields = result.error.fields ?? {};
return {
ok: false,
errors: { email: fields.email?.[0], consent: fields.consent?.[0], form: result.error.message },
};
});
export function NewsletterForm() {
const send = useServerFn(subscribe);
const [done, setDone] = useState(false);
const [errors, setErrors] = useState<Errors>({});
const [pending, setPending] = useState(false);
async function onSubmit(event: FormEvent<HTMLFormElement>) {
event.preventDefault();
setPending(true);
try {
const result = await send({ data: new FormData(event.currentTarget) });
setDone(result.ok);
setErrors(result.errors);
} catch {
setErrors({ form: "Couldn't subscribe. Try again." });
} finally {
setPending(false);
}
}
if (done) return <p role="status">Almost done. Open the email we sent you and confirm your address.</p>;
return (
<form method="post" onSubmit={onSubmit}>
<label htmlFor="nl-email">Email</label>
<input id="nl-email" name="email" type="email" required autoComplete="email" />
{errors.email ? <p>{errors.email}</p> : null}
<label htmlFor="nl-name">First name (optional)</label>
<input id="nl-name" name="first_name" type="text" maxLength={80} />
<label>
<input name="consent" type="checkbox" value="true" required />
Email me the newsletter. I can unsubscribe at any time.
</label>
{errors.consent ? <p>{errors.consent}</p> : null}
<input type="hidden" name="source" value="footer" />
<input name="_gotcha" type="text" tabIndex={-1} autoComplete="off" hidden />
{errors.form && !errors.email && !errors.consent ? <p role="alert">{errors.form}</p> : null}
<button disabled={pending}>{pending ? "Subscribing" : "Subscribe"}</button>
</form>
);
}Mehr zu TanStack Start? Lies Newsletter-Anmeldeformular mit TanStack Start bauen.
Ein Footer-Formular sollte nicht von der Seite wegnavigieren, die jemand gerade liest. Leg die Action in eine Resource Route unter /newsletter und sende mit useFetcher dorthin; das sendet ohne Navigation und behält das Ergebnis auf der aktuellen Seite.
import { data } from "react-router";
import type { Route } from "./+types/newsletter";
export async function action({ request }: Route.ActionArgs) {
const formData = await request.formData();
const response = await fetch("https://ikiform.com/f/frm_xxxxxxxxxxxxxxxx", {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
Authorization: `Bearer ${process.env.IKIFORM_SECRET_KEY}`,
Origin: request.headers.get("origin") ?? "",
},
body: JSON.stringify({
email: String(formData.get("email") ?? "").trim(),
first_name: String(formData.get("first_name") ?? "").trim() || undefined,
consent: formData.get("consent") === "true",
source: String(formData.get("source") ?? "footer"),
_gotcha: String(formData.get("_gotcha") ?? ""),
}),
});
const result = await response.json();
if (result.ok) return { ok: true as const };
return data(
{ ok: false as const, fields: (result.error.fields ?? {}) as Record<string, string[]>, message: String(result.error.message) },
{ status: response.status },
);
}import { type RouteConfig, index, route } from "@react-router/dev/routes";
export default [index("routes/home.tsx"), route("newsletter", "routes/newsletter.tsx")] satisfies RouteConfig;import { useFetcher } from "react-router";
import type { action } from "~/routes/newsletter";
export function NewsletterForm() {
const fetcher = useFetcher<typeof action>();
const result = fetcher.data;
const busy = fetcher.state !== "idle";
if (result?.ok) return <p role="status">Almost done. Open the email we sent you and confirm your address.</p>;
return (
<fetcher.Form method="post" action="/newsletter">
<label htmlFor="nl-email">Email</label>
<input id="nl-email" name="email" type="email" required autoComplete="email" />
{result?.fields.email ? <p>{result.fields.email[0]}</p> : null}
<label htmlFor="nl-name">First name (optional)</label>
<input id="nl-name" name="first_name" type="text" maxLength={80} />
<label>
<input name="consent" type="checkbox" value="true" required />
Email me the newsletter. I can unsubscribe at any time.
</label>
{result?.fields.consent ? <p>{result.fields.consent[0]}</p> : null}
<input type="hidden" name="source" value="footer" />
<input name="_gotcha" type="text" tabIndex={-1} autoComplete="off" hidden />
{result && !result.fields.email && !result.fields.consent ? <p role="alert">{result.message}</p> : null}
<button disabled={busy}>{busy ? "Subscribing" : "Subscribe"}</button>
</fetcher.Form>
);
}Mehr zu React Router? Lies Newsletter-Anmeldeformular mit React Router bauen.
Actions liegen in einer +page.server.ts, nie in einem Layout. Die Action sitzt also auf einer Seite /newsletter, und der Footer sendet an /newsletter?/subscribe. Der eigene use:enhance-Callback verarbeitet das Ergebnis selbst, statt update() aufzurufen, damit der Leser beim Artikel bleibt. Deklariere IKIFORM_SECRET_KEY in src/env.ts mit defineEnvVars, damit $app/env/private ihn lesen kann.
import { fail } from "@sveltejs/kit";
import { IKIFORM_SECRET_KEY } from "$app/env/private";
import type { Actions } from "./$types";
export const actions = {
subscribe: async ({ request, fetch, url }) => {
const form = await request.formData();
const response = await fetch("https://ikiform.com/f/frm_xxxxxxxxxxxxxxxx", {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
Authorization: `Bearer ${IKIFORM_SECRET_KEY}`,
Origin: request.headers.get("origin") ?? url.origin,
},
body: JSON.stringify({
email: String(form.get("email") ?? "").trim(),
first_name: String(form.get("first_name") ?? "").trim() || undefined,
consent: form.get("consent") === "true",
source: String(form.get("source") ?? "footer"),
_gotcha: String(form.get("_gotcha") ?? ""),
}),
});
const result = await response.json();
if (!result.ok) {
const fields = result.error.fields ?? {};
return fail(response.status, { message: String(fields.email?.[0] ?? fields.consent?.[0] ?? result.error.message) });
}
return { subscribed: true };
},
} satisfies Actions;<script lang="ts">
import { applyAction, enhance } from "$app/forms";
let done = $state(false);
let message = $state("");
let pending = $state(false);
</script>
{#if done}
<p role="status">Almost done. Open the email we sent you and confirm your address.</p>
{:else}
<form
method="POST"
action="/newsletter?/subscribe"
use:enhance={() => {
pending = true;
return async ({ result }) => {
pending = false;
if (result.type === "success") done = true;
else if (result.type === "failure") message = String(result.data?.message ?? "Couldn't subscribe.");
else await applyAction(result);
};
}}
>
<label for="nl-email">Email</label>
<input id="nl-email" name="email" type="email" required autocomplete="email" />
<label for="nl-name">First name (optional)</label>
<input id="nl-name" name="first_name" type="text" maxlength="80" />
<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="footer" />
<input name="_gotcha" type="text" tabindex="-1" autocomplete="off" hidden />
{#if message}
<p role="alert">{message}</p>
{/if}
<button disabled={pending}>{pending ? "Subscribing" : "Subscribe"}</button>
</form>
{/if}Mehr zu SvelteKit? Lies Newsletter-Anmeldeformular mit SvelteKit bauen.
Eine Serverroute hält den Secret Key vom Browser fern und gibt die Feldfehler von Ikiform in den data des Fehlers zurück. Setze NUXT_IKIFORM_SECRET_KEY und deklariere ikiformSecretKey unter runtimeConfig in nuxt.config.ts.
interface Signup {
email?: string;
first_name?: string;
consent?: boolean;
source?: string;
_gotcha?: string;
}
export default defineEventHandler(async (event) => {
const body = await readBody<Signup>(event);
const config = useRuntimeConfig(event);
const origin = getRequestHeader(event, "origin");
const response = await fetch("https://ikiform.com/f/frm_xxxxxxxxxxxxxxxx", {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
Authorization: `Bearer ${config.ikiformSecretKey}`,
...(origin ? { Origin: origin } : {}),
},
body: JSON.stringify({
email: body.email?.trim() ?? "",
first_name: body.first_name?.trim() || undefined,
consent: body.consent === true,
source: body.source ?? "footer",
_gotcha: body._gotcha ?? "",
}),
});
const result = await response.json();
if (!result.ok) {
throw createError({
status: response.status,
message: String(result.error.message),
data: { fields: result.error.fields ?? {} },
});
}
return { subscribed: true };
});<script setup lang="ts">
const email = ref("");
const firstName = ref("");
const consent = ref(false);
const trap = ref("");
const status = ref<"idle" | "sending" | "done">("idle");
const errors = ref<Record<string, string[]>>({});
const message = ref("");
async function subscribe() {
status.value = "sending";
errors.value = {};
message.value = "";
try {
await $fetch("/api/newsletter", {
method: "POST",
body: { email: email.value, first_name: firstName.value, consent: consent.value, source: "footer", _gotcha: trap.value },
});
status.value = "done";
} catch (failure) {
const data = (failure as { data?: { message?: string; data?: { fields?: Record<string, string[]> } } }).data;
errors.value = data?.data?.fields ?? {};
message.value = data?.message ?? "Couldn't subscribe. Try again.";
status.value = "idle";
}
}
</script>
<template>
<p v-if="status === 'done'" role="status">Almost done. Open the email we sent you and confirm your address.</p>
<form v-else @submit.prevent="subscribe">
<label for="nl-email">Email</label>
<input id="nl-email" v-model="email" name="email" type="email" required autocomplete="email" />
<p v-if="errors.email">{{ errors.email[0] }}</p>
<label for="nl-name">First name (optional)</label>
<input id="nl-name" v-model="firstName" name="first_name" type="text" maxlength="80" />
<label>
<input v-model="consent" name="consent" type="checkbox" required />
Email me the newsletter. I can unsubscribe at any time.
</label>
<p v-if="errors.consent">{{ errors.consent[0] }}</p>
<input v-model="trap" name="_gotcha" type="text" tabindex="-1" autocomplete="off" hidden />
<p v-if="message && !errors.email && !errors.consent" role="alert">{{ message }}</p>
<button :disabled="status === 'sending'">{{ status === "sending" ? "Subscribing" : "Subscribe" }}</button>
</form>
</template>Mehr zu Nuxt? Lies Newsletter-Anmeldeformular mit Nuxt bauen.
Eine Single-Page-App sendet aus dem Browser. submit() aus @ikiform/sdk sendet die FormData und liefert immer ok und bei einem Fehler die Feldfehler, du schreibst also keinen try-Block. Leg hier nie einen Secret Key ab; stattdessen schützt Allowed domains (erlaubte Domains) das Formular.
import { submit } from "@ikiform/sdk";
import { type FormEvent, useState } from "react";
type FieldErrors = Record<string, string[]>;
export function NewsletterForm() {
const [done, setDone] = useState(false);
const [errors, setErrors] = useState<FieldErrors>({});
const [message, setMessage] = useState("");
const [pending, setPending] = useState(false);
async function onSubmit(event: FormEvent<HTMLFormElement>) {
event.preventDefault();
setPending(true);
setErrors({});
setMessage("");
const result = await submit("frm_xxxxxxxxxxxxxxxx", new FormData(event.currentTarget));
setPending(false);
if (result.ok) {
setDone(true);
return;
}
setErrors(result.error.fields ?? {});
setMessage(result.error.message);
}
if (done) return <p role="status">Almost done. Open the email we sent you and confirm your address.</p>;
return (
<form action="https://ikiform.com/f/frm_xxxxxxxxxxxxxxxx" method="post" onSubmit={onSubmit}>
<label htmlFor="nl-email">Email</label>
<input id="nl-email" name="email" type="email" required autoComplete="email" />
{errors.email ? <p>{errors.email[0]}</p> : null}
<label htmlFor="nl-name">First name (optional)</label>
<input id="nl-name" name="first_name" type="text" maxLength={80} />
<label>
<input name="consent" type="checkbox" value="true" required />
Email me the newsletter. I can unsubscribe at any time.
</label>
{errors.consent ? <p>{errors.consent[0]}</p> : null}
<input type="hidden" name="source" value="footer" />
<input name="_gotcha" type="text" tabIndex={-1} autoComplete="off" hidden />
{message && !errors.email && !errors.consent ? <p role="alert">{message}</p> : null}
<button disabled={pending}>{pending ? "Subscribing" : "Subscribe"}</button>
</form>
);
}Derselbe submit()-Aufruf in einer Vue-Single-File-Component. Das Formular behält seine action und sendet daher weiter, falls das Skript nicht lädt.
<script setup lang="ts">
import { submit } from "@ikiform/sdk";
import { ref } from "vue";
const status = ref<"idle" | "sending" | "done">("idle");
const errors = ref<Record<string, string[]>>({});
const message = ref("");
async function subscribe(event: SubmitEvent) {
status.value = "sending";
const result = await submit("frm_xxxxxxxxxxxxxxxx", new FormData(event.target as HTMLFormElement));
if (result.ok) {
status.value = "done";
return;
}
errors.value = result.error.fields ?? {};
message.value = result.error.message;
status.value = "idle";
}
</script>
<template>
<p v-if="status === 'done'" role="status">Almost done. Open the email we sent you and confirm your address.</p>
<form v-else action="https://ikiform.com/f/frm_xxxxxxxxxxxxxxxx" method="post" @submit.prevent="subscribe">
<label for="nl-email">Email</label>
<input id="nl-email" name="email" type="email" required autocomplete="email" />
<p v-if="errors.email">{{ errors.email[0] }}</p>
<label for="nl-name">First name (optional)</label>
<input id="nl-name" name="first_name" type="text" maxlength="80" />
<label>
<input name="consent" type="checkbox" value="true" required />
Email me the newsletter. I can unsubscribe at any time.
</label>
<p v-if="errors.consent">{{ errors.consent[0] }}</p>
<input type="hidden" name="source" value="footer" />
<input name="_gotcha" type="text" tabindex="-1" autocomplete="off" hidden />
<p v-if="message && !errors.email && !errors.consent" role="alert">{{ message }}</p>
<button :disabled="status === 'sending'">{{ status === "sending" ? "Subscribing" : "Subscribe" }}</button>
</form>
</template>- Ersetze
frm_xxxxxxxxxxxxxxxxdurch die ID deines Formulars aus dem Dashboard. - Lass den Namen des E-Mail-Inputs bei
email. Ikiform validiert ihn, schreibt die Domain klein und gleicht sie mit Wegwerf-E-Mail-Domains ab. - Das versteckte
_gotcha-Input ist der Honeypot. Menschen sehen es nie, und Bots, die jedes Input ausfüllen, markieren sich selbst als Spam. - Die Checkbox sendet angehakt
true. Nicht angehakt sendet sie nichts, und eine erforderliche Checkbox wird mitThis box must be checkedabgelehnt. - Die Erfolgszeile bittet darum, ins Postfach zu schauen, weil dein E-Mail-Tool eine Bestätigung sendet. Hast du Double-Opt-in ausgeschaltet, mach daraus ein einfaches Dankeschön.
- Die serverseitigen Varianten geben den
Origin-Header des Browsers weiter, weil Ikiform kostenlose Tests auflocalhostdaran erkennt und der eigenefetcheines Servers keinen sendet.
IKIFORM_SECRET_KEY in reinen Server-Umgebungsvariablen.Die Einrichtung pro Framework steht in der Doku: Next.js, Astro, SvelteKit, Nuxt, React, Vue und reines HTML. Die Vorlage für Newsletter-Formulare erzeugt dieselben Felder als HTML, React und Next.js-Server-Action.
Felder und die Definition
Füge die Definition unten im Tab Fields (Felder) des Formulars ein, und Ikiform setzt sie auf dem Server durch, egal was die Seite sendet. Die Vorschau führt denselben Validator in deinem Browser aus: Lass die Box leer und sende ab, um den Fehler zu sehen, den ein Bot oder eine kaputte Seite bekäme.
Vorschau
Führt den Validator von Ikiform in deinem Browser aus. Es wird nichts gesendet.
source ein verstecktes Input. Hier ist es nur ein sichtbares Textfeld, damit du es ausprobieren kannst.| Feld | Warum | Beachte |
|---|---|---|
email | Der Abonnent | Pflicht. type="email" öffnet auf dem Handy die passende Tastatur |
first_name | Eine Anrede in jeder Ausgabe | Optional. Die meisten E-Mail-Tools haben ein Vornamensfeld, dem du es zuordnen kannst |
consent | Ein Nachweis, dass die Person E-Mails angefordert hat | Erforderliche Checkbox. Gespeichert als true |
source | Welches Formular auf der Site gewirkt hat: Footer, Beitrag, Popup | Versteckt. Setze pro Platzierung einen anderen Wert und gib ihn als Tag oder Feld weiter |
Sind Felder definiert, lehnt Ikiform jedes unbekannte Input mit unknown_field ab. Füge ein Feld also erst der Definition hinzu, bevor du der Seite ein Input hinzufügst. Namen, die mit _ beginnen, wie _redirect, sind Anweisungen an Ikiform und zählen nie als Felder.
Einwilligung festhalten
Ein Newsletter ist E-Mail, um die jemand gebeten hat. Die Checkbox macht diese Bitte ausdrücklich, und die gespeicherte Einsendung hält sie fest. Das speichert Ikiform für jede angenommene Anmeldung:
{
"email": "ada@example.com",
"first_name": "Ada",
"consent": true,
"source": "footer"
}Neben den Feldern hat jede Einsendung ihren Zeitstempel created_at, Origin und Referrer der Seite, das Land und den User-Agent des Browsers. Zusammen zeigen sie, wann und wo die Person die Box angehakt hat. Zwei Gewohnheiten machen diesen Nachweis später nützlich:
- Formuliere das Label als Versprechen. "Schick mir den Newsletter per E-Mail. Ich kann mich jederzeit abmelden." sagt, was die Person bekommt. Wenn du auch Angebote verschickst, sag das im Label.
- Notiere das Datum, wenn sich der Wortlaut ändert. Ein gespeichertes
truesagt, dass die Person zugestimmt hat, nicht zu welchem Text. Führe ein kurzes Änderungsprotokoll des Labels mit Datum, damit sich jede Anmeldung dem Wortlaut zuordnen lässt, den sie gesehen hat.
Ikiform speichert den Nachweis; welche Regeln dort gelten, wo du und deine Leser leben, kann es dir nicht sagen. Kläre das mit jemandem, der sich damit auskennt.
Bestätigungs-E-Mails kommen aus deinem E-Mail-Tool
Ikiform schreibt 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. Sende diese aus deinem E-Mail-Tool, ausgelöst durch einen Webhook. Diese Trennung ist Absicht. Das Tool, das deinen Newsletter versendet, verfolgt bereits, wer bestätigt hat, wer sich abgemeldet hat und welche Adressen bouncen. Würde ein zweites System Bestätigungen senden, liefen diese Daten auseinander.
Das Relay trägt den Leser deshalb in einem Zustand ein, in dem das E-Mail-Tool seine eigene Bestätigung sendet, und das Tool setzt ihn auf aktiv, sobald er klickt. Jedes Tool schaltet das anders ein: über eine Formulareinstellung, einen Schalter im Konto oder ein Flag im API-Request. Die Anleitung zu Double-Opt-in behandelt alle drei und was passiert, wenn sich ein bekannter Abonnent erneut anmeldet.
Das Relay
Ein Relay ist eine kurze Funktion, die den Webhook von Ikiform empfängt, seine Signatur prüft und die API deines E-Mail-Tools aufruft. Es braucht eine öffentliche HTTPS-URL und ein Secret pro Seite. Dieses hier ist ein Next.js-Route-Handler; derselbe Code läuft in jeder Runtime mit fetch.
import { verifyWebhook, WebhookVerificationError } from "@ikiform/sdk/webhooks";
import { addSubscriber } from "@/lib/email-tool";
export async function POST(request: Request) {
const payload = await request.text();
let event;
try {
event = await verifyWebhook({ payload, headers: request.headers, secret: process.env.IKIFORM_WEBHOOK_SECRET! });
} catch (error) {
if (error instanceof WebhookVerificationError) return new Response(error.reason, { status: 400 });
throw error;
}
if (event.type !== "submission.created") return new Response(null, { status: 204 });
const fields = event.data.fields;
if (fields.consent !== true) return new Response("No consent, skipped", { status: 200 });
const answer = await addSubscriber({
email: String(fields.email),
firstName: typeof fields.first_name === "string" ? fields.first_name : undefined,
source: typeof fields.source === "string" ? fields.source : "unknown",
referrer: typeof event.data.meta.referrer === "string" ? event.data.meta.referrer : undefined,
});
if (answer.status === 429) {
return new Response("Email tool rate limit", {
status: 503,
headers: { "retry-after": answer.headers.get("retry-after") ?? "60" },
});
}
if (!answer.ok) return new Response(`Email tool answered ${answer.status}`, { status: 502 });
return new Response(null, { status: 204 });
}verifyWebhookprüft die HMAC-Signatur und den Zeitstempel. Ohne das kann jeder, der die URL findet, Adressen zu deiner Liste hinzufügen.endpoint.test-Events von Send test im Dashboard werden mit204beantwortet und übersprungen.- Die Einwilligungsprüfung ist ein zweites Schloss hinter der Formulardefinition.
addSubscriberist der einzige Teil, der vom Tool abhängt. Den Inhalt für jedes Tool findest du auf den Seiten zu Kit, MailerLite und beehiiv.
Was das Relay antwortet, entscheidet, was Ikiform als Nächstes tut:
- Das E-Mail-Tool legt den Abonnenten an oder aktualisiert ihn: Das Relay antwortet 204, und die Zustellung gilt als erfolgreich.
- Das E-Mail-Tool ist rate-limitiert: Das Relay antwortet 503 mit Retry-After, und Ikiform wartet so lange bis zum nächsten Versuch.
- Das E-Mail-Tool lehnt den Request ab oder ist down: Das Relay antwortet 502, und Ikiform versucht es nach seinem Zeitplan mit derselben webhook-id erneut.
Trag die URL des Relays unter Webhooks im Dashboard ein, wähle das Newsletter-Formular und kopiere das whsec_-Secret in IKIFORM_WEBHOOK_SECRET. Ein Workspace hat bis zu 3 Endpunkte. Die URL muss öffentliches HTTPS auf Port 443 sein; Hostnamen unter workers.dev und pages.dev werden abgelehnt, gib einem Worker also eine eigene Domain. Retries, Payloads und das Signaturschema stehen in der Webhook-Doku, und Formulareinsendungen an deinen eigenen Server zeigt eine Variante, die jede Zustellung nur einmal speichert.
Bots und List Bombing fernhalten
Bot-Anmeldungen schaden einem Newsletter mehr als den meisten Formularen. Eine echte Adresse, die ein Bot angemeldet hat, bekommt E-Mails, die sie nie wollte, und Beschwerden dieser Menschen schaden der Domain, von der du sendest. Manche Bots melden die Adresse eines Opfers auf einen Schlag bei Hunderten Formularen an, um sein Postfach zu fluten, und jedes Formular mit Bestätigungs-E-Mail macht dabei mit.
| Abwehr | Was sie stoppt | Einrichtung |
|---|---|---|
| Honeypot | Bots, die jedes Eingabefeld ausfüllen | Das versteckte Eingabefeld _gotcha |
| Erforderliche Einwilligung | Skripte, die nur eine E-Mail senden | Die consent-Checkbox mit required: true in der Definition |
| Spam-Bewertung | Wegwerfadressen (+2), identische Wiederholungen (+3), Links und Spam-Phrasen. Ab 5 wird eine Anmeldung als Spam gespeichert | Keine |
| Rate-Limits | Ein Besucher, der in einer Schleife sendet | Standardmäßig aktiv: Standard, 5 pro Minute und Besucher |
| Menschlichkeitsprüfung | Bots, die einen echten Browser ausführen | Setze Spam check (Spamprüfung) auf Ikiform, um Cloudflare Turnstile ohne eigene Keys zu nutzen |
| Erlaubte Domains | Der Endpunkt deines Formulars, auf fremde Sites kopiert | Trag jede Domain ein, auf der das Formular erscheint |
Spam bekommt dieselbe Erfolgsantwort wie eine echte Anmeldung und sendet keinen Webhook, erreicht also nie dein E-Mail-Tool und löst nie eine Bestätigungs-E-Mail aus. Landet ein echter Leser im Spam, markiere ihn als kein Spam, und erst dann geht sein Webhook raus. Die Spam-Doku erklärt jede Prüfung und ihren Score.
Ein Formular auf vielen Seiten
Ein Endpunkt kann im Footer jeder Seite, am Ende jedes Beitrags und auf einer Seite /newsletter sitzen. Gib jeder Platzierung einen eigenen source-Wert, um zu sehen, welche Anmeldungen bringt:
footer: auf jeder Seite zu sehen. Beschränke es auf E-Mail und Checkbox.post: Leser, die einen Artikel zu Ende gelesen haben, oft die beste Conversion.newsletter-page: wohin Social-Media-Profile verlinken. Platz für eine Beispielausgabe.
Jede Einsendung speichert auch den Referrer, also genau die Seite, auf der der Leser war. source sagt dir die Platzierung, der Referrer den Artikel. Trag jede Domain, die das Formular zeigt, unter Allowed domains ein, bis zu 20 pro Formular; Subdomains einer eingetragenen Domain sind automatisch erlaubt.
Derselbe Leser zweimal
Ikiform führt wiederholte Einsendungen nicht anhand der E-Mail zusammen und lehnt sie auch nicht ab. Eine identische Wiederholung innerhalb von 24 Stunden erhöht nur den Spam-Score. Entferne Duplikate dort, wo die Liste liegt, oder beim Export. Bei einem Newsletter ist das selten ein Problem: Kit, MailerLite und beehiiv identifizieren Abonnenten jeweils über die E-Mail, eine Wiederholung aktualisiert also einen Kontakt, statt einen zweiten anzulegen.
Später das E-Mail-Tool wechseln
Hier zahlt es sich aus, dass dir das Formular gehört. Der Wechsel von einem E-Mail-Tool zum anderen heißt normalerweise, jedes Embed auf der Site zu ersetzen. Hier weiß das Formular nicht, welches Tool dahinter sitzt:
- Mit einem Embed trägt jede Seite Code, der direkt an das alte Tool sendet. Ein Wechsel heißt also, jede Kopie zu ersetzen.
- Mit deinem eigenen Formular sendet jede Seite weiter an Ikiform, und Ikiform sendet weiter denselben Webhook.
- Nur die Funktion addSubscriber des Relays ändert sich und ruft die API des neuen Tools auf.
Übertrage bestehende Abonnenten mit dem Export des alten Tools, denn nur dieses weiß, wer bestätigt hat und wer gegangen ist. Der CSV-Export von Ikiform mit bis zu 100.000 Zeilen pro Datei dient zum Prüfen der Zahlen, nicht zum Importieren.
Wähle deinen Fall
Das Formular oben ist überall dasselbe. Diese Seiten behandeln Bestätigungs-E-Mails, Sites ohne Server und die genauen API-Aufrufe für drei E-Mail-Tools.
- Double-Opt-in-VerfahrenIkiform sendet keine Bestätigungs-E-Mails, also läuft Double-Opt-in in deinem E-Mail-Tool: Ein Relay legt jede Anmeldung als unbestätigt an, und das Tool sendet den Link.
- Statische SiteFü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.
- Kit (ConvertKit)Verbinde dein eigenes Newsletter-Formular mit Kit, über ein Relay, das den Abonnenten anlegt, ihn für die Incentive-E-Mail einem Kit-Formular hinzufügt und die Platzierung taggt.
- MailerLiteVerbinde dein eigenes Anmeldeformular mit MailerLite: Ein Relay fügt neue Leser für das Double Opt-in unbestätigt hinzu und bittet einen Abonnenten nie zweimal um Bestätigung.
- beehiivVerbinde dein eigenes Anmeldeformular mit beehiiv: Ein Relay legt jedes Abo mit aktivem Double Opt-in an, mit der Platzierung als UTM-Quelle und der Seite als Referrer.
- Newsletter-Formular mit Next.jsEin Newsletter-Formular im Footer mit Next.js: eine Server Action mit Einwilligungsprüfung und Turnstile, die an Ikiform sendet, plus ein Route Handler, der Leser zu Kit hinzufügt.
- Newsletter-Formular mit AstroEin Newsletter-Formular mit Astro, das auf vorgerenderten Seiten bleibt: Eine Astro Action prüft die Einwilligung und sendet an Ikiform, und ein Endpunkt fügt Leser zu MailerLite hinzu.
- Newsletter-Formular mit TanStack StartEin Newsletter-Formular mit TanStack Start: eine Server-Funktion für Anmeldungen auf der Seite, eine Server-Route als Fallback ohne JavaScript und echte Anmeldungen, die an beehiiv gehen.
- Newsletter-Formular mit React RouterEin Newsletter-Formular im Footer mit React Router: useFetcher sendet an eine Route /newsletter, die auch ohne JavaScript funktioniert, und eine Resource Route beliefert MailerLite.
- Newsletter-Formular mit SvelteKitEin Newsletter-Formular mit SvelteKit 3 im Layout: eine Form Action mit Einwilligung und IP-Limit, use:enhance, das auf der Seite bleibt, und eine Webhook-Route für Kit.
- Newsletter-Formular mit NuxtEin Newsletter-Formular mit Nuxt, das mit oder ohne JavaScript funktioniert: Eine Server-Route mit Turnstile-Prüfung sendet an Ikiform, und eine Nitro-Route beliefert beehiiv.
Prüfen, ob es funktioniert
Geh das durch, bevor du das Formular auf jede Seite setzt. Ikiform hat keinen kostenlosen Tarif. Testen ist kostenlos: Ein Workspace ohne Tarif nimmt 50 Testeinsendungen im Monat von localhost an. Für den Livebetrieb wähle Pro ab 6 $/Monat für 500 Einsendungen oder zahle einmalig: 10.000 Einsendungen für 49 $ oder 50.000 Einsendungen für 149 $, mit Guthaben, das nie verfällt. Siehe Preise.
- 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. - Sende das Formular über die Entwicklertools des Browsers mit leerer Box ab, oder entferne kurz
required. Du bekommst422mitThis box must be checked, und nichts wird gespeichert. - Sende es richtig mit einer Adresse ab, die dir gehört. Du siehst die Zeile mit dem Hinweis aufs Postfach oder landest auf deiner Dankeseite.
- Öffne Submissions im Dashboard. Die neue Einsendung ist da, mit jedem Feld, das du gesendet hast.
- Öffne den Endpunkt unter Webhooks. Die Zustellung zeigt
succeededund die Antwort deines Relays. - Öffne dein E-Mail-Tool. Der Abonnent ist da, unbestätigt, wenn Double-Opt-in an ist, und die Bestätigungs-E-Mail liegt in deinem Postfach.
- 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. - Nichts angekommen? Stell den Statusfilter im Dashboard auf Spam. Spam bekommt dieselbe Erfolgsantwort wie eine echte Einsendung.
Fehlerbehebung
| Problem | Was zu tun ist |
|---|---|
422 validation_failed mit consent | Die Checkbox war nicht angehakt, oder der name ihres Inputs ist nicht consent. Eine serverseitige Variante muss true, "true" oder "on" senden; eine leere Box sendet nichts. |
422 validation_failed mit unknown_field | Die Seite sendet ein Input, das die Definition nicht aufführt, oft ein verstecktes source oder ein später hinzugefügtes Tracking-Input. Füge es der Definition hinzu oder entferne das Input. |
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. |
429 rate_limited | Ein Besucher hat mehr gesendet, als das Rate-Limit des Formulars erlaubt. Standard erlaubt 5 pro Minute und Besucher. Warte Retry-After ab oder wähle Relaxed oder Custom für Formulare, die von einer gemeinsamen IP aus senden. |
| Die Einsendung ist im Dashboard, aber keine E-Mail und kein Webhook kam an | Sie wurde als Spam gespeichert. Spam löst nichts aus. Öffne sie unter dem Filter Spam, um die Gründe zu sehen, und markiere sie als kein Spam, um ihre E-Mail und Webhooks zu senden. |
| Anmeldungen kommen in Ikiform an, aber nicht im E-Mail-Tool | Lies das Zustellungsprotokoll unter Webhooks. 400 heißt, das Secret des Relays ist falsch. 502 enthält den Status des E-Mail-Tools: meist ein falscher API-Key, eine falsche Listen-ID oder Formular-ID. |
| Der Abonnent ist im E-Mail-Tool, hat aber keine Bestätigungs-E-Mail bekommen | Double-Opt-in ist im Tool aus, oder das Relay hat den Abonnenten als bereits bestätigt angelegt. Unter Double-Opt-in findest du den Schalter für jedes Tool. |
| 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
Ja. Schreib das Formular in deinem eigenen HTML, sende es an ein Formular-Backend wie Ikiform und leite jede Anmeldung per Webhook über die API an Mailchimp oder ein anderes E-Mail-Tool weiter. Das Formular sieht dann aus wie der Rest deiner Site und ändert sich nicht, wenn das E-Mail-Tool wechselt.
Nein. Ikiform sammelt, validiert und speichert Anmeldungen und gibt sie per Webhook weiter. Es schreibt dem Abonnenten nie, Bestätigungs-E-Mail, jede Ausgabe und der Abmeldelink kommen also aus deinem E-Mail-Tool.
Mit jedem Tool, das eine API zum Anlegen von Abonnenten hat, oder jedem Tool, das ein Automatisierungsdienst wie Zapier, Make oder n8n unterstützt. Es gibt keine Ein-Klick-Integrationen; das Relay oder die Automatisierung stellt die Verbindung her. Für Kit, MailerLite und beehiiv gibt es in dieser Anleitung Schritt-für-Schritt-Seiten.
In den serverseitigen Umgebungsvariablen des Relays, nie in der Seite. Der API-Key eines E-Mail-Tools kann meist die ganze Liste lesen und ändern, und ein Key in Browser-JavaScript ist ein Key, den jeder kopieren kann.
Behalte den versteckten Honeypot _gotcha und die erforderliche Einwilligungs-Checkbox, trag deine Domains unter Allowed domains ein und setze die Spamprüfung auf Ikiform für eine Prüfung mit Cloudflare Turnstile. Als Spam gespeicherte Anmeldungen senden nie einen Webhook und lösen daher nie eine Bestätigungs-E-Mail aus.
Nein. Jeder angenommene Post wird gespeichert. Eine identische Wiederholung innerhalb von 24 Stunden erhöht den Spam-Score, ist für sich allein aber kein Spam, also dedupliziere per E-Mail-Adresse in deiner Liste, deiner Datenbank oder deinem CSV-Export.
Nein, es gibt keinen kostenlosen Tarif. Testen ist kostenlos: Du kannst 50 Testeinsendungen pro Monat von localhost senden, bevor du zahlst. Für den Livebetrieb startet Pro bei $6/Monat für 500 Einsendungen, oder du zahlst einmalig 10.000 Einsendungen für $49 oder 50.000 Einsendungen für $149, mit Guthaben, das nie verfällt.
Hängst du bei einem Schritt fest oder willst eine Anleitung für deinen Fall? Schreib an hello@ikiform.com.