Kontaktmails und Bestätigungen zuverlässig organisieren
Jede Bestätigung an den tatsächlich gespeicherten Eingang und jede Versandentscheidung an beobachtete Zustände binden.

Formulareingang und Mailversand sind verschiedene Zustände
Eine Kontaktanfrage soll nicht verschwinden, nur weil ein Mailserver vorübergehend nicht erreichbar ist. Gleichzeitig darf eine sichtbare Bestätigung keinen Versand behaupten, der noch gar nicht stattgefunden hat. Deshalb werden Eingang, gespeicherter Versandauftrag und tatsächliche SMTP-Rückmeldung getrennt behandelt. Diese Unterscheidung macht einen Kontaktablauf nachvollziehbar und hilft bei der späteren Bearbeitung einer fehlenden Nachricht.
Unser eigener fiktiver PHP-Baustein speichert eine Anfrage zusammen mit ihren Mailaufträgen in SQLite. Ein getrennter Worker übernimmt den Übermittlungsversuch. Für die Nachrichtenerzeugung verwenden wir PHPMailer. Der tatsächlich ausgeführte Versandtest endet ausschließlich bei einem eigenen lokalen SMTP-Testempfänger mit nicht real zustellbaren Beispieladressen. Es wird keine Kontaktmail an einen Kunden, Mitarbeiter oder fremden Empfänger verschickt.
Der Versuch umfasst 56 Bedingungen, zwölf echte lokale SMTP-Verbindungen und sieben dort vollständig erfasste Nachrichtennutzlasten. Er prüft Annahme, zeitweilige Ablehnung, endgültige Ablehnung und einen unklaren Ausgang nach einem Verbindungsverlust. Eine echte Zustellung in ein Postfach oder das Lesen einer Nachricht wird damit nicht nachgewiesen. Genau diese Grenzen bilden den Ausgangspunkt für die folgenden Zustände und Rückmeldungen.
Eine Eingangsbestätigung muss das tatsächlich Erreichte benennen
Nach dem erfolgreichen Speichern darf die Oberfläche erklären, dass die Anfrage erfasst wurde. Sie darf daraus nicht automatisch machen, dass die zuständige Person die Mail erhalten oder den Inhalt bereits bearbeitet hat. Diese späteren Aussagen würden andere Nachweise benötigen. Der Rückgabewert unseres Annahmekerns heißt deshalb recorded. Er entsteht erst nach dem gemeinsamen Datenbankabschluss und nicht bereits nach dem Lesen der Formularfelder.
Die Datenbank erhält die Anfrage und die dazugehörigen Mailaufträge gemeinsam. Misslingt diese Speicherung, entsteht kein erfolgreicher Eingangszustand. Ein realer HTTP-Aufrufer muss einen solchen Fehler passend behandeln und eingegebene Inhalte für die Korrektur sinnvoll erhalten. Der gezeigte Baustein liefert den technischen Kern. Er enthält keine vollständige Besucheroberfläche und keine Anmeldung oder Formularschutzentscheidung für eine produktive Webseite.
Die fachliche Bearbeitung sollte zusätzlich einen verlässlichen Zugang zu den gespeicherten Anfragen besitzen. Ein nur als Transport gedachte Mail darf nicht die einzige Beschreibung des Eingangs sein. Der kleine Versuch implementiert keine solche Verwaltungsoberfläche. Er zeigt aber den gespeicherten Zustand, auf dem eine tatsächlich entwickelte Bearbeitung aufbauen kann. Die bloße Existenz einer Tabellenzeile wird nicht als bereits fertig organisierter Kundendienst ausgegeben.
Absender und Antwortadresse bewusst trennen
Die Nachricht stammt im Beispiel immer von einer festgelegten eigenen Absenderadresse. Die von der anfragenden Person genannte Adresse steht bei der internen Nachricht im Reply-To-Feld. Damit wird der Besucher nicht eigenmächtig zum technischen Absender der Anwendung gemacht. Die feste Absenderidentität ist Teil der konfigurierten Integration und darf nicht durch ein beliebiges Formularfeld ersetzt werden.
Für das Kontaktformular akzeptiert der kleine Vertrag ausschließlich passende ASCII-Mailadressen. Steuerzeichen und Leerraum in der Adresse werden zurückgewiesen und zusätzlich wird der PHP-Adressfilter verwendet. Diese Entscheidung schließt internationalisierte Adressen bewusst aus und ist keine vollständige universelle Adressbibliothek. Ein reales Produkt mit anderen Anforderungen muss deren Unterstützung einschließlich des tatsächlichen Mailtransports ausdrücklich entwickeln.
Eine syntaktisch passende Adresse beweist weder die Existenz des Postfachs noch die Berechtigung der eingebenden Person. Die Beispieladressen besitzen absichtlich ungültige Demonstrationsdomänen. Der lokale Testempfänger nimmt sie nur für den Versuch an. Für eine Veröffentlichung gehören die tatsächliche Absenderkonfiguration, ein geeigneter Transport und die beobachteten Zustellergebnisse dazu. Die vorliegende Prüfung ändert keine DNS-Einträge und verbindet keinen produktiven Mailzugang.
Freie Nachrichtentexte bleiben Inhalte statt Header
Der Betreff wird durch die Anwendung vorgegeben und nicht direkt aus einem frei eingegebenen Feld zusammengesetzt. Name und Nachricht erscheinen im Inhalt. PHPMailer erzeugt die MIME-Nachricht mit einer Klartext- und einer HTML-Alternative. Für die HTML-Variante werden eingegebene Zeichen passend maskiert. Der ausgeführte Gegenversuch enthält ein script-ähnliches Textelement, das im HTML als Text sichtbar bleibt.
Zeilenumbrüche in der Antwortadresse könnten eine andere Bedeutung haben als Zeilenumbrüche im eigentlichen Nachrichtentext. Der Adressvertrag weist solche Eingaben deshalb zurück. Im Text werden Zeilen dagegen als Inhalt behandelt. Der SMTP-Test enthält zusätzlich eine mit einem Punkt beginnende Zeile. Sie kommt nach der tatsächlichen Übermittlung unverändert als Inhalt an. Die Bibliothek übernimmt dabei die für SMTP nötige Übertragungskodierung.
Diese konkreten Prüfungen sind kein allgemeiner Nachweis für jede spätere Mailvorlage. Ein zusätzlicher unmaskierter Link oder eine weitere direkt übernommene Headerzeile könnte neue Probleme erzeugen. Vorlagenänderungen werden deshalb mit den tatsächlichen eingehenden Zeichen und den erzeugten MIME-Teilen betrachtet. Ein optisch freundliches Maildesign ersetzt weder die Trennung der Kontexte noch eine verständliche lesbare Klartextalternative.
Eine angeforderte Bestätigung als eigenen Auftrag anlegen
Standardmäßig erzeugt der Annahmekern nur den Auftrag für die zuständige interne Adresse. Eine zweite Nachricht entsteht ausschließlich bei der ausdrücklich gesetzten booleschen Bestätigungsoption. Der Code behandelt eine Zeichenfolge wie yes nicht beiläufig als dieselbe Entscheidung. Die Tests prüfen den Unterschied. Eine allgemeine Kontaktanfrage wird dadurch nicht automatisch um weitere beliebige Kommunikationszwecke erweitert.
Die Bestätigung hat einen eigenen Empfänger, eine eigene Nachrichtenkennung und eine eigene feste Vorlage. Sie erklärt nur, dass die Anfrage erfasst wurde. Sie behauptet keine bereits erfolgte fachliche Bearbeitung und wiederholt nicht ungeprüft den gesamten freien Formulartext. Die interne Nachricht und die Bestätigung können unterschiedliche Transportergebnisse besitzen. Deshalb werden ihre Aufträge getrennt gespeichert und nicht als eine einzige ununterscheidbare Erfolgsmeldung behandelt.
Eine produktive Bestätigungsfunktion braucht zusätzlich Schutz gegen missbräuchlich eingetragene fremde Adressen und übermäßige Anfragen. Eine sichtbare Option allein löst diese Aufgabe nicht. Ebenso ist eine gewünschte Eingangsbestätigung keine pauschale Anmeldung für beliebige spätere Werbung. Unser technischer Versuch verschickt ausschließlich fiktive Inhalte an seine lokale Senke und bewertet keine rechtliche Freigabe eines realen Kommunikationskonzepts.
Die ausgehende Nachricht vor dem Versand festhalten
Jeder Auftrag erhält beim Anlegen seine eigene feste Message-ID. Die vorbereitete MIME-Nachricht wird zusammen mit dem Auftrag gespeichert. Ein späterer Wiederholungsversuch verwendet dadurch dieselbe Nachricht und nicht jedes Mal einen neu erzeugten vermeintlichen Erstversand. Der tatsächliche SMTP-Versuch nach einer vorübergehenden Ablehnung bestätigt die unverändert gespeicherte Message-ID im erfassten Nachrichtenkopf.
Die Kennung hilft bei der Zuordnung, ist aber keine allgemeine Garantie gegen doppelte Zustellung. Ein Mailserver kann eine Nachricht bereits angenommen haben, während die Bestätigung auf dem Rückweg verloren geht. Ein identischer Message-ID-Wert zwingt nicht jedes Postfach dazu, jede Wiederholung automatisch zu verwerfen. Deshalb braucht der Worker einen ausdrücklich unklaren Zustand und darf nicht jeden Verbindungsfehler blind in eine erneute Übermittlung übersetzen.
Die gespeicherte MIME-Nachricht enthält die eigentlichen Anfrageinhalte. Ihre Ablage muss deshalb zum Schutzbedarf dieser Inhalte passen. Sie gehört nicht in ein öffentliches Verzeichnis oder ein allgemeines Diagnoseprotokoll. Welche Aufbewahrung und welche Bearbeitungsrechte angemessen sind, wird für die reale Anwendung festgelegt. Der Beispielbestand enthält nur eigene künstliche Texte und keine produktiven Kontaktinformationen.
Den vollständigen PHP-Annahmekern und Worker lesen
Der folgende Code verwendet PDO mit SQLite sowie PHPMailer für die vorbereiteten Nachrichten. Die Bibliothek muss durch die Anwendung vor der Verwendung geladen werden. Im ausgeführten Versuch war dies PHPMailer 7.2.0. Eine reale Einbindung kann dafür beispielsweise ihre geregelte Paketverwaltung verwenden. Der Baustein verändert keinen vorhandenen produktiven Mailweg und kopiert keine Zugangsdaten aus einer anderen Anwendung.
Der Worker reserviert einen fälligen Auftrag innerhalb einer unmittelbaren Datenbanktransaktion. Erst danach arbeitet der Transport außerhalb dieser Transaktion. Zwei konkurrierende Worker können dadurch nicht denselben noch ausstehenden Auftrag gemeinsam beanspruchen. Die Prüfung startet dafür tatsächlich zwei getrennte PHP-Prozesse. Genau einer reserviert den Auftrag und es entsteht genau eine lokale SMTP-Verbindung für diesen Fall.
<?php
declare(strict_types=1);
use PHPMailer\PHPMailer\PHPMailer;
function contactDatabase(string $path): PDO {
$db=new PDO('sqlite:'.$path,null,null,[PDO::ATTR_ERRMODE=>PDO::ERRMODE_EXCEPTION]);
$db->exec('PRAGMA foreign_keys=ON');$db->exec('PRAGMA busy_timeout=5000');
$db->exec('CREATE TABLE IF NOT EXISTS submissions(id TEXT PRIMARY KEY,name TEXT NOT NULL,email TEXT NOT NULL,body TEXT NOT NULL,created_at INTEGER NOT NULL)');
$db->exec("CREATE TABLE IF NOT EXISTS mail_jobs(id TEXT PRIMARY KEY,submission_id TEXT NOT NULL REFERENCES submissions(id),kind TEXT NOT NULL,recipient TEXT NOT NULL,state TEXT NOT NULL,attempts INTEGER NOT NULL DEFAULT 0,next_at INTEGER NOT NULL,message_id TEXT NOT NULL,eml TEXT NOT NULL,error_code INTEGER,UNIQUE(submission_id,kind))");
return $db;
}
function contactMessage(string $id,string $submissionId,string $kind,array $data,int $now): array {
$mail=new PHPMailer(true);$mail->CharSet='UTF-8';$mail->Encoding='quoted-printable';
$mail->setFrom('noreply@demo.invalid','Kontaktbeispiel');
$recipient=$kind==='operator'?'anfragen@demo.invalid':$data['email'];
$mail->addAddress($recipient);
$mail->addReplyTo($kind==='operator'?$data['email']:'anfragen@demo.invalid');
$mail->Subject=$kind==='operator'?'Neue Kontaktanfrage':'Ihre Kontaktanfrage wurde erfasst';
$mail->MessageID='<'.$id.'@demo.invalid>';
$mail->MessageDate=gmdate('D, d M Y H:i:s +0000',$now);
$mail->isHTML(true);
$plain=$kind==='operator'?"Name: ".$data['name']."\nAntwortadresse: ".$data['email']."\n\n".$data['body']
:"Ihre Anfrage wurde erfasst.\nVorgang: ".$submissionId."\nDies ist keine Zusage einer bereits erfolgten Bearbeitung.";
$mail->AltBody=$plain;
$mail->Body='<h1>'.htmlspecialchars($mail->Subject,ENT_QUOTES|ENT_SUBSTITUTE,'UTF-8').'</h1><p>'.nl2br(htmlspecialchars($plain,ENT_QUOTES|ENT_SUBSTITUTE,'UTF-8'),false).'</p>';
$mail->preSend();
return ['recipient'=>$recipient,'message_id'=>$mail->MessageID,'eml'=>$mail->getSentMIMEMessage()];
}
function recordContact(PDO $db,array $input,int $now): array {
foreach(['name','email','body'] as $field)if(!isset($input[$field])||!is_string($input[$field]))throw new InvalidArgumentException('Ungültige Eingabe.');
$data=['name'=>trim($input['name']),'email'=>trim($input['email']),'body'=>trim($input['body'])];
$confirmation=$input['confirmation']??false;
if(!is_bool($confirmation)||$now<0||strlen($data['name'])<1||strlen($data['name'])>240
||strlen($data['body'])<20||strlen($data['body'])>8000||strlen($data['email'])>254
||preg_match('/[\x00-\x20\x7f-\xff]/',$data['email'])||!filter_var($data['email'],FILTER_VALIDATE_EMAIL))
throw new InvalidArgumentException('Ungültige Kontaktangaben.');
foreach($data as $value)if(!preg_match('//u',$value))throw new InvalidArgumentException('Ungültiges UTF-8.');
$submissionId=bin2hex(random_bytes(16));$prepared=[];
foreach($confirmation?['operator','confirmation']:['operator'] as $kind) {
$id=bin2hex(random_bytes(16));$prepared[]=['id'=>$id,'kind'=>$kind]+contactMessage($id,$submissionId,$kind,$data,$now);
}
$db->exec('BEGIN IMMEDIATE');
try {
$db->prepare('INSERT INTO submissions VALUES(?,?,?,?,?)')->execute([$submissionId,$data['name'],$data['email'],$data['body'],$now]);
$insert=$db->prepare("INSERT INTO mail_jobs(id,submission_id,kind,recipient,state,next_at,message_id,eml) VALUES(?,?,?,?,'pending',?,?,?)");
foreach($prepared as $job)$insert->execute([$job['id'],$submissionId,$job['kind'],$job['recipient'],$now,$job['message_id'],$job['eml']]);
$db->exec('COMMIT');
} catch(Throwable $error){$db->exec('ROLLBACK');throw $error;}
return ['submission_id'=>$submissionId,'status'=>'recorded','mail_jobs'=>count($prepared)];
}
function runContactJob(PDO $db,callable $transport,int $now): ?array {
$db->exec('BEGIN IMMEDIATE');
try {
$query=$db->prepare("SELECT * FROM mail_jobs WHERE state='pending' AND next_at<=? ORDER BY rowid LIMIT 1");$query->execute([$now]);$job=$query->fetch(PDO::FETCH_ASSOC);
if(!$job){$db->exec('COMMIT');return null;}
$db->prepare("UPDATE mail_jobs SET state='sending',attempts=attempts+1 WHERE id=?")->execute([$job['id']]);$db->exec('COMMIT');
}catch(Throwable $error){$db->exec('ROLLBACK');throw $error;}
try{$result=$transport($job);}catch(Throwable $error){$result=['outcome'=>'uncertain','code'=>0];}
$outcome=$result['outcome']??'uncertain';$attempts=(int)$job['attempts']+1;
$state=match($outcome){'accepted'=>'accepted_by_smtp','failed'=>'failed','retryable'=>$attempts>=3?'failed':'pending',default=>'uncertain'};
$next=$state==='pending'?$now+60*$attempts:$now;
$code=is_int($result['code']??null)?$result['code']:0;
$db->prepare('UPDATE mail_jobs SET state=?,next_at=?,error_code=? WHERE id=?')->execute([$state,$next,$code,$job['id']]);
return ['job_id'=>$job['id'],'state'=>$state,'attempts'=>$attempts,'code'=>$code];
}
Eine SMTP-Annahme ist noch keine gelesene Mail
Nach der erfolgreichen SMTP-Antwort auf die übergebenen Nachrichtendaten setzt der Worker den Zustand accepted_by_smtp. Diese Formulierung ist bewusst genau. Sie beschreibt die Annahme durch die betrachtete Gegenstelle und nicht das Lesen durch einen Menschen. Auch später können Zustellprobleme oder andere Verarbeitungsschritte auftreten. Der Status darf deshalb nicht als abgeschlossene Bearbeitung der Kontaktanfrage verwendet werden.
Der lokale Testempfänger liefert die entsprechende positive Antwort und erfasst die tatsächliche Nachricht. Die Prüfung betrachtet Absender, Empfänger, Reply-To, Message-ID sowie beide Inhaltsalternativen. Daraus folgt keine Aussage über die Darstellung in jedem Mailprogramm oder die Filterentscheidung eines echten Anbieters. Solche Fragen werden mit passenden Konten und freigegebenen Integrationsprüfungen separat untersucht.
Für die Besucherkommunikation bleiben die Zustände getrennt. Die Webseite kann den gespeicherten Eingang bestätigen. Eine ausdrücklich gewünschte zusätzliche Mail kann später beim SMTP-Dienst angenommen werden. Ob die Person sie tatsächlich sieht, ist eine weitere Frage. Diese Trennung verhindert falsche Versprechen und hilft im Support, weil ein fehlender Postfacheintrag nicht automatisch eine verlorene Formularanfrage bedeutet.
Vorübergehende und endgültige Ablehnung unterschiedlich behandeln
Im kontrollierten Versuch weist der lokale Empfänger einen Empfänger zunächst mit einem vorübergehenden SMTP-Code zurück. Der Worker hält den Auftrag ausstehend und plant den nächsten Versuch. Vor dem vorgesehenen Zeitpunkt wird nichts übertragen. Danach funktioniert die Übermittlung mit derselben gespeicherten Nachricht. Der zusätzliche Versuch wird ausdrücklich gezählt und die feste Message-ID bleibt erhalten.
Eine endgültige Ablehnung führt dagegen zum Zustand failed. Es gibt keine automatische Endlosschleife. Für wiederholte vorübergehende Ablehnungen besitzt der kleine Vertrag ebenfalls eine Grenze von drei Versuchen. Die gewählten Warteabstände und diese Grenze sind Beispielregeln, keine allgemeine Vorschrift für alle Maildienste. Eine reale Integration richtet ihre Strategie nach dem tatsächlichen Transportvertrag und ihrem betrieblichen Bedarf aus.
Eine sichtbare Betriebsübersicht sollte solche Fälle auffindbar machen. Sie braucht nachvollziehbare Kennungen und begrenzte Fehlergründe, aber nicht unkontrollierte Kopien aller Inhalte in jeder Meldung. Der gespeicherte Eingang bleibt unabhängig vom Transportfehler vorhanden. Wie Verantwortliche daraus eine manuelle Bearbeitung starten, muss die reale Anwendung entwickeln. Der Versuch stellt keine bereits fertige Supportoberfläche oder automatische Eskalation dar.
Den unklaren Ausgang nach DATA nicht weginterpretieren
Ein besonders wichtiger Gegenversuch erfasst beim lokalen SMTP-Empfänger die vollständigen Nachrichtendaten und trennt anschließend die Verbindung vor der positiven Abschlussantwort. Der PHP-Transport kann nun nicht sicher behaupten, ob die Gegenstelle eine Annahme bestätigen wollte. Der Auftrag wird als uncertain geführt. Ein späterer gewöhnlicher Workeraufruf versendet ihn nicht automatisch erneut.
Diese Entscheidung vermeidet die falsche Gleichsetzung von fehlender Antwort und sicher fehlender Wirkung. Sie ist keine Behauptung, der Versuch kenne bereits den tatsächlichen Postfacheingang. Die lokale Senke belegt nur, dass der Inhalt auf ihrer Seite vorhanden war. Ein realer Betrieb benötigt für solche Fälle passende Prüfung, Kennungen und gegebenenfalls eine bewusst freigegebene weitere Behandlung.
Ein anderer Fehlerpunkt liegt nach einer positiven SMTP-Antwort, aber vor dem abschließenden Speichern des neuen Jobstatus. Der Versuch löst dort gezielt einen Datenbankfehler aus. Der Auftrag bleibt sending und wird ebenfalls nicht blind erneut übernommen. Auch hier braucht die reale Anwendung eine geregelte Klärung. Eine Zeitüberschreitung allein darf nicht aus einem möglicherweise bereits angenommenen Auftrag automatisch einen sicheren Neuversand machen.
Den Transport als eigene Integration behandeln
Die vollständige lokale Transportfunktion unten ist ausdrücklich ein Testadapter. Sie verbindet sich nur mit dem eigenen Loopback-Empfänger und verwendet keine Zugangsdaten. Eine produktive Anwendung muss stattdessen ihren tatsächlich konfigurierten SMTP- oder API-Transport mit den passenden Sicherheits- und Fehlerregeln verwenden. Das Beispiel ist keine Aufforderung, unverschlüsselten öffentlichen Mailverkehr mit beliebigen Servern aufzubauen.
PHPMailer übernimmt im Versuch sowohl die Nachrichtenerzeugung als auch die konkrete SMTP-Kommunikation. Die Rückmeldung wird unmittelbar am jeweiligen Protokollschritt ausgewertet. Ein Fehler vor der Datenübermittlung hat eine andere Unsicherheit als eine verlorene Abschlussantwort danach. Diese Zuordnung muss auch bei einem anderen Adapter erhalten bleiben. Eine pauschale Zeichenfolge Mail fehlgeschlagen wäre für die gezeigten Wiederholungsentscheidungen zu ungenau.
Für eine echte Abnahme gehören die tatsächliche Domäne, der berechtigte Absender, Transportverschlüsselung, Anbieterkonfiguration und beobachtete Rückmeldungen dazu. Dieser Beitrag liefert keine fertigen DNS-Einstellungen und keine Zustellquote. Er zeigt den eigenen kontrollierten Entscheidungsweg. Die Bibliotheksversion und der begrenzte Aufbau sind dokumentiert, damit der Nachweis nicht mit einer anders konfigurierten produktiven Mailstrecke verwechselt wird.
<?php
declare(strict_types=1);
use PHPMailer\PHPMailer\SMTP;
// Isolated SMTP fixture only. Real deployments need their actual TLS/auth transport.
function deliverToLoopback(array $job,int $port): array {
if($port<1||$port>65535)throw new InvalidArgumentException('Ungültiger Testport.');
$smtp=new SMTP();$smtp->Timeout=3;$smtp->Timelimit=3;$phase='connect';
try {
if(!$smtp->connect('127.0.0.1',$port,3)||!$smtp->hello('demo.invalid'))return ['outcome'=>'retryable','code'=>(int)($smtp->getError()['smtp_code']??0)];
$phase='envelope';
if(!$smtp->mail('noreply@demo.invalid')||!$smtp->recipient($job['recipient'])) {
$code=(int)($smtp->getError()['smtp_code']??0);return ['outcome'=>$code>=500?'failed':'retryable','code'=>$code];
}
$phase='data';
if($smtp->data($job['eml']))return ['outcome'=>'accepted','code'=>250];
$code=(int)($smtp->getError()['smtp_code']??0);
return ['outcome'=>$code>=500?'failed':($code>=400?'retryable':'uncertain'),'code'=>$code];
}catch(Throwable $error){return ['outcome'=>$phase==='data'?'uncertain':'retryable','code'=>0];}
finally{$smtp->close();}
}

