(int) $pdo->query('SELECT COUNT(*) FROM mail_queue')->fetchColumn(); // 1) Server nicht erreichbar: Mail wird gespeichert $ok = send_mail('kunde@firma.example', 'Ihre Antwort', '

Hallo

'); $check($ok === false && mail_last_queued() === true, 'Fehlgeschlagene Mail kommt in die Warteschlange'); $q = $pdo->query('SELECT * FROM mail_queue')->fetchAll(); $check(count($q) === 1 && $q[0]['status'] === 'pending' && $q[0]['to_email'] === 'kunde@firma.example' && (int) $q[0]['attempts'] === 1 && $q[0]['body'] === '

Hallo

', 'Eintrag enthält Empfänger, Text und den ersten Versuch'); $check($q && (string) $q[0]['last_error'] !== '', 'Der Fehlergrund wird gespeichert'); $check($q && strtotime((string) $q[0]['next_attempt_at']) > time() + 240, 'Nächster Versuch frühestens in etwa 5 Minuten'); // Test-Mails und .invalid-Adressen werden nicht eingereiht $ok2 = send_mail('test@firma.example', 'Test', '

t

', null, false); $check($ok2 === false && mail_last_queued() === false && $count() === 1, 'Test-Mails (queue=false) kommen nicht in die Warteschlange'); $check(send_mail('bot@x.invalid', 'x', 'x') === true && $count() === 1, 'Adressen mit .invalid werden übersprungen'); // 2) Noch nicht fällig: nichts passiert $r = mail_queue_run($pdo); $check($r['sent'] === 0 && $r['failed'] === 0, 'Noch nicht fällige Mails werden nicht versucht'); // 3) Fällig, Server weiter tot: nächster Versuch später (wachsender Abstand) $pdo->exec("UPDATE mail_queue SET next_attempt_at = '2000-01-01 00:00:00'"); $r = mail_queue_run($pdo); $row = $pdo->query('SELECT * FROM mail_queue')->fetch(); $check($r['retry'] === 1 && (int) $row['attempts'] === 2 && $row['status'] === 'pending', 'Zweiter Fehlversuch bleibt in der Warteschlange'); $check(strtotime((string) $row['next_attempt_at']) > time() + 9 * 60 && strtotime((string) $row['next_attempt_at']) < time() + 11 * 60, 'Abstand wächst auf 10 Minuten'); // Gesundheitsanzeige $check(in_array('mail_queue_pending', array_column(system_health($pdo), 'code'), true), 'Statusanzeige nennt die wartenden Mails'); // 4) Ausschalter set_setting('mail_queue_enabled', '0'); send_mail('zweiter@firma.example', 'Aus', '

x

'); $check($count() === 1, 'Ausgeschaltete Warteschlange nimmt nichts mehr an'); set_setting('mail_queue_enabled', '1'); // 5) Server wieder da: Mail geht raus, genau einmal $proc = proc_open([PHP_BINARY, __DIR__ . '/fixtures/smtp_server.php', (string) $port, $log], [1 => ['file', sys_get_temp_dir() . '/deskhub-mq-out.txt', 'w'], 2 => ['file', sys_get_temp_dir() . '/deskhub-mq-err.txt', 'w']], $pipes); for ($i = 0; $i < 50 && !is_file($log); $i++) usleep(100000); $pdo->exec("UPDATE mail_queue SET next_attempt_at = '2000-01-01 00:00:00'"); $r = mail_queue_run($pdo); $row = $pdo->query('SELECT * FROM mail_queue')->fetch(); $check($r['sent'] === 1 && $row['status'] === 'sent' && $row['sent_at'] !== null && $row['last_error'] === null, 'Nach Ende der Störung wird die Mail nachträglich gesendet'); $r = mail_queue_run($pdo); $check($r['sent'] === 0, 'Eine gesendete Mail wird nicht noch einmal gesendet'); $received = is_file($log) ? substr_count((string) file_get_contents($log), 'MAIL kunde@firma.example') : 0; $check($received === 1, 'Der Server hat die Mail genau einmal erhalten (erhalten: ' . $received . ')'); $check(!in_array('mail_queue_pending', array_column(system_health($pdo), 'code'), true), 'Statusanzeige ist wieder sauber'); if (is_resource($proc)) { proc_terminate($proc); proc_close($proc); } usleep(300000); // 6) Aufgeben nach zu vielen Versuchen und nach 48 Stunden $ins = $pdo->prepare("INSERT INTO mail_queue (to_email, subject, body, status, attempts, next_attempt_at, created_at) VALUES (?, 'S', '

a

', 'pending', ?, '2000-01-01 00:00:00', ?)"); $ins->execute(['alt@firma.example', 9, db_now()]); $r = mail_queue_run($pdo); $check($r['gave_up'] === 1 && $pdo->query("SELECT status FROM mail_queue WHERE to_email = 'alt@firma.example'")->fetchColumn() === 'failed', 'Nach dem 10. Fehlversuch wird die Mail aufgegeben'); $check(in_array('mail_queue_failed', array_column(system_health($pdo), 'code'), true), 'Statusanzeige nennt aufgegebene Mails'); $ins->execute(['uralt@firma.example', 1, date('Y-m-d H:i:s', time() - 3 * 86400)]); mail_queue_run($pdo); $check($pdo->query("SELECT status FROM mail_queue WHERE to_email = 'uralt@firma.example'")->fetchColumn() === 'failed', 'Nach 48 Stunden wird die Mail aufgegeben'); // 7) Reservierung: Eine bereits reservierte (noch nicht fällige) Mail wird nicht bearbeitet $pdo->prepare("INSERT INTO mail_queue (to_email, subject, body, status, attempts, next_attempt_at, created_at) VALUES ('res@firma.example','Res','

r

','pending',1,?,?)")->execute([date('Y-m-d H:i:s', time() + 600), db_now()]); $r = mail_queue_run($pdo); $check($r['sent'] === 0 && $r['failed'] === 0, 'Eine reservierte Mail wird nicht doppelt bearbeitet'); // 8) Cron-Job und Menü require_once dirname(__DIR__) . '/cron/process_mail_queue.php'; $res = run_process_mail_queue($pdo); $check(isset($res['log'][0]) && str_contains($res['log'][0], 'Mail-Warteschlange') && get_setting('cron_process_mail_queue_last_run', '') !== '', 'Cron-Job läuft und merkt sich den Zeitpunkt'); $check(isset(pseudo_cron_job_definitions()['process_mail_queue']), 'Der eingebaute Hintergrundbetrieb kennt den Job'); $check(str_contains((string) file_get_contents(dirname(__DIR__) . '/staff/_header.php'), '/staff/mail_queue.php'), 'Verwaltungsseite ist im Menü verlinkt'); @unlink($db); @unlink($log); if ($failures) { fwrite(STDERR, "Fehlgeschlagen:\n - " . implode("\n - ", $failures) . "\n"); exit(1); } echo "Mail queue checks passed.\n";