loreal-sla-calculator/server/services/mailgunService.js
2026-03-13 10:52:07 +00:00

179 lines
7 KiB
JavaScript
Executable file

'use strict';
const FormData = require('form-data');
const Mailgun = require('mailgun.js');
let mg = null;
function getClient() {
if (!mg) {
const mailgun = new Mailgun(FormData);
mg = mailgun.client({ username: 'api', key: process.env.MAILGUN_API_KEY });
}
return mg;
}
// ---------------------------------------------------------------------------
// Shared HTML email wrapper — L'Oréal branded, table-based for email clients
// ---------------------------------------------------------------------------
function emailWrapper({ headline, bodyHtml, ctaUrl, ctaLabel, footerNote }) {
return `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>${headline}</title>
</head>
<body style="margin:0;padding:0;background-color:#f4f4f0;font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;">
<table width="100%" cellpadding="0" cellspacing="0" border="0" style="background-color:#f4f4f0;padding:40px 0;">
<tr>
<td align="center">
<table width="560" cellpadding="0" cellspacing="0" border="0" style="max-width:560px;width:100%;">
<!-- ── Header ── -->
<tr>
<td style="background-color:#0a0a0a;padding:28px 40px 24px;">
<table width="100%" cellpadding="0" cellspacing="0" border="0">
<tr>
<td>
<p style="margin:0;font-size:11px;letter-spacing:0.35em;color:#9a9a9a;text-transform:uppercase;font-weight:400;">
L&rsquo;OR&Eacute;AL &nbsp;&middot;&nbsp; PARIS
</p>
<p style="margin:6px 0 0;font-size:22px;letter-spacing:0.12em;color:#ffffff;text-transform:uppercase;font-weight:700;line-height:1;">
SLA Calculator
</p>
</td>
<td align="right" valign="bottom">
<span style="display:inline-block;width:28px;height:3px;background-color:#e11d48;border-radius:2px;"></span>
</td>
</tr>
</table>
</td>
</tr>
<!-- ── Red accent bar ── -->
<tr>
<td style="background-color:#e11d48;height:3px;font-size:0;line-height:0;">&nbsp;</td>
</tr>
<!-- ── Body ── -->
<tr>
<td style="background-color:#ffffff;padding:44px 40px 36px;">
<!-- Headline -->
<h1 style="margin:0 0 20px;font-size:26px;font-weight:700;color:#0a0a0a;letter-spacing:-0.01em;line-height:1.25;">
${headline}
</h1>
<!-- Body copy -->
${bodyHtml}
<!-- CTA button -->
<table cellpadding="0" cellspacing="0" border="0" style="margin:32px 0 0;">
<tr>
<td style="background-color:#e11d48;border-radius:2px;">
<a href="${ctaUrl}"
style="display:inline-block;padding:14px 32px;font-size:12px;font-weight:700;letter-spacing:0.15em;text-transform:uppercase;color:#ffffff;text-decoration:none;border-radius:2px;">
${ctaLabel}
</a>
</td>
</tr>
</table>
<!-- Fallback URL -->
<p style="margin:20px 0 0;font-size:11px;color:#9a9a9a;line-height:1.6;">
If the button doesn&rsquo;t work, copy and paste this link into your browser:<br>
<a href="${ctaUrl}" style="color:#e11d48;word-break:break-all;">${ctaUrl}</a>
</p>
</td>
</tr>
<!-- ── Footer note ── -->
<tr>
<td style="background-color:#f9f9f7;border-top:1px solid #ebebeb;padding:20px 40px;">
<p style="margin:0;font-size:11px;color:#9a9a9a;line-height:1.7;">
${footerNote}
</p>
</td>
</tr>
<!-- ── Footer ── -->
<tr>
<td style="padding:20px 40px 0;">
<p style="margin:0;font-size:10px;color:#b0b0a8;letter-spacing:0.05em;text-align:center;line-height:1.6;">
&copy; ${new Date().getFullYear()} L&rsquo;Or&eacute;al. All rights reserved.<br>
This is an automated message — please do not reply.
</p>
</td>
</tr>
</table>
</td>
</tr>
</table>
</body>
</html>`;
}
// ---------------------------------------------------------------------------
async function sendPasswordResetEmail(toEmail, rawToken) {
const resetUrl = `${process.env.APP_BASE_URL}?reset_token=${rawToken}`;
const from = process.env.MAILGUN_FROM || 'noreply@loreal.com';
const html = emailWrapper({
headline: 'Reset your password',
ctaUrl: resetUrl,
ctaLabel: 'Reset my password',
bodyHtml: `
<p style="margin:0 0 12px;font-size:15px;color:#3a3a3a;line-height:1.7;">
We received a request to reset the password for your SLA Calculator account.
Click the button below to choose a new password.
</p>
<p style="margin:0;font-size:13px;color:#9a9a9a;line-height:1.7;">
This link expires in <strong style="color:#3a3a3a;">1 hour</strong>.
</p>`,
footerNote: 'If you did not request a password reset, you can safely ignore this email &mdash; your password will not be changed.',
});
const text = `Reset your SLA Calculator password\n\nWe received a request to reset your password.\n\nClick the link below to set a new password (valid for 1 hour):\n${resetUrl}\n\nIf you did not request this, please ignore this email.`;
return getClient().messages.create(process.env.MAILGUN_DOMAIN, {
from,
to: [toEmail],
subject: 'Reset your SLA Calculator password',
text,
html,
});
}
async function sendVerificationEmail(toEmail, rawToken) {
const verifyUrl = `${process.env.APP_BASE_URL}?verify_token=${rawToken}`;
const from = process.env.MAILGUN_FROM || 'noreply@loreal.com';
const html = emailWrapper({
headline: 'Verify your email address',
ctaUrl: verifyUrl,
ctaLabel: 'Verify my email',
bodyHtml: `
<p style="margin:0 0 12px;font-size:15px;color:#3a3a3a;line-height:1.7;">
Welcome to the SLA Calculator. Please verify your email address to activate your account.
</p>
<p style="margin:0;font-size:13px;color:#9a9a9a;line-height:1.7;">
This link expires in <strong style="color:#3a3a3a;">24 hours</strong>.
</p>`,
footerNote: 'If you did not create an SLA Calculator account, you can safely ignore this email.',
});
const text = `Verify your SLA Calculator email\n\nPlease verify your email address to activate your account:\n${verifyUrl}\n\nThis link is valid for 24 hours.`;
return getClient().messages.create(process.env.MAILGUN_DOMAIN, {
from,
to: [toEmail],
subject: 'Verify your SLA Calculator email',
text,
html,
});
}
module.exports = { sendPasswordResetEmail, sendVerificationEmail };