56 ausgeführte Bedingungen mit zwölf lokalen SMTP-Verbindungen
Die Prüfumgebung verwendet PHP 8.4.27, PHPMailer 7.2.0 und eine selbst betriebene lokale SMTP-Testsenke. Alle Adressen sind synthetisch. Es entstehen zwölf tatsächliche SMTP-Verbindungen und sieben vollständig erfasste Nachrichtennutzlasten. Die 56 Bedingungen prüfen unter anderem festen Absender, getrennte Antwortadresse, optionale Bestätigung, Klartext, HTML-Maskierung, Punktbehandlung und die unverändert gespeicherte Message-ID beim erneuten Versuch.
Die Eingabegegenfälle betreffen eingeschleuste Headerumbrüche, eine außerhalb des gewählten ASCII-Vertrags liegende Adresse, unpassende Adresssyntax, leeren Namen, zu kurze oder zu lange Nachricht und einen falschen Optionstyp. Solche Eingaben hinterlassen keine teilweise gespeicherte Anfrage. Getrennte Fälle prüfen vorübergehende und endgültige SMTP-Ablehnung, die maximale Versuchszahl und den Schutz vor doppelter Reservierung durch zwei PHP-Prozesse.
Der Verbindungsverlust nach den Daten und der Datenbankfehler nach der Annahme machen die Grenzen ausdrücklich sichtbar. Die Erklärgrafik verwendet tatsächliche Rückgaben dieses Ablaufs. Weder externe Mailserver noch reale Postfächer wurden getestet. Der Nachweis betrifft den gespeicherten Eingang, die eigene Warteschlange und die beobachteten Protokollzustände. Eine allgemeine Zustell- oder Bearbeitungsgarantie wäre durch diese Prüfung nicht gedeckt.
Eine verlässliche Bearbeitung braucht mehr als eine hübsche Vorlage
Die Mailvorlage sollte den Anlass verständlich nennen und Antworten sinnvoll ermöglichen. Gleichzeitig braucht der Betrieb einen Überblick über ausstehende, fehlgeschlagene und unklare Aufträge. Ein freundliches Logo im Kopfbereich kann diese Aufgabe nicht übernehmen. Die konkrete Zuständigkeit für eine unbearbeitete Anfrage und die technische Bedeutung eines Versandstatus müssen voneinander unterscheidbar bleiben und gemeinsam zum tatsächlichen Arbeitsablauf passen.
Bei Änderungen an der Vorlage werden beide Inhaltsalternativen und die tatsächlich erzeugten Header betrachtet. Bei Änderungen am Transport werden die Fehlerfälle erneut ausgeführt. Bei einer Wiederherstellung müssen Anfrage und Mailaufträge zusammenpassen. Ein alter Datenstand darf nicht unbemerkt bereits übermittelte Aufträge wieder als neu erscheinen lassen. Solche Übergänge sind Teil des Betriebs und keine reine Formatierungsfrage.
Kontaktmails werden zuverlässig organisierbar, wenn Eingang, Übermittlung und Bearbeitung klare Grenzen besitzen. Der gezeigte Baustein stellt dafür eine begrenzte ausgeführte Grundlage bereit. Die tatsächliche Webseite verbindet ihn anschließend mit ihrem Formularschutz, ihrem Transport und ihrer Bearbeitung. So bleibt jede Bestätigung an das gebunden, was die Anwendung wirklich gespeichert, beobachtet und verstanden hat.
Den Eingang nicht mit einem erneut abgesendeten Formular verwechseln
Die Warteschlange begrenzt im Beispiel die Bearbeitung eines vorhandenen Mailauftrags. Sie erkennt nicht automatisch, ob eine Person dasselbe Formular zweimal neu absendet. Jeder neue Aufruf des Annahmekerns erzeugt eine neue Anfragekennung. Wer auch diese fachliche Doppelanlage verhindern muss, braucht einen zusätzlichen passenden Idempotenzvertrag für den Formularvorgang. Ein gesperrter Absenden-Button allein wäre dafür keine verlässliche Serverentscheidung.
Diese Unterscheidung gehört in die Abnahme. Zwei Worker für denselben vorhandenen Auftrag sind ein anderer Fall als zwei eigenständige Formulareingänge. Ebenso wenig beweist ein identischer Nachrichtentext, dass beide Eingänge zusammengeführt werden dürfen. Vielleicht wollte die Person tatsächlich eine weitere Anfrage stellen. Die Anwendung benötigt eine klare Vorgangsidentität und einen verständlichen Weg, einen neuen Auftrag von einer Wiederholung zu unterscheiden. Der gezeigte Mailkern ersetzt diese zusätzliche fachliche Entscheidung nicht.
Zustände im tatsächlichen Versuch
| Ergebnis | Bedeutung |
|---|---|
| recorded | Anfrage und Mailaufträge gespeichert |
| accepted_by_smtp | SMTP-Gegenstelle bestätigt, kein Lesebeleg |
| pending nach 450 | Begrenzter späterer Versuch vorgesehen |
| failed nach 550 | Keine blinde automatische Wiederholung |
| uncertain nach Datenübermittlung | Ausgang unklar, Prüfung erforderlich |
| sending nach Status-Speicherfehler | Mögliche Annahme klären statt erneut senden |
Checkliste für Kontaktmails und Bestätigungen
- Gespeicherten Eingang von Mailübermittlung und Bearbeitung unterscheiden.
- Festen berechtigten Absender und getrennte Antwortadresse verwenden.
- Adressvertrag und unterstützte Zeichen ausdrücklich festlegen.
- Header und freie Inhaltsfelder getrennt behandeln.
- Klartext und maskiertes HTML gemeinsam prüfen.
- Gewünschte Bestätigung als eigenen Auftrag anlegen.
- Nachricht und feste Message-ID vor dem Versuch speichern.
- Aufträge gegen doppelte Workerreservierung schützen.
- Vorübergehende, endgültige und unklare Ergebnisse unterscheiden.
- Transport, Wiederherstellung und manuelle Klärung gesondert abnehmen.
Quellen und Prüfstand
Primärquellen am 09.11.2026 geprüft. PHP 8.4.27 und PHPMailer 7.2.0, 56 tatsächlich ausgeführte Bedingungen, zwölf lokale SMTP-Verbindungen und sieben erfasste künstliche Nutzlasten. Keine externen Empfänger angeschrieben und keine reale Postfachzustellung gemessen.







