Beispiele zum Versenden von Web-Formularen
In diesem Beispiel (ab PHP7.0 mit) kann der Besucher ein Formular ausfüllen.
Beim Absenden des Formulars wird das Script phpmailer.php aufgerufen und alle Formulardaten übergeben.
Das Script überprüft die Daten und zeigt entweder eine Webseite mit einem Fehler an oder sendet das Email und zeigt eine Webseite mit einer Erfolgsmeldung.
WICHTIG
Aus Sicherheitsgründen (Spam) darf die Emailadresse des/der Empfänger nicht einfach von einem öffentlichen Web-Formular übernommen werden.
Das Script muss vor Bots geschützt werden:
| Massnahme | Schützt gegen | Aufwand |
|---|---|---|
| Honeypot-Feld (unsichtbares Feld, das nur Bots ausfüllen) | einfache Bots | sehr gering |
| Zeitprüfung (Formular in weniger als 3 s abgeschickt = Bot) | automatisierte Bots | gering |
| CSRF-Token (Session) | direkte POST-Requests ohne Aufruf des Formulars | gering |
| Rate Limit pro IP | Massenversand | gering |
| Captcha | gezielte, bessere Bots | mittel |
Einfache Version OHNE SCHUTZ:
Dies nur als Funktionsbeispiel, ohne zusätzlichen Schutz darf dies so nicht verwendet werden.
Das Formular "kontakt.html"
<html>
<head>
<title>Mail Beispiel mit PHP</title>
</head>
<body>
<font face="Arial">
<b>Mail Beispiel mit PHP</b>
<form action="phpmailer.php" method="POST">
Name, Vorname: <input type="text" name="name_surname" />
<br />
Absender-Email: <input type="text" name="mailfrom" />
<br />
Betreff: <input type="text" name="subject" />
<br />
Text:<br /><textarea name="text" rows="20" cols="50"></textarea>
<br />
<input type="submit" value="E-Mail senden" />
</form>
</font>
</body>
</html>
Das Script "phpmailer.php"
<?php
echo 'Mail Beispiel mit PHP
';
$name = trim($_POST['name_surname'] ?? '');
$mailfrom = trim($_POST['mailfrom'] ?? '');
$subject = trim($_POST['subject'] ?? '');
$text = trim($_POST['text'] ?? '');
$errors = [];
if ($name === '') $errors[] = 'Geben Sie Ihren Namen und Vornamen an!';
if (!filter_var($mailfrom, FILTER_VALIDATE_EMAIL))
$errors[] = 'Geben Sie eine gültige E-Mail-Adresse an!';
if ($subject === '') $errors[] = 'Kein Betreff angegeben!';
if ($text === '') $errors[] = 'Leere E-Mails werden nicht versendet!';
// Schutz gegen Header Injection
foreach ([$name, $mailfrom, $subject] as $v) {
if (preg_match('/[\r\n]/', $v)) { $errors[] = 'Ungültige Eingabe!'; break; }
}
if ($errors) {
foreach ($errors as $e) echo htmlspecialchars($e) . '
';
exit;
}
$mailto = 'name@deine-domain.ch';
$headers = "From: Kontaktformular <name@deine-domain.ch>\r\n";
$headers .= "Reply-To: " . mb_encode_mimeheader($name) . " <$mailfrom>\r\n";
$headers .= "MIME-Version: 1.0\r\n";
$headers .= "Content-Type: text/plain; charset=UTF-8\r\n";
$headers .= "Content-Transfer-Encoding: 8bit\r\n";
$body = "Name: $name\r\nE-Mail: $mailfrom\r\n\r\n$text";
if (mail($mailto, mb_encode_mimeheader($subject, 'UTF-8'), $body, $headers,
'-fnoreply@wunschname.ch')) {
echo 'E-Mail wurde erfolgreich versendet!';
} else {
echo 'Fehler beim Versand der E-Mail.';
}
Version MIT SCHUTZ:
Dies kann mit Cloudflare Turnstile oder Friendly Captcha benutzt werden.
Legen Sie im Cloudflare-Dashboard unter Turnstile ein Widget für Ihre Domain an. Die Domain muss dafür nicht über Cloudflare laufen. Tragen Sie dann den Site Key und den Secret Key ein.
Das Formular "kontakt.php"
<?php
session_start();
$_SESSION['csrf'] = bin2hex(random_bytes(32));
$_SESSION['form_time'] = time();
?>
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<title>Kontaktformular</title>
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
<style>.hp-field { position: absolute; left: -9999px; }</style>
</head>
<body style="font-family: Arial, sans-serif">
<b>Kontaktformular</b>
<form action="senden.php" method="POST">
<input type="hidden" name="csrf" value="<?= $_SESSION['csrf'] ?>">
<!-- Honeypot: für Menschen unsichtbar -->
<div class="hp-field" aria-hidden="true">
<label>Website <input type="text" name="website" tabindex="-1" autocomplete="off"></label>
</div>
Name, Vorname: <input type="text" name="name_surname" maxlength="100" required><br>
Absender-E-Mail: <input type="email" name="mailfrom" maxlength="254" required><br>
Betreff: <input type="text" name="subject" maxlength="150" required><br>
Text:<br><textarea name="text" rows="20" cols="50" maxlength="5000" required></textarea><br>
<div class="cf-turnstile" data-sitekey="IHR_SITE_KEY"></div>
<input type="submit" value="E-Mail senden">
</form>
</body>
</html>
Das Script "senden.php"
<?php
session_start();
const TURNSTILE_SECRET = 'IHR_SECRET_KEY';
const MAIL_TO = 'wunschname@wunschname.ch';
const MAIL_FROM = 'noreply@wunschname.ch';
const MIN_SECONDS = 3; // schneller = Bot
const MAX_PER_HOUR = 5; // Mails pro IP pro Stunde
function abbruch($msg) {
http_response_code(400);
exit(htmlspecialchars($msg));
}
if ($_SERVER['REQUEST_METHOD'] !== 'POST') abbruch('Ungültiger Aufruf.');
// 1) CSRF-Token (einmalig verwendbar)
$token = $_POST['csrf'] ?? '';
if (empty($_SESSION['csrf']) || !hash_equals($_SESSION['csrf'], $token)) {
abbruch('Sitzung abgelaufen. Bitte Formular neu laden.');
}
unset($_SESSION['csrf']);
// 2) Honeypot: Bots erhalten eine scheinbare Erfolgsmeldung
if (!empty($_POST['website'])) {
exit('E-Mail wurde erfolgreich versendet!');
}
// 3) Zeitprüfung
if (time() - ($_SESSION['form_time'] ?? 0) < MIN_SECONDS) {
abbruch('Formular zu schnell abgeschickt.');
}
// 4) Rate Limit pro IP (dateibasiert)
$ip = $_SERVER['REMOTE_ADDR'];
$file = sys_get_temp_dir() . '/mailform_' . md5($ip);
$now = time();
$hits = is_file($file) ? json_decode(file_get_contents($file), true) : [];
$hits = array_filter((array)$hits, function ($t) use ($now) { return $t > $now - 3600; });
if (count($hits) >= MAX_PER_HOUR) abbruch('Zu viele Anfragen. Bitte später erneut versuchen.');
// 5) Captcha serverseitig prüfen
$ch = curl_init('https://challenges.cloudflare.com/turnstile/v0/siteverify');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 10,
CURLOPT_POSTFIELDS => http_build_query([
'secret' => TURNSTILE_SECRET,
'response' => $_POST['cf-turnstile-response'] ?? '',
'remoteip' => $ip,
]),
]);
$result = json_decode((string)curl_exec($ch), true);
curl_close($ch);
if (empty($result['success'])) abbruch('Captcha-Prüfung fehlgeschlagen.');
// Eingaben prüfen
$name = trim($_POST['name_surname'] ?? '');
$mailfrom = trim($_POST['mailfrom'] ?? '');
$subject = trim($_POST['subject'] ?? '');
$text = trim($_POST['text'] ?? '');
$errors = [];
if ($name === '' || mb_strlen($name) > 100) $errors[] = 'Bitte Namen angeben.';
if (!filter_var($mailfrom, FILTER_VALIDATE_EMAIL)) $errors[] = 'Bitte gültige E-Mail-Adresse angeben.';
if ($subject === '' || mb_strlen($subject) > 150) $errors[] = 'Bitte Betreff angeben.';
if ($text === '' || mb_strlen($text) > 5000) $errors[] = 'Leere oder zu lange E-Mails werden nicht versendet.';
foreach ([$name, $mailfrom, $subject] as $v) {
if (preg_match('/[\r\n]/', $v)) { $errors[] = 'Ungültige Eingabe.'; break; }
}
if ($errors) abbruch(implode(' ', $errors));
// Versand
$headers = "From: Kontaktformular <" . MAIL_FROM . ">\r\n";
$headers .= "Reply-To: " . mb_encode_mimeheader($name) . " <$mailfrom>\r\n";
$headers .= "MIME-Version: 1.0\r\n";
$headers .= "Content-Type: text/plain; charset=UTF-8\r\n";
$headers .= "Content-Transfer-Encoding: 8bit\r\n";
$body = "Name: $name\r\nE-Mail: $mailfrom\r\nIP: $ip\r\n\r\n$text";
if (mail(MAIL_TO, mb_encode_mimeheader('[Kontakt] ' . $subject, 'UTF-8'),
$body, $headers, '-f' . MAIL_FROM)) {
$hits[] = $now;
file_put_contents($file, json_encode(array_values($hits)), LOCK_EX);
echo 'E-Mail wurde erfolgreich versendet!';
} else {
abbruch('Fehler beim Versand der E-Mail.');
}
Turnstile einrichten
- Cloudflare-Konto erstellen oder anmelden: dash.cloudflare.com. Ein kostenloses Konto genügt. Die Domain muss nicht über Cloudflare laufen – weder DNS noch Proxy.
- Im Dashboard links Turnstile öffnen, direkt erreichbar unter dash.cloudflare.com/?to=/:account/turnstile.
- Add widget anklicken und ausfüllen:
- Widget name: frei wählbar, z. B.
Kontaktformular dein-name.ch - Hostnames:
dein-name.chundwww.dein-name.ch– nur auf diesen Hosts funktioniert das Widget. Für lokale Tests zusätzlichlocalhosteintragen. - Widget mode:
Managedist die Empfehlung. Die meisten Besucher sehen nur kurz eine Häkchen-Box, ohne Rätsel.Invisibleläuft ganz ohne sichtbares Element,Non-Interactivezeigt eine Box ohne Klick. - Pre-clearance brauchen man nicht, das ist für Single-Page-Apps.
- Widget name: frei wählbar, z. B.
- Create klicken. Danach erscheinen Site Key und Secret Key.
- Site Key → in
kontakt.phpbeidata-sitekey="IHR_SITE_KEY" - Secret Key → in
senden.phpbeiTURNSTILE_SECRET. Er gehört nie in den HTML-Code, da er sonst öffentlich sichtbar wäre.
- Site Key → in
- Beide Schlüssel lassen sich im Dashboard jederzeit wieder einsehen und bei Bedarf rotieren (Settings beim Widget).
Kosten und Grenzen: Turnstile ist im Standard-Plan kostenlos, ohne veröffentlichtes Anfragelimit. Die Analytics reichen nur 24 Stunden zurück. Erweiterte Funktionen gibt es nur im Enterprise-Vertrag. Pro Account sollten man ein Widget pro Domain oder Formular anlegen. In der Community wurde gelegentlich von einer Obergrenze bei der Anzahl Widgets berichtet, bei einzelnen Formularen ist das aber kein Thema.
Testschlüssel für die Entwicklung, ohne eigenes Widget – damit lässt sich das Script prüfen, bevor die Domain freigeschaltet ist:
| Zweck | Site Key | Secret Key |
|---|---|---|
| immer erfolgreich | 1x00000000000000000000AA |
1x0000000000000000000000000000000AA |
| immer abgelehnt | 2x00000000000000000000AB |
2x0000000000000000000000000000000AA |
Wichtig zum Testen: Prüfen Sie, ob das Formular ohne gültiges Captcha wirklich abgewiesen wird. Rufen Sie senden.php dazu einmal direkt im Browser auf. Es muss die Meldung «Ungültiger Aufruf.» erscheinen. Der Server muss ausserdem challenges.cloudflare.com über HTTPS erreichen können, sonst schlägt die Prüfung mit curl fehl. Falls auf dem Server eine ausgehende Firewall aktiv ist, muss diese Verbindung offen sein.
Datenschutz: Turnstile übermittelt IP-Adresse und Browserdaten an Cloudflare (US-Anbieter mit EU-Rechenzentren). Das gehört in die Datenschutzerklärung. Cloudflare setzt dabei keine Tracking-Cookies für Werbung.
Alternative: Friendly Captcha aus Deutschland, mit Hosting in der EU. Die Einbindung ist analog, das Prüf-Script ist https://cdn.jsdelivr.net/npm/friendly-challenge/widget.module.min.js, das Feld heisst frc-captcha-solution und der Verify-Endpoint ist https://api.friendlycaptcha.com/api/v1/siteverify. Dort gibt es einen kostenlosen Plan für kleine Websites, grössere Kontingente sind kostenpflichtig