101 lines · 4.2 KB
import { env } from "cloudflare:workers";
import { publicUrl } from "./events/emit";
import type { QueueMessage } from "./events/emit";

export type EmailQueueMessage = Extract<QueueMessage, { kind: "email.send" }>;
type EmailContent = { subject: string; text: string; html: string };

const HTML_ESCAPES: Record<string, string> = { "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" };

function escapeHtml(value: string): string {
  return value.replace(/[&<>"']/g, (char) => HTML_ESCAPES[char] ?? char);
}

function forgeName(): string {
  return env.FORGE_NAME || "gitfor";
}

function layout(heading: string, paragraphs: string[], action: { label: string; url: string } | null): string {
  const body = paragraphs.map((text) => `<p style="margin:0 0 12px;color:#d4d4d8">${escapeHtml(text)}</p>`).join("");
  const button = action
    ? `<p style="margin:20px 0"><a href="${escapeHtml(action.url)}" style="background:#f97316;color:#09090b;padding:10px 16px;border-radius:6px;text-decoration:none;font-weight:600">${escapeHtml(action.label)}</a></p>
       <p style="margin:0;color:#71717a;font-size:12px">${escapeHtml(action.url)}</p>`
    : "";
  return `<!doctype html><html><body style="margin:0;background:#09090b;font-family:'IBM Plex Sans',system-ui,sans-serif">
<div style="max-width:560px;margin:0 auto;padding:32px 24px">
<div style="color:#fb923c;font-size:11px;letter-spacing:.12em;text-transform:uppercase;margin-bottom:8px">${escapeHtml(forgeName())}</div>
<h1 style="color:#fafafa;font-size:20px;margin:0 0 16px">${escapeHtml(heading)}</h1>${body}${button}
</div></body></html>`;
}

function render(subject: string, paragraphs: string[], action: { label: string; url: string } | null): EmailContent {
  const text = [...paragraphs, ...(action ? [`${action.label}: ${action.url}`] : [])].join("\n\n");
  return { subject: `[${forgeName()}] ${subject}`, text, html: layout(subject, paragraphs, action) };
}

export const emailTemplates = {
  verifyEmail(name: string, url: string): EmailContent {
    return render(
      "Verify your email address",
      [`Hi ${name}, confirm this address to finish setting up your account.`, "The link expires in 24 hours."],