61 lines
2.8 KiB
TypeScript
61 lines
2.8 KiB
TypeScript
import nodemailer, { type Transporter } from "nodemailer";
|
|
import { childLogger } from "../_core/logger.js";
|
|
|
|
const log = childLogger("email");
|
|
|
|
let cachedTransporter: Transporter | null = null;
|
|
|
|
function getTransporter(): Transporter | null {
|
|
if (cachedTransporter) return cachedTransporter;
|
|
const host = process.env.SMTP_HOST;
|
|
const port = process.env.SMTP_PORT ? Number(process.env.SMTP_PORT) : undefined;
|
|
const user = process.env.SMTP_USER;
|
|
const pass = process.env.SMTP_PASS;
|
|
if (!host || !port) {
|
|
log.warn("SMTP_HOST/SMTP_PORT not configured — emails will be logged only");
|
|
return null;
|
|
}
|
|
cachedTransporter = nodemailer.createTransport({
|
|
host,
|
|
port,
|
|
secure: port === 465,
|
|
auth: user && pass ? { user, pass } : undefined,
|
|
});
|
|
return cachedTransporter;
|
|
}
|
|
|
|
export async function sendMail(opts: { to: string; subject: string; html: string; text?: string }): Promise<void> {
|
|
const from = process.env.SMTP_FROM ?? process.env.SMTP_USER ?? "no-reply@queuemed.app";
|
|
const transporter = getTransporter();
|
|
if (!transporter) {
|
|
log.info({ to: opts.to, subject: opts.subject }, "(dev) would send email");
|
|
return;
|
|
}
|
|
await transporter.sendMail({
|
|
from,
|
|
to: opts.to,
|
|
subject: opts.subject,
|
|
html: opts.html,
|
|
text: opts.text,
|
|
});
|
|
}
|
|
|
|
export function buildResetEmail(resetUrl: string): { subject: string; html: string; text: string } {
|
|
const subject = "QueueMed — Réinitialisation de votre mot de passe";
|
|
const text = `Bonjour,\n\nVous avez demandé à réinitialiser votre mot de passe QueueMed.\n\nCliquez sur ce lien (valable 1 heure) :\n${resetUrl}\n\nSi vous n'êtes pas à l'origine de cette demande, ignorez ce message.\n\n— L'équipe QueueMed`;
|
|
const html = `<!doctype html>
|
|
<html><body style="font-family:Inter,Arial,sans-serif;color:#0f172a;background:#f0fdf4;margin:0;padding:24px">
|
|
<div style="max-width:540px;margin:0 auto;background:white;border-radius:16px;padding:32px;box-shadow:0 4px 20px rgba(16,185,129,0.1)">
|
|
<h1 style="color:#10b981;margin:0 0 16px;font-size:22px">Réinitialisation de votre mot de passe</h1>
|
|
<p>Bonjour,</p>
|
|
<p>Vous avez demandé à réinitialiser votre mot de passe QueueMed. Cliquez sur le bouton ci-dessous (valable 1 heure) :</p>
|
|
<p style="margin:24px 0">
|
|
<a href="${resetUrl}" style="display:inline-block;padding:12px 24px;background:linear-gradient(135deg,#10b981,#06b6d4);color:white;text-decoration:none;border-radius:12px;font-weight:600">Réinitialiser mon mot de passe</a>
|
|
</p>
|
|
<p style="color:#64748b;font-size:13px">Si vous n'êtes pas à l'origine de cette demande, ignorez ce message.</p>
|
|
<hr style="border:none;border-top:1px solid #e2e8f0;margin:24px 0" />
|
|
<p style="color:#94a3b8;font-size:12px">— L'équipe QueueMed</p>
|
|
</div>
|
|
</body></html>`;
|
|
return { subject, html, text };
|
|
}
|