/** * Hand-written HTML emails (no React Email dependency): small, deterministic, and * renderable everywhere. Each template returns { subject, html, text }. */ export interface EmailContent { subject: string; html: string; text: string; } const BRAND = { name: "PolyLLM", tagline: "Your models. Your keys. One workspace.", bg: "#0b0c10", card: "#12141a", fg: "#f4f5f7", muted: "#9aa3b2", accent: "#7c8cff", accentDark: "#5b6cf0", border: "#232734", }; function escapeHtml(s: string): string { return s.replace(/&/g, "&").replace(//g, ">").replace(/"/g, """); } function layout(opts: { siteUrl: string; preheader: string; title: string; body: string; cta?: { label: string; url: string }; footnote?: string }): string { const cta = opts.cta ? `
| ${escapeHtml(opts.cta.label)} |
Or paste this link in your browser:
${opts.cta.url}
|
${text}
`; } const textFooter = (siteUrl: string) => `\n\n—\n${BRAND.name} — ${BRAND.tagline}\n${siteUrl}\n`; export function verificationEmail(siteUrl: string, url: string): EmailContent { return { subject: "Verify your email — PolyLLM", html: layout({ siteUrl, preheader: "Confirm your email address to activate your PolyLLM account.", title: "Confirm your email", body: p("Welcome to PolyLLM. Click the button below to verify your email address and activate your account.") + p("This link expires in 24 hours."), cta: { label: "Verify email", url }, footnote: "If you didn't create a PolyLLM account, you can safely ignore this email.", }), text: `Confirm your email\n\nWelcome to PolyLLM. Open the link below to verify your email address and activate your account (expires in 24 hours):\n\n${url}\n\nIf you didn't create a PolyLLM account, you can ignore this email.${textFooter(siteUrl)}`, }; } export function welcomeEmail(siteUrl: string, name: string): EmailContent { const hi = name ? `Hi ${escapeHtml(name)},` : "Hi,"; return { subject: "Welcome to PolyLLM", html: layout({ siteUrl, preheader: "Your account is verified. Connect a provider to start chatting.", title: "Welcome to PolyLLM", body: p(hi) + p("Your email is verified. PolyLLM is a universal control center for AI models: connect your own API keys, discover the latest models, configure them precisely, and chat with every provider through one interface.") + p("Next step: connect at least one provider (OpenAI, Anthropic, Gemini, xAI, Mistral, DeepSeek, Kimi, OpenRouter or Cerebras). Your keys are encrypted at rest and only decrypted on the server immediately before a request."), cta: { label: "Connect a provider", url: `${siteUrl}/app/settings/providers` }, }), text: `${name ? `Hi ${name},` : "Hi,"}\n\nYour email is verified. Connect at least one provider to start chatting:\n${siteUrl}/app/settings/providers${textFooter(siteUrl)}`, }; } export function passwordResetEmail(siteUrl: string, url: string): EmailContent { return { subject: "Reset your PolyLLM password", html: layout({ siteUrl, preheader: "Use this link to choose a new password.", title: "Reset your password", body: p("We received a request to reset the password of your PolyLLM account. Click below to choose a new one.") + p("This link expires in 1 hour and can be used once."), cta: { label: "Choose a new password", url }, footnote: "If you didn't request a password reset, no action is needed — your password stays unchanged.", }), text: `Reset your password\n\nOpen this link to choose a new password (expires in 1 hour):\n\n${url}\n\nIf you didn't request this, ignore this email.${textFooter(siteUrl)}`, }; } export function passwordChangedEmail(siteUrl: string, when: string, ip?: string | null): EmailContent { const where = ip ? ` from IP ${escapeHtml(ip)}` : ""; return { subject: "Your PolyLLM password was changed", html: layout({ siteUrl, preheader: "Security notice: your password was changed.", title: "Password changed", body: p(`The password of your PolyLLM account was changed on ${escapeHtml(when)}${where}.`) + p("If this was you, no action is needed. Other sessions have been signed out."), cta: { label: "Review security settings", url: `${siteUrl}/app/settings/security` }, footnote: "If you did not make this change, reset your password immediately and review your active sessions.", }), text: `Password changed\n\nThe password of your PolyLLM account was changed on ${when}${ip ? ` from IP ${ip}` : ""}. If this wasn't you, reset your password immediately: ${siteUrl}/forgot-password${textFooter(siteUrl)}`, }; } export function emailChangeApprovalEmail(siteUrl: string, url: string, newEmail: string): EmailContent { return { subject: "Approve your email change — PolyLLM", html: layout({ siteUrl, preheader: "Confirm that you want to change the email on your account.", title: "Approve email change", body: p(`You asked to change the email of your PolyLLM account to ${escapeHtml(newEmail)}.`) + p("Confirm this change from your current address by clicking below."), cta: { label: "Approve change", url }, footnote: "If you didn't request this, ignore this email — nothing changes without your approval.", }), text: `Approve email change\n\nYou asked to change your PolyLLM email to ${newEmail}. Confirm here:\n\n${url}${textFooter(siteUrl)}`, }; } export function emailChangedEmail(siteUrl: string, newEmail: string): EmailContent { return { subject: "Your PolyLLM email was changed", html: layout({ siteUrl, preheader: "Security notice: the email on your account changed.", title: "Email changed", body: p(`The email address of your PolyLLM account is now ${escapeHtml(newEmail)}.`), footnote: "If you did not make this change, contact us immediately by replying to this email.", }), text: `Email changed\n\nThe email of your PolyLLM account is now ${newEmail}. If this wasn't you, reply to this email immediately.${textFooter(siteUrl)}`, }; } export function accountDeletionEmail(siteUrl: string, url: string): EmailContent { return { subject: "Confirm account deletion — PolyLLM", html: layout({ siteUrl, preheader: "Confirm that you want to permanently delete your account.", title: "Delete your account?", body: p("You asked to permanently delete your PolyLLM account, including your conversations, presets, usage history and encrypted provider keys.") + p("This cannot be undone. Confirm by clicking below."), cta: { label: "Permanently delete my account", url }, footnote: "If you didn't request this, ignore this email — your account stays untouched.", }), text: `Delete your account?\n\nConfirm permanent deletion of your PolyLLM account (irreversible):\n\n${url}${textFooter(siteUrl)}`, }; } export function newLoginEmail(siteUrl: string, when: string, ip?: string | null, userAgent?: string | null): EmailContent { return { subject: "New sign-in to PolyLLM", html: layout({ siteUrl, preheader: "A new sign-in to your account was detected.", title: "New sign-in detected", body: p(`Your PolyLLM account was signed in on ${escapeHtml(when)}.`) + `