@lacspace/email-templates 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Lacspace
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/dist/index.cjs ADDED
@@ -0,0 +1,164 @@
1
+ 'use strict';
2
+
3
+ // src/index.ts
4
+ var defaultTheme = {
5
+ brandColor: "#2563eb",
6
+ bg: "#f4f5f7",
7
+ cardBg: "#ffffff",
8
+ textColor: "#1f2937",
9
+ mutedColor: "#6b7280",
10
+ borderColor: "#e5e7eb",
11
+ fontFamily: "-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif",
12
+ borderRadius: "12px"
13
+ };
14
+ function escapeHtml(input) {
15
+ return String(input).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
16
+ }
17
+ function theme(t) {
18
+ return { ...defaultTheme, ...t };
19
+ }
20
+ function heading(text2, opts = {}) {
21
+ const t = theme(opts.theme);
22
+ const size = opts.level === 3 ? 18 : opts.level === 2 ? 22 : 26;
23
+ return `<h1 style="margin:0 0 12px;font-family:${t.fontFamily};font-size:${size}px;line-height:1.3;font-weight:700;color:${t.textColor};">${escapeHtml(text2)}</h1>`;
24
+ }
25
+ function text(content, opts = {}) {
26
+ const t = theme(opts.theme);
27
+ const color = opts.muted ? t.mutedColor : t.textColor;
28
+ return `<p style="margin:0 0 16px;font-family:${t.fontFamily};font-size:15px;line-height:1.6;color:${color};">${escapeHtml(content)}</p>`;
29
+ }
30
+ function html(raw) {
31
+ return raw;
32
+ }
33
+ function button(label, href, opts = {}) {
34
+ const t = theme(opts.theme);
35
+ const safeHref = escapeHtml(href);
36
+ return `<table role="presentation" cellpadding="0" cellspacing="0" style="margin:8px 0 20px;"><tr><td align="center" bgcolor="${t.brandColor}" style="border-radius:8px;">
37
+ <a href="${safeHref}" target="_blank" style="display:inline-block;padding:12px 28px;font-family:${t.fontFamily};font-size:15px;font-weight:600;color:#ffffff;text-decoration:none;border-radius:8px;background:${t.brandColor};">${escapeHtml(label)}</a>
38
+ </td></tr></table>`;
39
+ }
40
+ function code(value, opts = {}) {
41
+ const t = theme(opts.theme);
42
+ return `<div style="margin:0 0 20px;padding:16px;text-align:center;background:${t.bg};border:1px solid ${t.borderColor};border-radius:${t.borderRadius};font-family:'SFMono-Regular',Consolas,Menlo,monospace;font-size:32px;font-weight:700;letter-spacing:8px;color:${t.textColor};">${escapeHtml(value)}</div>`;
43
+ }
44
+ function divider(opts = {}) {
45
+ const t = theme(opts.theme);
46
+ return `<hr style="border:none;border-top:1px solid ${t.borderColor};margin:24px 0;" />`;
47
+ }
48
+ function spacer(height = 16) {
49
+ return `<div style="line-height:${height}px;height:${height}px;">&nbsp;</div>`;
50
+ }
51
+ function image(src, opts = {}) {
52
+ const w = opts.width ? `width="${opts.width}" style="max-width:100%;height:auto;display:block;margin:0 auto;"` : `style="max-width:100%;height:auto;display:block;margin:0 auto;"`;
53
+ return `<img src="${escapeHtml(src)}" alt="${escapeHtml(opts.alt ?? "")}" ${w} />`;
54
+ }
55
+ function list(items, opts = {}) {
56
+ const t = theme(opts.theme);
57
+ const lis = items.map(
58
+ (i) => `<li style="margin:0 0 8px;font-family:${t.fontFamily};font-size:15px;line-height:1.6;color:${t.textColor};">${escapeHtml(i)}</li>`
59
+ ).join("");
60
+ return `<ul style="margin:0 0 16px;padding-left:20px;">${lis}</ul>`;
61
+ }
62
+ function keyValue(rows, opts = {}) {
63
+ const t = theme(opts.theme);
64
+ const body = rows.map(
65
+ ([k, v]) => `<tr><td style="padding:8px 0;font-family:${t.fontFamily};font-size:14px;color:${t.mutedColor};">${escapeHtml(k)}</td><td align="right" style="padding:8px 0;font-family:${t.fontFamily};font-size:14px;font-weight:600;color:${t.textColor};">${escapeHtml(v)}</td></tr>`
66
+ ).join("");
67
+ return `<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="margin:0 0 16px;border-top:1px solid ${t.borderColor};">${body}</table>`;
68
+ }
69
+ function render(opts, blocks) {
70
+ const t = theme(opts.theme);
71
+ const width = opts.width ?? 600;
72
+ const preheader = opts.preheader ? `<div style="display:none;max-height:0;overflow:hidden;opacity:0;">${escapeHtml(opts.preheader)}</div>` : "";
73
+ const brand = opts.logoUrl ? `<img src="${escapeHtml(opts.logoUrl)}" alt="${escapeHtml(opts.brandName ?? "")}" height="32" style="display:block;height:32px;" />` : opts.brandName ? `<span style="font-family:${t.fontFamily};font-size:18px;font-weight:700;color:${t.brandColor};">${escapeHtml(opts.brandName)}</span>` : "";
74
+ const header = brand ? `<tr><td style="padding:0 0 20px;">${brand}</td></tr>` : "";
75
+ const footer = opts.footer ? `<tr><td style="padding:24px 8px 0;font-family:${t.fontFamily};font-size:12px;line-height:1.6;color:${t.mutedColor};text-align:center;">${opts.footer}</td></tr>` : "";
76
+ return `<!DOCTYPE html>
77
+ <html lang="en" xmlns="http://www.w3.org/1999/xhtml">
78
+ <head>
79
+ <meta charset="utf-8" />
80
+ <meta name="viewport" content="width=device-width,initial-scale=1" />
81
+ <meta name="x-apple-disable-message-reformatting" />
82
+ <title>${escapeHtml(opts.title ?? "")}</title>
83
+ <style>
84
+ @media only screen and (max-width:600px){ .container{width:100%!important;} .px{padding-left:20px!important;padding-right:20px!important;} }
85
+ @media (prefers-color-scheme: dark){ body,.bg{background:#0b0b12!important;} .card{background:#16161f!important;} .t{color:#e5e7eb!important;} }
86
+ </style>
87
+ </head>
88
+ <body class="bg" style="margin:0;padding:0;background:${t.bg};">
89
+ ${preheader}
90
+ <table role="presentation" width="100%" cellpadding="0" cellspacing="0" class="bg" style="background:${t.bg};padding:32px 0;">
91
+ <tr><td align="center">
92
+ <table role="presentation" width="${width}" class="container" cellpadding="0" cellspacing="0" style="width:${width}px;max-width:${width}px;">
93
+ ${header}
94
+ <tr><td class="card px" style="background:${t.cardBg};border:1px solid ${t.borderColor};border-radius:${t.borderRadius};padding:32px;">
95
+ ${blocks.join("\n")}
96
+ </td></tr>
97
+ ${footer}
98
+ </table>
99
+ </td></tr>
100
+ </table>
101
+ </body>
102
+ </html>`;
103
+ }
104
+ function otpEmail(o) {
105
+ return render(
106
+ { title: o.heading ?? "Your verification code", preheader: `Your code is ${o.code}`, ...o },
107
+ [
108
+ heading(o.heading ?? "Verify your email", { theme: o.theme }),
109
+ text(o.message ?? "Use the code below to continue. Never share it with anyone.", {
110
+ theme: o.theme
111
+ }),
112
+ code(o.code, { theme: o.theme }),
113
+ text(
114
+ `This code expires in ${o.expiresMinutes ?? 10} minutes.`,
115
+ { muted: true, theme: o.theme }
116
+ )
117
+ ]
118
+ );
119
+ }
120
+ function welcomeEmail(o) {
121
+ const blocks = [
122
+ heading(o.name ? `Welcome, ${o.name}!` : "Welcome aboard!", { theme: o.theme }),
123
+ text(o.message ?? "We're thrilled to have you. Let's get you started.", { theme: o.theme })
124
+ ];
125
+ if (o.ctaHref) blocks.push(button(o.ctaLabel ?? "Get started", o.ctaHref, { theme: o.theme }));
126
+ return render({ title: "Welcome", preheader: o.message, ...o }, blocks);
127
+ }
128
+ function alertEmail(o) {
129
+ const blocks = [
130
+ heading(o.title, { theme: o.theme }),
131
+ text(o.message, { theme: o.theme })
132
+ ];
133
+ if (o.ctaHref) blocks.push(button(o.ctaLabel ?? "View details", o.ctaHref, { theme: o.theme }));
134
+ return render({ preheader: o.message, ...o }, blocks);
135
+ }
136
+ function invoiceEmail(o) {
137
+ const blocks = [
138
+ heading(o.heading ?? "Payment receipt", { theme: o.theme }),
139
+ text(o.intro ?? "Thanks for your payment. Here's your receipt.", { theme: o.theme }),
140
+ keyValue(o.total ? [...o.rows, o.total] : o.rows, { theme: o.theme })
141
+ ];
142
+ if (o.ctaHref) blocks.push(button(o.ctaLabel ?? "View invoice", o.ctaHref, { theme: o.theme }));
143
+ return render({ title: o.heading ?? "Receipt", ...o }, blocks);
144
+ }
145
+
146
+ exports.alertEmail = alertEmail;
147
+ exports.button = button;
148
+ exports.code = code;
149
+ exports.defaultTheme = defaultTheme;
150
+ exports.divider = divider;
151
+ exports.escapeHtml = escapeHtml;
152
+ exports.heading = heading;
153
+ exports.html = html;
154
+ exports.image = image;
155
+ exports.invoiceEmail = invoiceEmail;
156
+ exports.keyValue = keyValue;
157
+ exports.list = list;
158
+ exports.otpEmail = otpEmail;
159
+ exports.render = render;
160
+ exports.spacer = spacer;
161
+ exports.text = text;
162
+ exports.welcomeEmail = welcomeEmail;
163
+ //# sourceMappingURL=index.cjs.map
164
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"names":["text"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * @lacspace/email-templates\n * Compose bulletproof, responsive HTML emails from simple blocks.\n *\n * No more hand-writing <table> soup. Components return email-client-safe HTML\n * with inline styles; `render()` wraps them in a responsive, dark-mode-aware\n * layout. Ships with ready-made OTP / welcome / alert / invoice templates.\n *\n * Zero dependencies · isomorphic · fully typed.\n */\n\nexport interface Theme {\n brandColor: string;\n bg: string;\n cardBg: string;\n textColor: string;\n mutedColor: string;\n borderColor: string;\n fontFamily: string;\n borderRadius: string;\n}\n\nexport const defaultTheme: Theme = {\n brandColor: \"#2563eb\",\n bg: \"#f4f5f7\",\n cardBg: \"#ffffff\",\n textColor: \"#1f2937\",\n mutedColor: \"#6b7280\",\n borderColor: \"#e5e7eb\",\n fontFamily:\n \"-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif\",\n borderRadius: \"12px\",\n};\n\n/** Escape untrusted text before placing it in HTML. */\nexport function escapeHtml(input: string): string {\n return String(input)\n .replace(/&/g, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\")\n .replace(/\"/g, \"&quot;\")\n .replace(/'/g, \"&#39;\");\n}\n\nfunction theme(t?: Partial<Theme>): Theme {\n return { ...defaultTheme, ...t };\n}\n\n/* ------------------------------------------------------------------ *\n * Blocks — each returns an HTML string. Compose them into render().\n * ------------------------------------------------------------------ */\n\nexport function heading(text: string, opts: { level?: 1 | 2 | 3; theme?: Partial<Theme> } = {}): string {\n const t = theme(opts.theme);\n const size = opts.level === 3 ? 18 : opts.level === 2 ? 22 : 26;\n return `<h1 style=\"margin:0 0 12px;font-family:${t.fontFamily};font-size:${size}px;line-height:1.3;font-weight:700;color:${t.textColor};\">${escapeHtml(text)}</h1>`;\n}\n\nexport function text(content: string, opts: { muted?: boolean; theme?: Partial<Theme> } = {}): string {\n const t = theme(opts.theme);\n const color = opts.muted ? t.mutedColor : t.textColor;\n return `<p style=\"margin:0 0 16px;font-family:${t.fontFamily};font-size:15px;line-height:1.6;color:${color};\">${escapeHtml(content)}</p>`;\n}\n\n/** Raw HTML passthrough — you are responsible for escaping. */\nexport function html(raw: string): string {\n return raw;\n}\n\n/** A bulletproof, table-based CTA button that renders in Outlook too. */\nexport function button(\n label: string,\n href: string,\n opts: { theme?: Partial<Theme> } = {},\n): string {\n const t = theme(opts.theme);\n const safeHref = escapeHtml(href);\n return `<table role=\"presentation\" cellpadding=\"0\" cellspacing=\"0\" style=\"margin:8px 0 20px;\"><tr><td align=\"center\" bgcolor=\"${t.brandColor}\" style=\"border-radius:8px;\">\n<a href=\"${safeHref}\" target=\"_blank\" style=\"display:inline-block;padding:12px 28px;font-family:${t.fontFamily};font-size:15px;font-weight:600;color:#ffffff;text-decoration:none;border-radius:8px;background:${t.brandColor};\">${escapeHtml(label)}</a>\n</td></tr></table>`;\n}\n\n/** A large, letter-spaced code block — perfect for OTPs. */\nexport function code(value: string, opts: { theme?: Partial<Theme> } = {}): string {\n const t = theme(opts.theme);\n return `<div style=\"margin:0 0 20px;padding:16px;text-align:center;background:${t.bg};border:1px solid ${t.borderColor};border-radius:${t.borderRadius};font-family:'SFMono-Regular',Consolas,Menlo,monospace;font-size:32px;font-weight:700;letter-spacing:8px;color:${t.textColor};\">${escapeHtml(value)}</div>`;\n}\n\nexport function divider(opts: { theme?: Partial<Theme> } = {}): string {\n const t = theme(opts.theme);\n return `<hr style=\"border:none;border-top:1px solid ${t.borderColor};margin:24px 0;\" />`;\n}\n\nexport function spacer(height = 16): string {\n return `<div style=\"line-height:${height}px;height:${height}px;\">&nbsp;</div>`;\n}\n\nexport function image(src: string, opts: { alt?: string; width?: number } = {}): string {\n const w = opts.width ? `width=\"${opts.width}\" style=\"max-width:100%;height:auto;display:block;margin:0 auto;\"` : `style=\"max-width:100%;height:auto;display:block;margin:0 auto;\"`;\n return `<img src=\"${escapeHtml(src)}\" alt=\"${escapeHtml(opts.alt ?? \"\")}\" ${w} />`;\n}\n\nexport function list(items: string[], opts: { theme?: Partial<Theme> } = {}): string {\n const t = theme(opts.theme);\n const lis = items\n .map(\n (i) =>\n `<li style=\"margin:0 0 8px;font-family:${t.fontFamily};font-size:15px;line-height:1.6;color:${t.textColor};\">${escapeHtml(i)}</li>`,\n )\n .join(\"\");\n return `<ul style=\"margin:0 0 16px;padding-left:20px;\">${lis}</ul>`;\n}\n\n/** A simple two-column key/value table — great for invoices & receipts. */\nexport function keyValue(\n rows: [string, string][],\n opts: { theme?: Partial<Theme> } = {},\n): string {\n const t = theme(opts.theme);\n const body = rows\n .map(\n ([k, v]) =>\n `<tr><td style=\"padding:8px 0;font-family:${t.fontFamily};font-size:14px;color:${t.mutedColor};\">${escapeHtml(k)}</td><td align=\"right\" style=\"padding:8px 0;font-family:${t.fontFamily};font-size:14px;font-weight:600;color:${t.textColor};\">${escapeHtml(v)}</td></tr>`,\n )\n .join(\"\");\n return `<table role=\"presentation\" width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" style=\"margin:0 0 16px;border-top:1px solid ${t.borderColor};\">${body}</table>`;\n}\n\n/* ------------------------------------------------------------------ *\n * Layout\n * ------------------------------------------------------------------ */\n\nexport interface EmailOptions {\n title?: string;\n /** Hidden inbox-preview text. */\n preheader?: string;\n theme?: Partial<Theme>;\n /** Footer HTML/text (already trusted). */\n footer?: string;\n brandName?: string;\n logoUrl?: string;\n width?: number;\n}\n\n/** Wrap composed blocks in a responsive, email-client-safe document. */\nexport function render(opts: EmailOptions, blocks: string[]): string {\n const t = theme(opts.theme);\n const width = opts.width ?? 600;\n const preheader = opts.preheader\n ? `<div style=\"display:none;max-height:0;overflow:hidden;opacity:0;\">${escapeHtml(opts.preheader)}</div>`\n : \"\";\n const brand = opts.logoUrl\n ? `<img src=\"${escapeHtml(opts.logoUrl)}\" alt=\"${escapeHtml(opts.brandName ?? \"\")}\" height=\"32\" style=\"display:block;height:32px;\" />`\n : opts.brandName\n ? `<span style=\"font-family:${t.fontFamily};font-size:18px;font-weight:700;color:${t.brandColor};\">${escapeHtml(opts.brandName)}</span>`\n : \"\";\n const header = brand ? `<tr><td style=\"padding:0 0 20px;\">${brand}</td></tr>` : \"\";\n const footer = opts.footer\n ? `<tr><td style=\"padding:24px 8px 0;font-family:${t.fontFamily};font-size:12px;line-height:1.6;color:${t.mutedColor};text-align:center;\">${opts.footer}</td></tr>`\n : \"\";\n\n return `<!DOCTYPE html>\n<html lang=\"en\" xmlns=\"http://www.w3.org/1999/xhtml\">\n<head>\n<meta charset=\"utf-8\" />\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1\" />\n<meta name=\"x-apple-disable-message-reformatting\" />\n<title>${escapeHtml(opts.title ?? \"\")}</title>\n<style>\n @media only screen and (max-width:600px){ .container{width:100%!important;} .px{padding-left:20px!important;padding-right:20px!important;} }\n @media (prefers-color-scheme: dark){ body,.bg{background:#0b0b12!important;} .card{background:#16161f!important;} .t{color:#e5e7eb!important;} }\n</style>\n</head>\n<body class=\"bg\" style=\"margin:0;padding:0;background:${t.bg};\">\n${preheader}\n<table role=\"presentation\" width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" class=\"bg\" style=\"background:${t.bg};padding:32px 0;\">\n<tr><td align=\"center\">\n<table role=\"presentation\" width=\"${width}\" class=\"container\" cellpadding=\"0\" cellspacing=\"0\" style=\"width:${width}px;max-width:${width}px;\">\n${header}\n<tr><td class=\"card px\" style=\"background:${t.cardBg};border:1px solid ${t.borderColor};border-radius:${t.borderRadius};padding:32px;\">\n${blocks.join(\"\\n\")}\n</td></tr>\n${footer}\n</table>\n</td></tr>\n</table>\n</body>\n</html>`;\n}\n\n/* ------------------------------------------------------------------ *\n * Ready-made templates\n * ------------------------------------------------------------------ */\n\nexport interface BrandInfo {\n brandName?: string;\n logoUrl?: string;\n theme?: Partial<Theme>;\n footer?: string;\n}\n\n/** One-time-passcode email. */\nexport function otpEmail(\n o: { code: string; heading?: string; message?: string; expiresMinutes?: number } & BrandInfo,\n): string {\n return render(\n { title: o.heading ?? \"Your verification code\", preheader: `Your code is ${o.code}`, ...o },\n [\n heading(o.heading ?? \"Verify your email\", { theme: o.theme }),\n text(o.message ?? \"Use the code below to continue. Never share it with anyone.\", {\n theme: o.theme,\n }),\n code(o.code, { theme: o.theme }),\n text(\n `This code expires in ${o.expiresMinutes ?? 10} minutes.`,\n { muted: true, theme: o.theme },\n ),\n ],\n );\n}\n\n/** Welcome / onboarding email with a CTA. */\nexport function welcomeEmail(\n o: { name?: string; message?: string; ctaLabel?: string; ctaHref?: string } & BrandInfo,\n): string {\n const blocks = [\n heading(o.name ? `Welcome, ${o.name}!` : \"Welcome aboard!\", { theme: o.theme }),\n text(o.message ?? \"We're thrilled to have you. Let's get you started.\", { theme: o.theme }),\n ];\n if (o.ctaHref) blocks.push(button(o.ctaLabel ?? \"Get started\", o.ctaHref, { theme: o.theme }));\n return render({ title: \"Welcome\", preheader: o.message, ...o }, blocks);\n}\n\n/** Alert / notification email. */\nexport function alertEmail(\n o: { title: string; message: string; ctaLabel?: string; ctaHref?: string } & BrandInfo,\n): string {\n const blocks = [\n heading(o.title, { theme: o.theme }),\n text(o.message, { theme: o.theme }),\n ];\n if (o.ctaHref) blocks.push(button(o.ctaLabel ?? \"View details\", o.ctaHref, { theme: o.theme }));\n return render({ preheader: o.message, ...o }, blocks);\n}\n\n/** Invoice / receipt email. */\nexport function invoiceEmail(\n o: {\n heading?: string;\n intro?: string;\n rows: [string, string][];\n total?: [string, string];\n ctaLabel?: string;\n ctaHref?: string;\n } & BrandInfo,\n): string {\n const blocks = [\n heading(o.heading ?? \"Payment receipt\", { theme: o.theme }),\n text(o.intro ?? \"Thanks for your payment. Here's your receipt.\", { theme: o.theme }),\n keyValue(o.total ? [...o.rows, o.total] : o.rows, { theme: o.theme }),\n ];\n if (o.ctaHref) blocks.push(button(o.ctaLabel ?? \"View invoice\", o.ctaHref, { theme: o.theme }));\n return render({ title: o.heading ?? \"Receipt\", ...o }, blocks);\n}\n"]}
@@ -0,0 +1,107 @@
1
+ /**
2
+ * @lacspace/email-templates
3
+ * Compose bulletproof, responsive HTML emails from simple blocks.
4
+ *
5
+ * No more hand-writing <table> soup. Components return email-client-safe HTML
6
+ * with inline styles; `render()` wraps them in a responsive, dark-mode-aware
7
+ * layout. Ships with ready-made OTP / welcome / alert / invoice templates.
8
+ *
9
+ * Zero dependencies · isomorphic · fully typed.
10
+ */
11
+ interface Theme {
12
+ brandColor: string;
13
+ bg: string;
14
+ cardBg: string;
15
+ textColor: string;
16
+ mutedColor: string;
17
+ borderColor: string;
18
+ fontFamily: string;
19
+ borderRadius: string;
20
+ }
21
+ declare const defaultTheme: Theme;
22
+ /** Escape untrusted text before placing it in HTML. */
23
+ declare function escapeHtml(input: string): string;
24
+ declare function heading(text: string, opts?: {
25
+ level?: 1 | 2 | 3;
26
+ theme?: Partial<Theme>;
27
+ }): string;
28
+ declare function text(content: string, opts?: {
29
+ muted?: boolean;
30
+ theme?: Partial<Theme>;
31
+ }): string;
32
+ /** Raw HTML passthrough — you are responsible for escaping. */
33
+ declare function html(raw: string): string;
34
+ /** A bulletproof, table-based CTA button that renders in Outlook too. */
35
+ declare function button(label: string, href: string, opts?: {
36
+ theme?: Partial<Theme>;
37
+ }): string;
38
+ /** A large, letter-spaced code block — perfect for OTPs. */
39
+ declare function code(value: string, opts?: {
40
+ theme?: Partial<Theme>;
41
+ }): string;
42
+ declare function divider(opts?: {
43
+ theme?: Partial<Theme>;
44
+ }): string;
45
+ declare function spacer(height?: number): string;
46
+ declare function image(src: string, opts?: {
47
+ alt?: string;
48
+ width?: number;
49
+ }): string;
50
+ declare function list(items: string[], opts?: {
51
+ theme?: Partial<Theme>;
52
+ }): string;
53
+ /** A simple two-column key/value table — great for invoices & receipts. */
54
+ declare function keyValue(rows: [string, string][], opts?: {
55
+ theme?: Partial<Theme>;
56
+ }): string;
57
+ interface EmailOptions {
58
+ title?: string;
59
+ /** Hidden inbox-preview text. */
60
+ preheader?: string;
61
+ theme?: Partial<Theme>;
62
+ /** Footer HTML/text (already trusted). */
63
+ footer?: string;
64
+ brandName?: string;
65
+ logoUrl?: string;
66
+ width?: number;
67
+ }
68
+ /** Wrap composed blocks in a responsive, email-client-safe document. */
69
+ declare function render(opts: EmailOptions, blocks: string[]): string;
70
+ interface BrandInfo {
71
+ brandName?: string;
72
+ logoUrl?: string;
73
+ theme?: Partial<Theme>;
74
+ footer?: string;
75
+ }
76
+ /** One-time-passcode email. */
77
+ declare function otpEmail(o: {
78
+ code: string;
79
+ heading?: string;
80
+ message?: string;
81
+ expiresMinutes?: number;
82
+ } & BrandInfo): string;
83
+ /** Welcome / onboarding email with a CTA. */
84
+ declare function welcomeEmail(o: {
85
+ name?: string;
86
+ message?: string;
87
+ ctaLabel?: string;
88
+ ctaHref?: string;
89
+ } & BrandInfo): string;
90
+ /** Alert / notification email. */
91
+ declare function alertEmail(o: {
92
+ title: string;
93
+ message: string;
94
+ ctaLabel?: string;
95
+ ctaHref?: string;
96
+ } & BrandInfo): string;
97
+ /** Invoice / receipt email. */
98
+ declare function invoiceEmail(o: {
99
+ heading?: string;
100
+ intro?: string;
101
+ rows: [string, string][];
102
+ total?: [string, string];
103
+ ctaLabel?: string;
104
+ ctaHref?: string;
105
+ } & BrandInfo): string;
106
+
107
+ export { type BrandInfo, type EmailOptions, type Theme, alertEmail, button, code, defaultTheme, divider, escapeHtml, heading, html, image, invoiceEmail, keyValue, list, otpEmail, render, spacer, text, welcomeEmail };
@@ -0,0 +1,107 @@
1
+ /**
2
+ * @lacspace/email-templates
3
+ * Compose bulletproof, responsive HTML emails from simple blocks.
4
+ *
5
+ * No more hand-writing <table> soup. Components return email-client-safe HTML
6
+ * with inline styles; `render()` wraps them in a responsive, dark-mode-aware
7
+ * layout. Ships with ready-made OTP / welcome / alert / invoice templates.
8
+ *
9
+ * Zero dependencies · isomorphic · fully typed.
10
+ */
11
+ interface Theme {
12
+ brandColor: string;
13
+ bg: string;
14
+ cardBg: string;
15
+ textColor: string;
16
+ mutedColor: string;
17
+ borderColor: string;
18
+ fontFamily: string;
19
+ borderRadius: string;
20
+ }
21
+ declare const defaultTheme: Theme;
22
+ /** Escape untrusted text before placing it in HTML. */
23
+ declare function escapeHtml(input: string): string;
24
+ declare function heading(text: string, opts?: {
25
+ level?: 1 | 2 | 3;
26
+ theme?: Partial<Theme>;
27
+ }): string;
28
+ declare function text(content: string, opts?: {
29
+ muted?: boolean;
30
+ theme?: Partial<Theme>;
31
+ }): string;
32
+ /** Raw HTML passthrough — you are responsible for escaping. */
33
+ declare function html(raw: string): string;
34
+ /** A bulletproof, table-based CTA button that renders in Outlook too. */
35
+ declare function button(label: string, href: string, opts?: {
36
+ theme?: Partial<Theme>;
37
+ }): string;
38
+ /** A large, letter-spaced code block — perfect for OTPs. */
39
+ declare function code(value: string, opts?: {
40
+ theme?: Partial<Theme>;
41
+ }): string;
42
+ declare function divider(opts?: {
43
+ theme?: Partial<Theme>;
44
+ }): string;
45
+ declare function spacer(height?: number): string;
46
+ declare function image(src: string, opts?: {
47
+ alt?: string;
48
+ width?: number;
49
+ }): string;
50
+ declare function list(items: string[], opts?: {
51
+ theme?: Partial<Theme>;
52
+ }): string;
53
+ /** A simple two-column key/value table — great for invoices & receipts. */
54
+ declare function keyValue(rows: [string, string][], opts?: {
55
+ theme?: Partial<Theme>;
56
+ }): string;
57
+ interface EmailOptions {
58
+ title?: string;
59
+ /** Hidden inbox-preview text. */
60
+ preheader?: string;
61
+ theme?: Partial<Theme>;
62
+ /** Footer HTML/text (already trusted). */
63
+ footer?: string;
64
+ brandName?: string;
65
+ logoUrl?: string;
66
+ width?: number;
67
+ }
68
+ /** Wrap composed blocks in a responsive, email-client-safe document. */
69
+ declare function render(opts: EmailOptions, blocks: string[]): string;
70
+ interface BrandInfo {
71
+ brandName?: string;
72
+ logoUrl?: string;
73
+ theme?: Partial<Theme>;
74
+ footer?: string;
75
+ }
76
+ /** One-time-passcode email. */
77
+ declare function otpEmail(o: {
78
+ code: string;
79
+ heading?: string;
80
+ message?: string;
81
+ expiresMinutes?: number;
82
+ } & BrandInfo): string;
83
+ /** Welcome / onboarding email with a CTA. */
84
+ declare function welcomeEmail(o: {
85
+ name?: string;
86
+ message?: string;
87
+ ctaLabel?: string;
88
+ ctaHref?: string;
89
+ } & BrandInfo): string;
90
+ /** Alert / notification email. */
91
+ declare function alertEmail(o: {
92
+ title: string;
93
+ message: string;
94
+ ctaLabel?: string;
95
+ ctaHref?: string;
96
+ } & BrandInfo): string;
97
+ /** Invoice / receipt email. */
98
+ declare function invoiceEmail(o: {
99
+ heading?: string;
100
+ intro?: string;
101
+ rows: [string, string][];
102
+ total?: [string, string];
103
+ ctaLabel?: string;
104
+ ctaHref?: string;
105
+ } & BrandInfo): string;
106
+
107
+ export { type BrandInfo, type EmailOptions, type Theme, alertEmail, button, code, defaultTheme, divider, escapeHtml, heading, html, image, invoiceEmail, keyValue, list, otpEmail, render, spacer, text, welcomeEmail };
package/dist/index.js ADDED
@@ -0,0 +1,146 @@
1
+ // src/index.ts
2
+ var defaultTheme = {
3
+ brandColor: "#2563eb",
4
+ bg: "#f4f5f7",
5
+ cardBg: "#ffffff",
6
+ textColor: "#1f2937",
7
+ mutedColor: "#6b7280",
8
+ borderColor: "#e5e7eb",
9
+ fontFamily: "-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif",
10
+ borderRadius: "12px"
11
+ };
12
+ function escapeHtml(input) {
13
+ return String(input).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
14
+ }
15
+ function theme(t) {
16
+ return { ...defaultTheme, ...t };
17
+ }
18
+ function heading(text2, opts = {}) {
19
+ const t = theme(opts.theme);
20
+ const size = opts.level === 3 ? 18 : opts.level === 2 ? 22 : 26;
21
+ return `<h1 style="margin:0 0 12px;font-family:${t.fontFamily};font-size:${size}px;line-height:1.3;font-weight:700;color:${t.textColor};">${escapeHtml(text2)}</h1>`;
22
+ }
23
+ function text(content, opts = {}) {
24
+ const t = theme(opts.theme);
25
+ const color = opts.muted ? t.mutedColor : t.textColor;
26
+ return `<p style="margin:0 0 16px;font-family:${t.fontFamily};font-size:15px;line-height:1.6;color:${color};">${escapeHtml(content)}</p>`;
27
+ }
28
+ function html(raw) {
29
+ return raw;
30
+ }
31
+ function button(label, href, opts = {}) {
32
+ const t = theme(opts.theme);
33
+ const safeHref = escapeHtml(href);
34
+ return `<table role="presentation" cellpadding="0" cellspacing="0" style="margin:8px 0 20px;"><tr><td align="center" bgcolor="${t.brandColor}" style="border-radius:8px;">
35
+ <a href="${safeHref}" target="_blank" style="display:inline-block;padding:12px 28px;font-family:${t.fontFamily};font-size:15px;font-weight:600;color:#ffffff;text-decoration:none;border-radius:8px;background:${t.brandColor};">${escapeHtml(label)}</a>
36
+ </td></tr></table>`;
37
+ }
38
+ function code(value, opts = {}) {
39
+ const t = theme(opts.theme);
40
+ return `<div style="margin:0 0 20px;padding:16px;text-align:center;background:${t.bg};border:1px solid ${t.borderColor};border-radius:${t.borderRadius};font-family:'SFMono-Regular',Consolas,Menlo,monospace;font-size:32px;font-weight:700;letter-spacing:8px;color:${t.textColor};">${escapeHtml(value)}</div>`;
41
+ }
42
+ function divider(opts = {}) {
43
+ const t = theme(opts.theme);
44
+ return `<hr style="border:none;border-top:1px solid ${t.borderColor};margin:24px 0;" />`;
45
+ }
46
+ function spacer(height = 16) {
47
+ return `<div style="line-height:${height}px;height:${height}px;">&nbsp;</div>`;
48
+ }
49
+ function image(src, opts = {}) {
50
+ const w = opts.width ? `width="${opts.width}" style="max-width:100%;height:auto;display:block;margin:0 auto;"` : `style="max-width:100%;height:auto;display:block;margin:0 auto;"`;
51
+ return `<img src="${escapeHtml(src)}" alt="${escapeHtml(opts.alt ?? "")}" ${w} />`;
52
+ }
53
+ function list(items, opts = {}) {
54
+ const t = theme(opts.theme);
55
+ const lis = items.map(
56
+ (i) => `<li style="margin:0 0 8px;font-family:${t.fontFamily};font-size:15px;line-height:1.6;color:${t.textColor};">${escapeHtml(i)}</li>`
57
+ ).join("");
58
+ return `<ul style="margin:0 0 16px;padding-left:20px;">${lis}</ul>`;
59
+ }
60
+ function keyValue(rows, opts = {}) {
61
+ const t = theme(opts.theme);
62
+ const body = rows.map(
63
+ ([k, v]) => `<tr><td style="padding:8px 0;font-family:${t.fontFamily};font-size:14px;color:${t.mutedColor};">${escapeHtml(k)}</td><td align="right" style="padding:8px 0;font-family:${t.fontFamily};font-size:14px;font-weight:600;color:${t.textColor};">${escapeHtml(v)}</td></tr>`
64
+ ).join("");
65
+ return `<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="margin:0 0 16px;border-top:1px solid ${t.borderColor};">${body}</table>`;
66
+ }
67
+ function render(opts, blocks) {
68
+ const t = theme(opts.theme);
69
+ const width = opts.width ?? 600;
70
+ const preheader = opts.preheader ? `<div style="display:none;max-height:0;overflow:hidden;opacity:0;">${escapeHtml(opts.preheader)}</div>` : "";
71
+ const brand = opts.logoUrl ? `<img src="${escapeHtml(opts.logoUrl)}" alt="${escapeHtml(opts.brandName ?? "")}" height="32" style="display:block;height:32px;" />` : opts.brandName ? `<span style="font-family:${t.fontFamily};font-size:18px;font-weight:700;color:${t.brandColor};">${escapeHtml(opts.brandName)}</span>` : "";
72
+ const header = brand ? `<tr><td style="padding:0 0 20px;">${brand}</td></tr>` : "";
73
+ const footer = opts.footer ? `<tr><td style="padding:24px 8px 0;font-family:${t.fontFamily};font-size:12px;line-height:1.6;color:${t.mutedColor};text-align:center;">${opts.footer}</td></tr>` : "";
74
+ return `<!DOCTYPE html>
75
+ <html lang="en" xmlns="http://www.w3.org/1999/xhtml">
76
+ <head>
77
+ <meta charset="utf-8" />
78
+ <meta name="viewport" content="width=device-width,initial-scale=1" />
79
+ <meta name="x-apple-disable-message-reformatting" />
80
+ <title>${escapeHtml(opts.title ?? "")}</title>
81
+ <style>
82
+ @media only screen and (max-width:600px){ .container{width:100%!important;} .px{padding-left:20px!important;padding-right:20px!important;} }
83
+ @media (prefers-color-scheme: dark){ body,.bg{background:#0b0b12!important;} .card{background:#16161f!important;} .t{color:#e5e7eb!important;} }
84
+ </style>
85
+ </head>
86
+ <body class="bg" style="margin:0;padding:0;background:${t.bg};">
87
+ ${preheader}
88
+ <table role="presentation" width="100%" cellpadding="0" cellspacing="0" class="bg" style="background:${t.bg};padding:32px 0;">
89
+ <tr><td align="center">
90
+ <table role="presentation" width="${width}" class="container" cellpadding="0" cellspacing="0" style="width:${width}px;max-width:${width}px;">
91
+ ${header}
92
+ <tr><td class="card px" style="background:${t.cardBg};border:1px solid ${t.borderColor};border-radius:${t.borderRadius};padding:32px;">
93
+ ${blocks.join("\n")}
94
+ </td></tr>
95
+ ${footer}
96
+ </table>
97
+ </td></tr>
98
+ </table>
99
+ </body>
100
+ </html>`;
101
+ }
102
+ function otpEmail(o) {
103
+ return render(
104
+ { title: o.heading ?? "Your verification code", preheader: `Your code is ${o.code}`, ...o },
105
+ [
106
+ heading(o.heading ?? "Verify your email", { theme: o.theme }),
107
+ text(o.message ?? "Use the code below to continue. Never share it with anyone.", {
108
+ theme: o.theme
109
+ }),
110
+ code(o.code, { theme: o.theme }),
111
+ text(
112
+ `This code expires in ${o.expiresMinutes ?? 10} minutes.`,
113
+ { muted: true, theme: o.theme }
114
+ )
115
+ ]
116
+ );
117
+ }
118
+ function welcomeEmail(o) {
119
+ const blocks = [
120
+ heading(o.name ? `Welcome, ${o.name}!` : "Welcome aboard!", { theme: o.theme }),
121
+ text(o.message ?? "We're thrilled to have you. Let's get you started.", { theme: o.theme })
122
+ ];
123
+ if (o.ctaHref) blocks.push(button(o.ctaLabel ?? "Get started", o.ctaHref, { theme: o.theme }));
124
+ return render({ title: "Welcome", preheader: o.message, ...o }, blocks);
125
+ }
126
+ function alertEmail(o) {
127
+ const blocks = [
128
+ heading(o.title, { theme: o.theme }),
129
+ text(o.message, { theme: o.theme })
130
+ ];
131
+ if (o.ctaHref) blocks.push(button(o.ctaLabel ?? "View details", o.ctaHref, { theme: o.theme }));
132
+ return render({ preheader: o.message, ...o }, blocks);
133
+ }
134
+ function invoiceEmail(o) {
135
+ const blocks = [
136
+ heading(o.heading ?? "Payment receipt", { theme: o.theme }),
137
+ text(o.intro ?? "Thanks for your payment. Here's your receipt.", { theme: o.theme }),
138
+ keyValue(o.total ? [...o.rows, o.total] : o.rows, { theme: o.theme })
139
+ ];
140
+ if (o.ctaHref) blocks.push(button(o.ctaLabel ?? "View invoice", o.ctaHref, { theme: o.theme }));
141
+ return render({ title: o.heading ?? "Receipt", ...o }, blocks);
142
+ }
143
+
144
+ export { alertEmail, button, code, defaultTheme, divider, escapeHtml, heading, html, image, invoiceEmail, keyValue, list, otpEmail, render, spacer, text, welcomeEmail };
145
+ //# sourceMappingURL=index.js.map
146
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"names":["text"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * @lacspace/email-templates\n * Compose bulletproof, responsive HTML emails from simple blocks.\n *\n * No more hand-writing <table> soup. Components return email-client-safe HTML\n * with inline styles; `render()` wraps them in a responsive, dark-mode-aware\n * layout. Ships with ready-made OTP / welcome / alert / invoice templates.\n *\n * Zero dependencies · isomorphic · fully typed.\n */\n\nexport interface Theme {\n brandColor: string;\n bg: string;\n cardBg: string;\n textColor: string;\n mutedColor: string;\n borderColor: string;\n fontFamily: string;\n borderRadius: string;\n}\n\nexport const defaultTheme: Theme = {\n brandColor: \"#2563eb\",\n bg: \"#f4f5f7\",\n cardBg: \"#ffffff\",\n textColor: \"#1f2937\",\n mutedColor: \"#6b7280\",\n borderColor: \"#e5e7eb\",\n fontFamily:\n \"-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif\",\n borderRadius: \"12px\",\n};\n\n/** Escape untrusted text before placing it in HTML. */\nexport function escapeHtml(input: string): string {\n return String(input)\n .replace(/&/g, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\")\n .replace(/\"/g, \"&quot;\")\n .replace(/'/g, \"&#39;\");\n}\n\nfunction theme(t?: Partial<Theme>): Theme {\n return { ...defaultTheme, ...t };\n}\n\n/* ------------------------------------------------------------------ *\n * Blocks — each returns an HTML string. Compose them into render().\n * ------------------------------------------------------------------ */\n\nexport function heading(text: string, opts: { level?: 1 | 2 | 3; theme?: Partial<Theme> } = {}): string {\n const t = theme(opts.theme);\n const size = opts.level === 3 ? 18 : opts.level === 2 ? 22 : 26;\n return `<h1 style=\"margin:0 0 12px;font-family:${t.fontFamily};font-size:${size}px;line-height:1.3;font-weight:700;color:${t.textColor};\">${escapeHtml(text)}</h1>`;\n}\n\nexport function text(content: string, opts: { muted?: boolean; theme?: Partial<Theme> } = {}): string {\n const t = theme(opts.theme);\n const color = opts.muted ? t.mutedColor : t.textColor;\n return `<p style=\"margin:0 0 16px;font-family:${t.fontFamily};font-size:15px;line-height:1.6;color:${color};\">${escapeHtml(content)}</p>`;\n}\n\n/** Raw HTML passthrough — you are responsible for escaping. */\nexport function html(raw: string): string {\n return raw;\n}\n\n/** A bulletproof, table-based CTA button that renders in Outlook too. */\nexport function button(\n label: string,\n href: string,\n opts: { theme?: Partial<Theme> } = {},\n): string {\n const t = theme(opts.theme);\n const safeHref = escapeHtml(href);\n return `<table role=\"presentation\" cellpadding=\"0\" cellspacing=\"0\" style=\"margin:8px 0 20px;\"><tr><td align=\"center\" bgcolor=\"${t.brandColor}\" style=\"border-radius:8px;\">\n<a href=\"${safeHref}\" target=\"_blank\" style=\"display:inline-block;padding:12px 28px;font-family:${t.fontFamily};font-size:15px;font-weight:600;color:#ffffff;text-decoration:none;border-radius:8px;background:${t.brandColor};\">${escapeHtml(label)}</a>\n</td></tr></table>`;\n}\n\n/** A large, letter-spaced code block — perfect for OTPs. */\nexport function code(value: string, opts: { theme?: Partial<Theme> } = {}): string {\n const t = theme(opts.theme);\n return `<div style=\"margin:0 0 20px;padding:16px;text-align:center;background:${t.bg};border:1px solid ${t.borderColor};border-radius:${t.borderRadius};font-family:'SFMono-Regular',Consolas,Menlo,monospace;font-size:32px;font-weight:700;letter-spacing:8px;color:${t.textColor};\">${escapeHtml(value)}</div>`;\n}\n\nexport function divider(opts: { theme?: Partial<Theme> } = {}): string {\n const t = theme(opts.theme);\n return `<hr style=\"border:none;border-top:1px solid ${t.borderColor};margin:24px 0;\" />`;\n}\n\nexport function spacer(height = 16): string {\n return `<div style=\"line-height:${height}px;height:${height}px;\">&nbsp;</div>`;\n}\n\nexport function image(src: string, opts: { alt?: string; width?: number } = {}): string {\n const w = opts.width ? `width=\"${opts.width}\" style=\"max-width:100%;height:auto;display:block;margin:0 auto;\"` : `style=\"max-width:100%;height:auto;display:block;margin:0 auto;\"`;\n return `<img src=\"${escapeHtml(src)}\" alt=\"${escapeHtml(opts.alt ?? \"\")}\" ${w} />`;\n}\n\nexport function list(items: string[], opts: { theme?: Partial<Theme> } = {}): string {\n const t = theme(opts.theme);\n const lis = items\n .map(\n (i) =>\n `<li style=\"margin:0 0 8px;font-family:${t.fontFamily};font-size:15px;line-height:1.6;color:${t.textColor};\">${escapeHtml(i)}</li>`,\n )\n .join(\"\");\n return `<ul style=\"margin:0 0 16px;padding-left:20px;\">${lis}</ul>`;\n}\n\n/** A simple two-column key/value table — great for invoices & receipts. */\nexport function keyValue(\n rows: [string, string][],\n opts: { theme?: Partial<Theme> } = {},\n): string {\n const t = theme(opts.theme);\n const body = rows\n .map(\n ([k, v]) =>\n `<tr><td style=\"padding:8px 0;font-family:${t.fontFamily};font-size:14px;color:${t.mutedColor};\">${escapeHtml(k)}</td><td align=\"right\" style=\"padding:8px 0;font-family:${t.fontFamily};font-size:14px;font-weight:600;color:${t.textColor};\">${escapeHtml(v)}</td></tr>`,\n )\n .join(\"\");\n return `<table role=\"presentation\" width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" style=\"margin:0 0 16px;border-top:1px solid ${t.borderColor};\">${body}</table>`;\n}\n\n/* ------------------------------------------------------------------ *\n * Layout\n * ------------------------------------------------------------------ */\n\nexport interface EmailOptions {\n title?: string;\n /** Hidden inbox-preview text. */\n preheader?: string;\n theme?: Partial<Theme>;\n /** Footer HTML/text (already trusted). */\n footer?: string;\n brandName?: string;\n logoUrl?: string;\n width?: number;\n}\n\n/** Wrap composed blocks in a responsive, email-client-safe document. */\nexport function render(opts: EmailOptions, blocks: string[]): string {\n const t = theme(opts.theme);\n const width = opts.width ?? 600;\n const preheader = opts.preheader\n ? `<div style=\"display:none;max-height:0;overflow:hidden;opacity:0;\">${escapeHtml(opts.preheader)}</div>`\n : \"\";\n const brand = opts.logoUrl\n ? `<img src=\"${escapeHtml(opts.logoUrl)}\" alt=\"${escapeHtml(opts.brandName ?? \"\")}\" height=\"32\" style=\"display:block;height:32px;\" />`\n : opts.brandName\n ? `<span style=\"font-family:${t.fontFamily};font-size:18px;font-weight:700;color:${t.brandColor};\">${escapeHtml(opts.brandName)}</span>`\n : \"\";\n const header = brand ? `<tr><td style=\"padding:0 0 20px;\">${brand}</td></tr>` : \"\";\n const footer = opts.footer\n ? `<tr><td style=\"padding:24px 8px 0;font-family:${t.fontFamily};font-size:12px;line-height:1.6;color:${t.mutedColor};text-align:center;\">${opts.footer}</td></tr>`\n : \"\";\n\n return `<!DOCTYPE html>\n<html lang=\"en\" xmlns=\"http://www.w3.org/1999/xhtml\">\n<head>\n<meta charset=\"utf-8\" />\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1\" />\n<meta name=\"x-apple-disable-message-reformatting\" />\n<title>${escapeHtml(opts.title ?? \"\")}</title>\n<style>\n @media only screen and (max-width:600px){ .container{width:100%!important;} .px{padding-left:20px!important;padding-right:20px!important;} }\n @media (prefers-color-scheme: dark){ body,.bg{background:#0b0b12!important;} .card{background:#16161f!important;} .t{color:#e5e7eb!important;} }\n</style>\n</head>\n<body class=\"bg\" style=\"margin:0;padding:0;background:${t.bg};\">\n${preheader}\n<table role=\"presentation\" width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" class=\"bg\" style=\"background:${t.bg};padding:32px 0;\">\n<tr><td align=\"center\">\n<table role=\"presentation\" width=\"${width}\" class=\"container\" cellpadding=\"0\" cellspacing=\"0\" style=\"width:${width}px;max-width:${width}px;\">\n${header}\n<tr><td class=\"card px\" style=\"background:${t.cardBg};border:1px solid ${t.borderColor};border-radius:${t.borderRadius};padding:32px;\">\n${blocks.join(\"\\n\")}\n</td></tr>\n${footer}\n</table>\n</td></tr>\n</table>\n</body>\n</html>`;\n}\n\n/* ------------------------------------------------------------------ *\n * Ready-made templates\n * ------------------------------------------------------------------ */\n\nexport interface BrandInfo {\n brandName?: string;\n logoUrl?: string;\n theme?: Partial<Theme>;\n footer?: string;\n}\n\n/** One-time-passcode email. */\nexport function otpEmail(\n o: { code: string; heading?: string; message?: string; expiresMinutes?: number } & BrandInfo,\n): string {\n return render(\n { title: o.heading ?? \"Your verification code\", preheader: `Your code is ${o.code}`, ...o },\n [\n heading(o.heading ?? \"Verify your email\", { theme: o.theme }),\n text(o.message ?? \"Use the code below to continue. Never share it with anyone.\", {\n theme: o.theme,\n }),\n code(o.code, { theme: o.theme }),\n text(\n `This code expires in ${o.expiresMinutes ?? 10} minutes.`,\n { muted: true, theme: o.theme },\n ),\n ],\n );\n}\n\n/** Welcome / onboarding email with a CTA. */\nexport function welcomeEmail(\n o: { name?: string; message?: string; ctaLabel?: string; ctaHref?: string } & BrandInfo,\n): string {\n const blocks = [\n heading(o.name ? `Welcome, ${o.name}!` : \"Welcome aboard!\", { theme: o.theme }),\n text(o.message ?? \"We're thrilled to have you. Let's get you started.\", { theme: o.theme }),\n ];\n if (o.ctaHref) blocks.push(button(o.ctaLabel ?? \"Get started\", o.ctaHref, { theme: o.theme }));\n return render({ title: \"Welcome\", preheader: o.message, ...o }, blocks);\n}\n\n/** Alert / notification email. */\nexport function alertEmail(\n o: { title: string; message: string; ctaLabel?: string; ctaHref?: string } & BrandInfo,\n): string {\n const blocks = [\n heading(o.title, { theme: o.theme }),\n text(o.message, { theme: o.theme }),\n ];\n if (o.ctaHref) blocks.push(button(o.ctaLabel ?? \"View details\", o.ctaHref, { theme: o.theme }));\n return render({ preheader: o.message, ...o }, blocks);\n}\n\n/** Invoice / receipt email. */\nexport function invoiceEmail(\n o: {\n heading?: string;\n intro?: string;\n rows: [string, string][];\n total?: [string, string];\n ctaLabel?: string;\n ctaHref?: string;\n } & BrandInfo,\n): string {\n const blocks = [\n heading(o.heading ?? \"Payment receipt\", { theme: o.theme }),\n text(o.intro ?? \"Thanks for your payment. Here's your receipt.\", { theme: o.theme }),\n keyValue(o.total ? [...o.rows, o.total] : o.rows, { theme: o.theme }),\n ];\n if (o.ctaHref) blocks.push(button(o.ctaLabel ?? \"View invoice\", o.ctaHref, { theme: o.theme }));\n return render({ title: o.heading ?? \"Receipt\", ...o }, blocks);\n}\n"]}
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "@lacspace/email-templates",
3
+ "version": "1.0.0",
4
+ "description": "Compose bulletproof, responsive, dark-mode-aware HTML emails from simple blocks — buttons, OTP codes, invoices — with ready-made OTP/welcome/alert/invoice templates. Zero-dependency, isomorphic.",
5
+ "type": "module",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": {
12
+ "types": "./dist/index.d.ts",
13
+ "default": "./dist/index.js"
14
+ },
15
+ "require": {
16
+ "types": "./dist/index.d.cts",
17
+ "default": "./dist/index.cjs"
18
+ }
19
+ }
20
+ },
21
+ "files": [
22
+ "dist"
23
+ ],
24
+ "sideEffects": false,
25
+ "scripts": {
26
+ "build": "tsup",
27
+ "prepublishOnly": "npm run build"
28
+ },
29
+ "keywords": [
30
+ "email-template",
31
+ "html-email",
32
+ "responsive-email",
33
+ "transactional-email",
34
+ "email-builder",
35
+ "otp-email",
36
+ "invoice-email",
37
+ "mjml-alternative",
38
+ "typescript"
39
+ ],
40
+ "author": "Lacspace <contact@lacspace.com>",
41
+ "license": "MIT",
42
+ "homepage": "https://lacspace.com/packages",
43
+ "repository": {
44
+ "type": "git",
45
+ "url": "git+https://github.com/lacspace/npm-packages.git",
46
+ "directory": "email-templates"
47
+ },
48
+ "bugs": {
49
+ "url": "https://github.com/lacspace/npm-packages/issues"
50
+ },
51
+ "engines": {
52
+ "node": ">=18"
53
+ }
54
+ }