spb/phi-conseil
Public
1import nodemailer from "nodemailer";2import type { ContactData } from "./validation";3import { getEnv } from "./env";45export interface Mailer {6 send(data: ContactData, meta: { ipHash: string }): Promise<{ delivered: boolean; id?: string }>;7}89function escapeHtml(s: string) {10 return s11 .replace(/&/g, "&")12 .replace(/</g, "<")13 .replace(/>/g, ">")14 .replace(/"/g, """);15}1617export function renderContactEmail(data: ContactData) {18 const text = [19 `Nouveau message depuis le site PHI Conseil`,20 ``,21 `Nom : ${data.name}`,22 `Courriel : ${data.email}`,23 `Organisation : ${data.organization || "—"}`,24 `Sujet : ${data.subject}`,25 ``,26 data.message,27 ].join("\n");28 const html = `<!doctype html><html lang="fr"><body style="font-family:system-ui,sans-serif;color:#1f2a33">29<h2 style="font-weight:600">Nouveau message depuis le site PHI Conseil</h2>30<table cellpadding="4">31<tr><td><strong>Nom</strong></td><td>${escapeHtml(data.name)}</td></tr>32<tr><td><strong>Courriel</strong></td><td>${escapeHtml(data.email)}</td></tr>33<tr><td><strong>Organisation</strong></td><td>${escapeHtml(data.organization || "—")}</td></tr>34<tr><td><strong>Sujet</strong></td><td>${escapeHtml(data.subject)}</td></tr>35</table>36<p style="white-space:pre-wrap">${escapeHtml(data.message)}</p>37</body></html>`;38 return { subject: `[Site PHI Conseil] ${data.subject}`, text, html };39}4041/** Transport « log » : n'envoie rien, journalise une empreinte. Jamais présenté comme une livraison. */42export const logMailer: Mailer = {43 async send(data, meta) {44 console.info(45 `[contact] CONTACT_TRANSPORT=log — courriel NON envoyé (sujet="${data.subject}", ip=${meta.ipHash})`,46 );47 return { delivered: false };48 },49};5051export function createSmtpMailer(): Mailer {52 const env = getEnv();53 const transporter = nodemailer.createTransport({54 host: env.SMTP_HOST,55 port: env.SMTP_PORT,56 secure: env.SMTP_SECURE,57 auth: env.SMTP_USER ? { user: env.SMTP_USER, pass: env.SMTP_PASS } : undefined,58 });59 return {60 async send(data) {61 const { subject, text, html } = renderContactEmail(data);62 const info = await transporter.sendMail({63 from: env.CONTACT_FROM,64 to: env.CONTACT_TO,65 replyTo: data.email,66 subject,67 text,68 html,69 });70 return { delivered: true, id: info.messageId };71 },72 };73}7475let instance: Mailer | null = null;76export function getMailer(): Mailer {77 if (instance) return instance;78 instance = getEnv().CONTACT_TRANSPORT === "smtp" ? createSmtpMailer() : logMailer;79 return instance;80}8182/** Pour les tests. */83export function setMailerForTests(m: Mailer | null) {84 instance = m;85}86