340 lines
11 KiB
JavaScript
340 lines
11 KiB
JavaScript
const BREVO_API_URL = 'https://api.brevo.com/v3/smtp/email';
|
|
|
|
/**
|
|
* Send email via Brevo API
|
|
*/
|
|
async function sendEmail({ to, subject, htmlContent, textContent }) {
|
|
const apiKey = process.env.BREVO_API_KEY;
|
|
|
|
if (!apiKey) {
|
|
console.error('BREVO_API_KEY not configured');
|
|
throw new Error('Email service not configured');
|
|
}
|
|
|
|
const payload = {
|
|
sender: {
|
|
name: process.env.BREVO_SENDER_NAME || 'WellNuo',
|
|
email: process.env.BREVO_SENDER_EMAIL || 'noreply@wellnuo.com'
|
|
},
|
|
to: [{ email: to }],
|
|
subject,
|
|
htmlContent,
|
|
textContent
|
|
};
|
|
|
|
const response = await fetch(BREVO_API_URL, {
|
|
method: 'POST',
|
|
headers: {
|
|
'accept': 'application/json',
|
|
'api-key': apiKey,
|
|
'content-type': 'application/json'
|
|
},
|
|
body: JSON.stringify(payload)
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const error = await response.text();
|
|
console.error('Brevo API error:', error);
|
|
throw new Error('Failed to send email');
|
|
}
|
|
|
|
return await response.json();
|
|
}
|
|
|
|
/**
|
|
* Send password reset email
|
|
*/
|
|
async function sendPasswordResetEmail(email, resetToken) {
|
|
const frontendUrl = process.env.FRONTEND_URL || 'https://wellnuo.smartlaunchhub.com';
|
|
const resetLink = `${frontendUrl}/reset-password?token=${resetToken}`;
|
|
|
|
const htmlContent = `
|
|
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<style>
|
|
body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; }
|
|
.container { max-width: 600px; margin: 0 auto; padding: 20px; }
|
|
.header { background: #4A90D9; color: white; padding: 20px; text-align: center; }
|
|
.content { padding: 30px; background: #f9f9f9; }
|
|
.button { display: inline-block; padding: 12px 30px; background: #4A90D9; color: white; text-decoration: none; border-radius: 5px; margin: 20px 0; }
|
|
.footer { padding: 20px; text-align: center; font-size: 12px; color: #666; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="container">
|
|
<div class="header">
|
|
<h1>WellNuo</h1>
|
|
</div>
|
|
<div class="content">
|
|
<h2>Password Reset Request</h2>
|
|
<p>We received a request to reset your password. Click the button below to create a new password:</p>
|
|
<p style="text-align: center;">
|
|
<a href="${resetLink}" class="button">Reset Password</a>
|
|
</p>
|
|
<p>Or copy and paste this link into your browser:</p>
|
|
<p style="word-break: break-all; color: #4A90D9;">${resetLink}</p>
|
|
<p><strong>This link will expire in 1 hour.</strong></p>
|
|
<p>If you didn't request a password reset, you can safely ignore this email.</p>
|
|
</div>
|
|
<div class="footer">
|
|
<p>© 2025 WellNuo - Elderly Care Monitoring</p>
|
|
</div>
|
|
</div>
|
|
</body>
|
|
</html>
|
|
`;
|
|
|
|
const textContent = `
|
|
WellNuo - Password Reset
|
|
|
|
We received a request to reset your password.
|
|
|
|
Click this link to reset your password:
|
|
${resetLink}
|
|
|
|
This link will expire in 1 hour.
|
|
|
|
If you didn't request a password reset, you can safely ignore this email.
|
|
|
|
WellNuo - Elderly Care Monitoring
|
|
`;
|
|
|
|
return sendEmail({
|
|
to: email,
|
|
subject: 'WellNuo - Password Reset Request',
|
|
htmlContent,
|
|
textContent
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Send OTP code for email-based login
|
|
*/
|
|
async function sendOTPEmail(email, code, userName) {
|
|
const htmlContent = `
|
|
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
</head>
|
|
<body style="margin: 0; padding: 0; background-color: #FFFFFF; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Arial, sans-serif;">
|
|
<table width="100%" cellspacing="0" cellpadding="0" style="background-color: #FFFFFF;">
|
|
<tr>
|
|
<td align="center" style="padding: 48px 24px;">
|
|
<table width="100%" cellspacing="0" cellpadding="0" style="max-width: 400px;">
|
|
|
|
<!-- Logo -->
|
|
<tr>
|
|
<td align="center" style="padding-bottom: 32px;">
|
|
<span style="font-size: 24px; font-weight: 700; color: #4A90D9;">WellNuo</span>
|
|
</td>
|
|
</tr>
|
|
|
|
<!-- Title -->
|
|
<tr>
|
|
<td align="center" style="padding-bottom: 8px;">
|
|
<h1 style="margin: 0; font-size: 20px; font-weight: 600; color: #333333;">Your verification code</h1>
|
|
</td>
|
|
</tr>
|
|
|
|
<!-- Subtitle -->
|
|
<tr>
|
|
<td align="center" style="padding-bottom: 32px;">
|
|
<p style="margin: 0; font-size: 14px; color: #666666;">
|
|
${userName ? `Hi ${userName}, use` : 'Use'} this code to sign in
|
|
</p>
|
|
</td>
|
|
</tr>
|
|
|
|
<!-- Code -->
|
|
<tr>
|
|
<td align="center" style="padding-bottom: 32px;">
|
|
<div style="display: inline-block; padding: 16px 32px; background-color: #F5F7FA; border-radius: 8px;">
|
|
<span style="font-size: 32px; font-weight: 700; letter-spacing: 6px; color: #333333;">${code}</span>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
|
|
<!-- Expires -->
|
|
<tr>
|
|
<td align="center" style="padding-bottom: 32px;">
|
|
<p style="margin: 0; font-size: 13px; color: #999999;">
|
|
Code expires in 10 minutes
|
|
</p>
|
|
</td>
|
|
</tr>
|
|
|
|
<!-- Divider -->
|
|
<tr>
|
|
<td style="padding-bottom: 24px;">
|
|
<div style="height: 1px; background-color: #E5E7EB;"></div>
|
|
</td>
|
|
</tr>
|
|
|
|
<!-- Security note -->
|
|
<tr>
|
|
<td align="center">
|
|
<p style="margin: 0; font-size: 12px; color: #999999; line-height: 1.5;">
|
|
If you didn't request this code, you can safely ignore this email.
|
|
</p>
|
|
</td>
|
|
</tr>
|
|
|
|
</table>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
</body>
|
|
</html>
|
|
`;
|
|
|
|
const textContent = `
|
|
WellNuo - Your verification code
|
|
|
|
${userName ? `Hi ${userName}, use` : 'Use'} this code to sign in:
|
|
|
|
${code}
|
|
|
|
Code expires in 10 minutes.
|
|
|
|
If you didn't request this code, you can safely ignore this email.
|
|
`;
|
|
|
|
try {
|
|
await sendEmail({
|
|
to: email,
|
|
subject: `${code} - Your WellNuo Login Code`,
|
|
htmlContent,
|
|
textContent
|
|
});
|
|
return true;
|
|
} catch (error) {
|
|
console.error('Failed to send OTP email:', error);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Send invitation email to share beneficiary access
|
|
*/
|
|
async function sendInvitationEmail({ email, inviterName, beneficiaryName, role, inviteCode }) {
|
|
const frontendUrl = process.env.FRONTEND_URL || 'https://wellnuo.smartlaunchhub.com';
|
|
const acceptLink = `${frontendUrl}/accept-invite?code=${inviteCode}`;
|
|
const roleText = role === 'guardian' ? 'Guardian (full access)' : 'Caretaker (view only)';
|
|
|
|
const htmlContent = `
|
|
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
</head>
|
|
<body style="margin: 0; padding: 0; background-color: #F5F7FA; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Arial, sans-serif;">
|
|
<table width="100%" cellspacing="0" cellpadding="0" style="background-color: #F5F7FA;">
|
|
<tr>
|
|
<td align="center" style="padding: 48px 24px;">
|
|
<table width="100%" cellspacing="0" cellpadding="0" style="max-width: 500px; background-color: #FFFFFF; border-radius: 16px; overflow: hidden; box-shadow: 0 4px 6px rgba(0,0,0,0.1);">
|
|
|
|
<!-- Header -->
|
|
<tr>
|
|
<td style="background: linear-gradient(135deg, #4A90D9 0%, #357ABD 100%); padding: 32px; text-align: center;">
|
|
<h1 style="margin: 0; font-size: 24px; font-weight: 700; color: #FFFFFF;">WellNuo</h1>
|
|
<p style="margin: 8px 0 0 0; font-size: 14px; color: rgba(255,255,255,0.9);">Elderly Care Monitoring</p>
|
|
</td>
|
|
</tr>
|
|
|
|
<!-- Content -->
|
|
<tr>
|
|
<td style="padding: 32px;">
|
|
<h2 style="margin: 0 0 16px 0; font-size: 20px; font-weight: 600; color: #333333;">You've been invited!</h2>
|
|
|
|
<p style="margin: 0 0 24px 0; font-size: 15px; color: #666666; line-height: 1.6;">
|
|
<strong>${inviterName || 'Someone'}</strong> has invited you to help monitor
|
|
<strong>${beneficiaryName || 'their loved one'}</strong> on WellNuo.
|
|
</p>
|
|
|
|
<!-- Role Badge -->
|
|
<div style="background-color: #F0F7FF; border-radius: 8px; padding: 16px; margin-bottom: 24px;">
|
|
<p style="margin: 0; font-size: 13px; color: #666666;">Your role:</p>
|
|
<p style="margin: 4px 0 0 0; font-size: 16px; font-weight: 600; color: #4A90D9;">${roleText}</p>
|
|
</div>
|
|
|
|
<!-- Invite Code -->
|
|
<div style="background-color: #F5F7FA; border-radius: 8px; padding: 20px; text-align: center; margin-bottom: 24px;">
|
|
<p style="margin: 0 0 8px 0; font-size: 13px; color: #666666;">Your invitation code:</p>
|
|
<p style="margin: 0; font-size: 28px; font-weight: 700; letter-spacing: 3px; color: #333333;">${inviteCode}</p>
|
|
</div>
|
|
|
|
<!-- CTA Button -->
|
|
<table width="100%" cellspacing="0" cellpadding="0">
|
|
<tr>
|
|
<td align="center">
|
|
<a href="${acceptLink}" style="display: inline-block; padding: 14px 32px; background: linear-gradient(135deg, #4A90D9 0%, #357ABD 100%); color: #FFFFFF; text-decoration: none; border-radius: 8px; font-size: 16px; font-weight: 600;">Accept Invitation</a>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
|
|
<p style="margin: 24px 0 0 0; font-size: 13px; color: #999999; text-align: center;">
|
|
This invitation expires in 7 days.
|
|
</p>
|
|
</td>
|
|
</tr>
|
|
|
|
<!-- Footer -->
|
|
<tr>
|
|
<td style="padding: 24px; background-color: #F5F7FA; text-align: center;">
|
|
<p style="margin: 0; font-size: 12px; color: #999999;">
|
|
If you didn't expect this invitation, you can safely ignore this email.
|
|
</p>
|
|
</td>
|
|
</tr>
|
|
|
|
</table>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
</body>
|
|
</html>
|
|
`;
|
|
|
|
const textContent = `
|
|
WellNuo - You've been invited!
|
|
|
|
${inviterName || 'Someone'} has invited you to help monitor ${beneficiaryName || 'their loved one'} on WellNuo.
|
|
|
|
Your role: ${roleText}
|
|
|
|
Your invitation code: ${inviteCode}
|
|
|
|
Accept your invitation: ${acceptLink}
|
|
|
|
This invitation expires in 7 days.
|
|
|
|
If you didn't expect this invitation, you can safely ignore this email.
|
|
|
|
WellNuo - Elderly Care Monitoring
|
|
`;
|
|
|
|
try {
|
|
await sendEmail({
|
|
to: email,
|
|
subject: `${inviterName || 'Someone'} invited you to WellNuo`,
|
|
htmlContent,
|
|
textContent
|
|
});
|
|
return true;
|
|
} catch (error) {
|
|
console.error('Failed to send invitation email:', error);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
module.exports = {
|
|
sendEmail,
|
|
sendPasswordResetEmail,
|
|
sendOTPEmail,
|
|
sendInvitationEmail
|
|
};
|