0ae59f3550
Introduces RECAP_MODE=multi alongside single-mode self-host: - Tenant auth + accounts (magic-link via System SMTP), per-tenant credit pool, anonymous trial minting with per-IP/-64 caps - Self-serve Pro/Max purchase: inline Lightning (BTCPay) + card (Zaprite), prepaid 30-day periods, expiry-reminder emails - Core-decoupling: relay owns cloud tier/expiry keyed by Recaps user-id - SQLite (better-sqlite3) schema for multi-mode; filesystem unchanged for single - StartOS actions/versions through 0.2.155
94 lines
3.8 KiB
JavaScript
94 lines
3.8 KiB
JavaScript
// "Your Recap [Pro|Max] account is ready" email — sent after an
|
|
// anon buyer's BTCPay invoice settles. Distinct from the standard
|
|
// sign-in magic-link email because the framing is celebratory ("your
|
|
// purchase is confirmed, click here to access") rather than the
|
|
// transactional "click here to sign in." Both share the underlying
|
|
// magic-link mechanism — only the copy and subject differ.
|
|
//
|
|
// Returns { subject, text, html } in the nodemailer shape.
|
|
|
|
export function renderLicenseReadyEmail({
|
|
verifyUrl,
|
|
tierLabel = "Pro",
|
|
brandName = "Recaps",
|
|
expiresInMinutes = 15,
|
|
}) {
|
|
const subject = `Your ${brandName} ${tierLabel} account is ready`;
|
|
|
|
const text = [
|
|
`Thanks for upgrading to ${brandName} ${tierLabel} — your payment is confirmed.`,
|
|
"",
|
|
`Click the link below to sign in to your new account. ${tierLabel} is already active and your license is attached.`,
|
|
"",
|
|
verifyUrl,
|
|
"",
|
|
`This link expires in ${expiresInMinutes} minutes and can only be used once. If it expires, just go back to ${brandName} and request a fresh sign-in link from the same email.`,
|
|
"",
|
|
"Welcome aboard.",
|
|
].join("\n");
|
|
|
|
const html = `<!doctype html>
|
|
<html>
|
|
<body style="margin:0;padding:0;background:#fafafa;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;">
|
|
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="background:#fafafa;padding:32px 0;">
|
|
<tr>
|
|
<td align="center">
|
|
<table role="presentation" width="480" cellpadding="0" cellspacing="0" style="background:#ffffff;border-radius:8px;padding:32px;max-width:90%;">
|
|
<tr>
|
|
<td style="font-size:18px;font-weight:600;color:#111;padding-bottom:8px;">
|
|
Your ${escapeHtml(brandName)} ${escapeHtml(tierLabel)} account is ready
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td style="font-size:14px;line-height:1.55;color:#444;padding-bottom:8px;">
|
|
Thanks for upgrading — your payment is confirmed and your <strong>${escapeHtml(tierLabel)}</strong> license is attached to your new account.
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td style="font-size:14px;line-height:1.55;color:#444;padding-bottom:24px;">
|
|
Click the button below to sign in. The link expires in ${expiresInMinutes} minutes.
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td align="center" style="padding-bottom:24px;">
|
|
<a href="${escapeAttr(verifyUrl)}" style="display:inline-block;background:#3b82f6;color:#fff;text-decoration:none;font-size:15px;font-weight:600;padding:12px 24px;border-radius:6px;">Sign in to ${escapeHtml(brandName)}</a>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td style="font-size:13px;line-height:1.5;color:#888;padding-bottom:8px;">
|
|
Or copy and paste this URL into your browser:
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td style="font-size:12px;color:#888;word-break:break-all;padding-bottom:24px;">
|
|
${escapeHtml(verifyUrl)}
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td style="font-size:12px;line-height:1.5;color:#888;border-top:1px solid #eee;padding-top:16px;">
|
|
If the link expires before you click it, you can request a fresh sign-in link from ${escapeHtml(brandName)} using this same email. Your ${escapeHtml(tierLabel)} license will already be on the account when you sign in.
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
</body>
|
|
</html>`;
|
|
|
|
return { subject, text, html };
|
|
}
|
|
|
|
function escapeHtml(s) {
|
|
return String(s)
|
|
.replace(/&/g, "&")
|
|
.replace(/</g, "<")
|
|
.replace(/>/g, ">")
|
|
.replace(/"/g, """)
|
|
.replace(/'/g, "'");
|
|
}
|
|
|
|
function escapeAttr(s) {
|
|
return escapeHtml(s);
|
|
}
|