Add email delivery for license keys (SMTP)

This commit is contained in:
Sami
2026-08-19 12:49:44 -07:00
parent b7dab4c0b2
commit 06d8062a5a
+107
View File
@@ -0,0 +1,107 @@
import crypto from 'crypto';
import nodemailer from 'nodemailer';
const LICENSE_SECRET = process.env.DASHCADDY_LICENSE_SECRET || '';
const ALPHABET = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789'; // Crockford-ish (no I, O, 0, 1)
/**
* Generate a DashCaddy license code in the format `DC-XXXX-XXXX-XXXX-XXXX`.
* Deterministic for the same (licenseKey, durationDays) tuple — if a customer
* pays again, regenerate the code with the same key + new duration, and the
* code stays the same. That's what "the same key gets more time" means.
*/
export function generateLicenseCode(licenseKey, durationDays) {
if (!LICENSE_SECRET) {
throw new Error('DASHCADDY_LICENSE_SECRET not set');
}
const seed = crypto
.createHmac('sha256', LICENSE_SECRET)
.update(`${licenseKey}|${durationDays}`)
.digest();
const segments = [];
for (let i = 0; i < seed.length && segments.length < 4; i += 2) {
const b1 = seed[i];
const b2 = seed[i + 1] || 0;
segments.push(
ALPHABET[b1 % 32] +
ALPHABET[Math.floor(b1 / 32) % 32] +
ALPHABET[b2 % 32] +
ALPHABET[Math.floor(b2 / 32) % 32]
);
}
return 'DC-' + segments.join('-');
}
let transporter = null;
function getTransporter() {
if (transporter) return transporter;
transporter = nodemailer.createTransport({
host: process.env.SMTP_HOST || '127.0.0.1',
port: parseInt(process.env.SMTP_PORT || '25', 10),
secure: process.env.SMTP_SECURE === 'true',
auth: process.env.SMTP_USERNAME ? {
user: process.env.SMTP_USERNAME,
pass: process.env.SMTP_PASSWORD
} : undefined,
tls: { rejectUnauthorized: false }
});
return transporter;
}
function formatDate(iso) {
if (!iso) return 'N/A';
return new Date(iso).toUTCString();
}
/**
* Send the license key email to the customer. Called after every payment
* (initial purchase or renewal extension).
*/
export async function sendLicenseEmail({ to, code, durationDays, planCode, expiresAt, extended = false }) {
const subject = extended
? `Your DashCaddy Premium license has been extended (${durationDays} days added)`
: `Your DashCaddy Premium license key`;
const text = [
extended ? 'Your DashCaddy Premium license has been extended.' : 'Thank you for purchasing DashCaddy Premium.',
'',
`License key: ${code}`,
`Plan: ${planCode}`,
`License valid until: ${formatDate(expiresAt)}`,
'',
'To activate: paste this license key into your DashCaddy dashboard at Admin → License.',
'',
'Need help? Reply to this email or visit https://dashcaddy.net/about'
].join('\n');
const html = `
<div style="font-family: system-ui, sans-serif; max-width: 600px; margin: 0 auto; padding: 20px;">
<h2 style="color: #0f172a;">${extended ? 'License extended' : 'Welcome to DashCaddy Premium'}</h2>
<p>${extended
? 'Your DashCaddy Premium license has been extended. Same key, more time.'
: 'Thank you for purchasing DashCaddy Premium. Your license key is below.'}</p>
<div style="background: #f1f5f9; padding: 16px; border-radius: 8px; margin: 20px 0; font-family: monospace; font-size: 16px; text-align: center; letter-spacing: 2px;">
${code}
</div>
<p><strong>Plan:</strong> ${planCode}</p>
<p><strong>License valid until:</strong> ${formatDate(expiresAt)}</p>
<p>To activate: paste this license key into your DashCaddy dashboard at <strong>Admin → License</strong>.</p>
<hr style="margin-top: 30px; border: none; border-top: 1px solid #e2e8f0;" />
<p style="color: #64748b; font-size: 14px;">Need help? Reply to this email or visit <a href="https://dashcaddy.net/about">dashcaddy.net/about</a></p>
</div>
`;
try {
const t = getTransporter();
await t.sendMail({
from: process.env.SMTP_FROM || 'licenses@dashcaddy.net',
to,
subject,
text,
html
});
return { delivered: true, via: 'smtp' };
} catch (err) {
console.error('Email delivery failed:', err.message);
return { delivered: false, error: err.message };
}
}