0) if ($coolMin > 0) { $q1 = "SELECT 1 FROM journal_smtp WHERE to_email = :e AND created_at >= NOW() - INTERVAL :cool AND status IN ('sent','queued') LIMIT 1"; $stmt = $pdo->prepare($q1); $stmt->execute([':e' => $email, ':cool' => sprintf('%d minutes', $coolMin)]); if ($stmt->fetchColumn()) { return [false, "Un email vient d’être envoyé. Réessayez dans {$coolMin} min."]; } } // Plafond 12h (actif seulement si >0) if ($maxPer12h > 0) { $q2 = "SELECT COUNT(*) FROM journal_smtp WHERE to_email = :e AND created_at >= NOW() - INTERVAL '12 hours' AND status IN ('sent','queued')"; $stmt = $pdo->prepare($q2); $stmt->execute([':e' => $email]); if ((int)$stmt->fetchColumn() >= $maxPer12h) { return [false, 'Quota atteint. Réessayez plus tard.']; } } return [true, '']; } /** * Envoi immédiat SMTP avec PHPMailer + journalisation. * @param string $to destinataire * @param string $subject objet * @param string $html corps HTML * @param string|null $text corps texte brut (optionnel, auto-généré si null) * @param array $opts ['reply_to'=>['email','name']] */ function envoyer_mail_smtp(string $to, string $subject, string $html, ?string $text = null, array $opts = []): bool { if (!($opts['bypass_rate_limit'] ?? false)) { [$ok, $msg] = mailer_can_send($to, (int)env('SMTP_COOLDOWN_MINUTES', '5'), (int)env('SMTP_MAX_PER_12H', '5')); if (!$ok) { throw new RuntimeException($msg); } } $pdo = db(); $pdo->beginTransaction(); try { $stmt = $pdo->prepare("INSERT INTO journal_smtp (created_at, script_path, to_email, subject, content_html, content_text, status, ip, user_agent) VALUES (NOW(), :script, :to, :subj, :html, :text, 'queued', :ip, :ua) RETURNING id"); $stmt->execute([ ':script' => ($_SERVER['SCRIPT_NAME'] ?? ''), ':to' => $to, ':subj' => $subject, ':html' => $html, ':text' => $text ?? trim(html_entity_decode(strip_tags($html), ENT_QUOTES)), ':ip' => ($_SERVER['HTTP_X_FORWARDED_FOR'] ?? $_SERVER['REMOTE_ADDR'] ?? ''), ':ua' => substr($_SERVER['HTTP_USER_AGENT'] ?? '', 0, 512), ]); $rowId = (int)$stmt->fetchColumn(); $pdo->commit(); } catch (\Throwable $e) { if ($pdo->inTransaction()) { $pdo->rollBack(); } throw $e; } $mail = new PHPMailer(true); try { $mail->isSMTP(); $_smtpRead = function_exists('smtpCfg'); $mail->Host = $_smtpRead ? smtpCfg('host', 'SMTP_HOST', 'localhost') : (string)env('SMTP_HOST', 'localhost'); $mail->Port = (int)($_smtpRead ? smtpCfg('port', 'SMTP_PORT', '587') : env('SMTP_PORT', '587')); $_smtpUser = $_smtpRead ? smtpCfg('user', 'SMTP_USER') : (string)env('SMTP_USER', ''); $_smtpPass = $_smtpRead ? smtpCfg('pass', 'SMTP_PASS') : (string)env('SMTP_PASS', ''); $mail->SMTPAuth = ($_smtpUser !== '' || $_smtpPass !== ''); $mail->Username = $_smtpUser; $mail->Password = $_smtpPass; $secure = strtolower($_smtpRead ? smtpCfg('secure', 'SMTP_SECURE', 'tls') : (string)env('SMTP_SECURE', 'tls')); if ($secure === 'ssl') { $mail->SMTPSecure = PHPMailer::ENCRYPTION_SMTPS; } elseif ($secure === 'tls') { $mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS; } $mail->SMTPKeepAlive = true; // réutilise la connexion $mail->Timeout = 30; // évite les blocages longs $mail->SMTPOptions = ['ssl' => ['verify_peer' => true,'verify_peer_name' => true,'allow_self_signed' => false]]; $mail->CharSet = 'UTF-8'; $mail->isHTML(true); // Expéditeur $from = $_smtpRead ? smtpCfg('from', 'SMTP_FROM', 'no-reply@varlog.a5l.fr') : (string)env('SMTP_FROM', 'no-reply@varlog.a5l.fr'); $fromName = $_smtpRead ? smtpCfg('from_name', 'SMTP_FROM_NAME', 'varlog') : (string)env('SMTP_FROM_NAME', 'varlog'); $mail->setFrom($from, $fromName); // Reply-To if (!empty($opts['reply_to']) && is_array($opts['reply_to']) && filter_var($opts['reply_to'][0] ?? '', FILTER_VALIDATE_EMAIL)) { $mail->addReplyTo($opts['reply_to'][0], $opts['reply_to'][1] ?? ''); } elseif ($rt = env('SMTP_REPLY_TO')) { $mail->addReplyTo($rt, (string)env('SMTP_REPLY_TO_NAME', 'Support')); } // DKIM optionnel if ($d = env('DKIM_DOMAIN')) { $mail->DKIM_domain = $d; $mail->DKIM_selector = (string)env('DKIM_SELECTOR', 'default'); $mail->DKIM_private = (string)env('DKIM_PRIVATE_KEY_PATH', ''); $mail->DKIM_passphrase = (string)env('DKIM_PASSPHRASE', ''); $mail->DKIM_identity = $from; } $mail->addAddress($to); $mail->Subject = $subject; $mail->Body = $html; $mail->AltBody = $text ?? trim(html_entity_decode(strip_tags($html), ENT_QUOTES)); $mail->send(); $pdo->prepare("UPDATE journal_smtp SET status='sent', sent_at=NOW() WHERE id=:id")->execute([':id' => $rowId]); return true; } catch (Exception $e) { $pdo->prepare("UPDATE journal_smtp SET status='error', error_message=:err, sent_at=NOW() WHERE id=:id") ->execute([':id' => $rowId, ':err' => substr($e->getMessage(), 0, 1000)]); throw new RuntimeException('Envoi email impossible: '.$e->getMessage()); } }