Add multi-tenant cloud mode: self-serve purchase, credit metering, core-decoupling
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
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
// "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);
|
||||
}
|
||||
Reference in New Issue
Block a user