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 { [$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(); $mail->Host = (string)env('SMTP_HOST', 'localhost'); $mail->Port = (int)env('SMTP_PORT', '587'); $mail->SMTPAuth = (env('SMTP_USER') || env('SMTP_PASS')) ? true : false; $mail->Username = (string)env('SMTP_USER', ''); $mail->Password = (string)env('SMTP_PASS', ''); $secure = strtolower((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 = (string)env('SMTP_FROM', 'no-reply@mug.a5l.fr'); $fromName = (string)env('SMTP_FROM_NAME', 'MUG'); $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()); } }