@emailens/engine 0.3.1 → 0.5.1
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/README.md +255 -246
- package/dist/chunk-PFONR3YC.js +56 -0
- package/dist/chunk-PFONR3YC.js.map +1 -0
- package/dist/chunk-PX25W7YG.js +331 -0
- package/dist/chunk-PX25W7YG.js.map +1 -0
- package/dist/chunk-SZ5O5PDZ.js +78 -0
- package/dist/chunk-SZ5O5PDZ.js.map +1 -0
- package/dist/chunk-W4SPWESS.js +64 -0
- package/dist/chunk-W4SPWESS.js.map +1 -0
- package/dist/compile/index.cjs +590 -0
- package/dist/compile/index.cjs.map +1 -0
- package/dist/compile/index.d.cts +47 -0
- package/dist/compile/index.d.ts +47 -0
- package/dist/compile/index.js +59 -0
- package/dist/compile/index.js.map +1 -0
- package/dist/index.cjs +5485 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +273 -0
- package/dist/index.d.ts +84 -231
- package/dist/index.js +1455 -927
- package/dist/index.js.map +1 -1
- package/dist/maizzle-YDSYDVSM.js +8 -0
- package/dist/maizzle-YDSYDVSM.js.map +1 -0
- package/dist/mjml-IYGC6AOM.js +8 -0
- package/dist/mjml-IYGC6AOM.js.map +1 -0
- package/dist/react-email-BQljgXbo.d.cts +289 -0
- package/dist/react-email-BQljgXbo.d.ts +289 -0
- package/dist/react-email-QRL5KZ4Y.js +8 -0
- package/dist/react-email-QRL5KZ4Y.js.map +1 -0
- package/package.json +97 -60
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @emailens/engine
|
|
2
2
|
|
|
3
|
-
Email compatibility engine that transforms CSS per email client, analyzes compatibility, scores results, simulates dark mode,
|
|
3
|
+
Email compatibility engine that transforms CSS per email client, analyzes compatibility, scores results, simulates dark mode, provides framework-aware fix snippets, and runs spam, accessibility, link, and image quality analysis.
|
|
4
4
|
|
|
5
5
|
Supports **12 email clients**: Gmail (Web, Android, iOS), Outlook (365, Windows), Apple Mail (macOS, iOS), Yahoo Mail, Samsung Mail, Thunderbird, HEY Mail, and Superhuman.
|
|
6
6
|
|
|
@@ -12,240 +12,299 @@ npm install @emailens/engine
|
|
|
12
12
|
bun add @emailens/engine
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
+
Requires Node.js >= 18.
|
|
16
|
+
|
|
15
17
|
## Quick Start
|
|
16
18
|
|
|
17
19
|
```typescript
|
|
18
|
-
import {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
} from "@emailens/engine";
|
|
24
|
-
|
|
25
|
-
const html = `
|
|
26
|
-
<html>
|
|
27
|
-
<head>
|
|
28
|
-
<style>
|
|
29
|
-
.card { border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); }
|
|
30
|
-
</style>
|
|
20
|
+
import { auditEmail } from "@emailens/engine";
|
|
21
|
+
|
|
22
|
+
const html = `<html lang="en">
|
|
23
|
+
<head><title>Newsletter</title>
|
|
24
|
+
<style>.card { border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); }</style>
|
|
31
25
|
</head>
|
|
32
26
|
<body>
|
|
33
27
|
<div class="card" style="display: flex; gap: 16px;">
|
|
34
28
|
<div>Column A</div>
|
|
35
29
|
<div>Column B</div>
|
|
36
30
|
</div>
|
|
31
|
+
<a href="https://example.com/unsubscribe">Unsubscribe</a>
|
|
37
32
|
</body>
|
|
38
|
-
</html
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
console.log(
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
//
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
}
|
|
33
|
+
</html>`;
|
|
34
|
+
|
|
35
|
+
// Run all checks in one call
|
|
36
|
+
const report = auditEmail(html, { framework: "jsx" });
|
|
37
|
+
|
|
38
|
+
console.log(report.compatibility.scores["gmail-web"]);
|
|
39
|
+
// { score: 75, errors: 0, warnings: 5, info: 0 }
|
|
40
|
+
|
|
41
|
+
console.log(report.spam);
|
|
42
|
+
// { score: 100, level: "low", issues: [] }
|
|
43
|
+
|
|
44
|
+
console.log(report.accessibility.score);
|
|
45
|
+
// 88
|
|
46
|
+
|
|
47
|
+
console.log(report.links.totalLinks);
|
|
48
|
+
// 1
|
|
55
49
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
console.log(darkMode.warnings); // Warns about transparent PNGs, missing prefers-color-scheme, etc.
|
|
50
|
+
console.log(report.images.total);
|
|
51
|
+
// 0
|
|
59
52
|
```
|
|
60
53
|
|
|
61
54
|
## API Reference
|
|
62
55
|
|
|
63
|
-
### `
|
|
64
|
-
|
|
65
|
-
Analyzes an HTML email and returns CSS compatibility warnings for all 12 email clients. Detects usage of `<style>`, `<link>`, `<svg>`, `<video>`, `<form>`, inline CSS properties, `@font-face`, `@media` queries, gradients, flexbox/grid, and more.
|
|
56
|
+
### `auditEmail(html: string, options?: AuditOptions): AuditReport`
|
|
66
57
|
|
|
67
|
-
|
|
58
|
+
**Unified API** — runs all email analysis checks in a single call. Returns compatibility warnings + scores, spam analysis, link validation, accessibility audit, and image analysis.
|
|
68
59
|
|
|
69
60
|
```typescript
|
|
70
|
-
|
|
71
|
-
const warnings = analyzeEmail(html);
|
|
61
|
+
import { auditEmail } from "@emailens/engine";
|
|
72
62
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
63
|
+
const report = auditEmail(html, {
|
|
64
|
+
framework: "jsx", // attach framework-specific fix snippets
|
|
65
|
+
spam: { emailType: "transactional" }, // skip unsubscribe check
|
|
66
|
+
skip: ["images"], // skip specific checks
|
|
67
|
+
});
|
|
78
68
|
|
|
79
|
-
//
|
|
80
|
-
|
|
69
|
+
// report.compatibility.warnings — CSSWarning[]
|
|
70
|
+
// report.compatibility.scores — Record<string, ClientScore>
|
|
71
|
+
// report.spam — SpamReport
|
|
72
|
+
// report.links — LinkReport
|
|
73
|
+
// report.accessibility — AccessibilityReport
|
|
74
|
+
// report.images — ImageReport
|
|
81
75
|
```
|
|
82
76
|
|
|
83
|
-
|
|
77
|
+
**`AuditOptions`:**
|
|
78
|
+
- `framework?: "jsx" | "mjml" | "maizzle"` — attach framework-specific fix snippets
|
|
79
|
+
- `spam?: SpamAnalysisOptions` — options for spam analysis
|
|
80
|
+
- `skip?: Array<"spam" | "links" | "accessibility" | "images" | "compatibility">` — skip specific checks
|
|
84
81
|
|
|
85
|
-
|
|
82
|
+
---
|
|
86
83
|
|
|
87
|
-
|
|
84
|
+
### `analyzeEmail(html: string, framework?: Framework): CSSWarning[]`
|
|
85
|
+
|
|
86
|
+
Analyzes an HTML email and returns CSS compatibility warnings for all 12 email clients. Detects `<style>`, `<link>`, `<svg>`, `<video>`, `<form>`, inline CSS properties, `@font-face`, `@media` queries, gradients, flexbox/grid, and more.
|
|
87
|
+
|
|
88
|
+
The optional `framework` parameter controls which fix snippets are attached to warnings. Analysis always runs on compiled HTML.
|
|
88
89
|
|
|
89
90
|
```typescript
|
|
90
|
-
const
|
|
91
|
-
//
|
|
92
|
-
|
|
93
|
-
// "outlook-windows": { score: 40, errors: 1, warnings: 8, info: 2 },
|
|
94
|
-
// "apple-mail-macos": { score: 100, errors: 0, warnings: 0, info: 0 },
|
|
95
|
-
// ...
|
|
96
|
-
// }
|
|
91
|
+
const warnings = analyzeEmail(html); // Plain HTML
|
|
92
|
+
const warnings = analyzeEmail(html, "jsx"); // React Email fixes
|
|
93
|
+
const warnings = analyzeEmail(html, "mjml"); // MJML fixes
|
|
97
94
|
```
|
|
98
95
|
|
|
99
|
-
### `
|
|
96
|
+
### `generateCompatibilityScore(warnings): Record<string, ClientScore>`
|
|
97
|
+
|
|
98
|
+
Generates a 0–100 compatibility score per email client. Formula: `100 - (errors × 15) - (warnings × 5) - (info × 1)`.
|
|
100
99
|
|
|
101
|
-
|
|
100
|
+
### `warningsForClient(warnings, clientId): CSSWarning[]`
|
|
101
|
+
|
|
102
|
+
Filter warnings for a specific client.
|
|
103
|
+
|
|
104
|
+
### `errorWarnings(warnings): CSSWarning[]`
|
|
105
|
+
|
|
106
|
+
Get only error-severity warnings.
|
|
107
|
+
|
|
108
|
+
### `structuralWarnings(warnings): CSSWarning[]`
|
|
109
|
+
|
|
110
|
+
Get only warnings that require HTML restructuring (`fixType: "structural"`).
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
### `analyzeSpam(html: string, options?: SpamAnalysisOptions): SpamReport`
|
|
115
|
+
|
|
116
|
+
Analyzes an HTML email for spam indicators. Returns a 0–100 score (100 = clean) and an array of issues. Uses heuristic rules modeled after SpamAssassin, CAN-SPAM, and GDPR.
|
|
102
117
|
|
|
103
118
|
```typescript
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
119
|
+
import { analyzeSpam } from "@emailens/engine";
|
|
120
|
+
|
|
121
|
+
const report = analyzeSpam(html, {
|
|
122
|
+
emailType: "transactional", // skip unsubscribe check
|
|
123
|
+
listUnsubscribeHeader: "...", // satisfies unsubscribe requirement
|
|
124
|
+
});
|
|
125
|
+
// { score: 95, level: "low", issues: [...] }
|
|
108
126
|
```
|
|
109
127
|
|
|
110
|
-
|
|
128
|
+
**Checks:** caps ratio, excessive punctuation, spam trigger phrases, missing unsubscribe link (with transactional email exemption), hidden text, URL shorteners, image-to-text ratio, deceptive links (with ESP tracking domain allowlist), all-caps subject.
|
|
111
129
|
|
|
112
|
-
|
|
130
|
+
### `validateLinks(html: string): LinkReport`
|
|
131
|
+
|
|
132
|
+
Static analysis of all links in an HTML email. No network requests.
|
|
113
133
|
|
|
114
134
|
```typescript
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
}
|
|
135
|
+
import { validateLinks } from "@emailens/engine";
|
|
136
|
+
|
|
137
|
+
const report = validateLinks(html);
|
|
138
|
+
// { totalLinks: 12, issues: [...], breakdown: { https: 10, http: 1, mailto: 1, ... } }
|
|
119
139
|
```
|
|
120
140
|
|
|
121
|
-
|
|
141
|
+
**Checks:** empty/placeholder hrefs, `javascript:` protocol, insecure HTTP, generic link text, missing accessible names, empty mailto/tel, very long URLs, duplicate links.
|
|
122
142
|
|
|
123
|
-
|
|
143
|
+
### `checkAccessibility(html: string): AccessibilityReport`
|
|
124
144
|
|
|
125
|
-
|
|
126
|
-
- **Partial inversion** (Gmail Web, Apple Mail, Yahoo, Outlook.com, HEY, Superhuman): only inverts white backgrounds
|
|
127
|
-
- **No dark mode** (Outlook Windows, Thunderbird): no transformation
|
|
145
|
+
Audits an HTML email for accessibility issues. Returns a 0–100 score and detailed issues.
|
|
128
146
|
|
|
129
147
|
```typescript
|
|
130
|
-
|
|
148
|
+
import { checkAccessibility } from "@emailens/engine";
|
|
149
|
+
|
|
150
|
+
const report = checkAccessibility(html);
|
|
151
|
+
// { score: 88, issues: [...] }
|
|
131
152
|
```
|
|
132
153
|
|
|
133
|
-
|
|
154
|
+
**Checks:** missing `lang` attribute, missing `<title>`, image alt text, link accessibility, layout table roles, small text, color contrast (WCAG 2.1), heading hierarchy.
|
|
134
155
|
|
|
135
|
-
|
|
156
|
+
### `analyzeImages(html: string): ImageReport`
|
|
136
157
|
|
|
137
|
-
|
|
138
|
-
2. **Framework specific** (e.g., `@font-face` + MJML → `<mj-font>`)
|
|
139
|
-
3. **Generic HTML fallback** (e.g., `display:flex` + Outlook → HTML table with MSO conditionals)
|
|
158
|
+
Analyzes images for email best practices.
|
|
140
159
|
|
|
141
160
|
```typescript
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
//
|
|
146
|
-
// before: "<div style={{ display: 'flex' }}>...",
|
|
147
|
-
// after: "<Row><Column>..."
|
|
148
|
-
// }
|
|
161
|
+
import { analyzeImages } from "@emailens/engine";
|
|
162
|
+
|
|
163
|
+
const report = analyzeImages(html);
|
|
164
|
+
// { total: 5, totalDataUriBytes: 0, issues: [...], images: [...] }
|
|
149
165
|
```
|
|
150
166
|
|
|
151
|
-
|
|
167
|
+
**Checks:** missing dimensions, oversized data URIs, missing alt, WebP/SVG format, missing `display:block`, tracking pixels, high image count.
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
### `transformForClient(html, clientId, framework?): TransformResult`
|
|
172
|
+
|
|
173
|
+
Transforms HTML for a specific email client — strips unsupported CSS, inlines `<style>` blocks (for Gmail), removes unsupported elements.
|
|
174
|
+
|
|
175
|
+
### `transformForAllClients(html, framework?): TransformResult[]`
|
|
176
|
+
|
|
177
|
+
Transforms HTML for all 12 email clients at once.
|
|
178
|
+
|
|
179
|
+
### `simulateDarkMode(html, clientId): { html, warnings }`
|
|
152
180
|
|
|
153
|
-
|
|
181
|
+
Simulates how an email client applies dark mode using luminance-based color detection.
|
|
182
|
+
|
|
183
|
+
- **Full inversion** (Gmail Android, Samsung Mail): inverts all light backgrounds and dark text
|
|
184
|
+
- **Partial inversion** (Gmail Web, Apple Mail, Yahoo, Outlook.com, HEY, Superhuman): only inverts very light/dark colors
|
|
185
|
+
- **No dark mode** (Outlook Windows, Thunderbird)
|
|
186
|
+
|
|
187
|
+
### `getCodeFix(property, clientId, framework?): CodeFix | undefined`
|
|
188
|
+
|
|
189
|
+
Returns a paste-ready code fix for a CSS property + client combination. Fixes are tiered:
|
|
190
|
+
|
|
191
|
+
1. **Framework + client specific** (e.g., `border-radius` + Outlook + JSX → VML component)
|
|
192
|
+
2. **Framework specific** (e.g., `@font-face` + MJML → `<mj-font>`)
|
|
193
|
+
3. **Client specific** (e.g., `border-radius` + Outlook → VML roundrect)
|
|
194
|
+
4. **Generic HTML fallback**
|
|
154
195
|
|
|
155
196
|
### `diffResults(before, after): DiffResult[]`
|
|
156
197
|
|
|
157
|
-
Compares two sets of analysis results
|
|
198
|
+
Compares two sets of analysis results to show what improved, regressed, or stayed the same.
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
## Compile Module
|
|
203
|
+
|
|
204
|
+
Compile email templates from JSX, MJML, or Maizzle to HTML.
|
|
158
205
|
|
|
159
206
|
```typescript
|
|
160
|
-
|
|
161
|
-
const after = { scores: generateCompatibilityScore(warningsAfter), warnings: warningsAfter };
|
|
162
|
-
const diffs = diffResults(before, after);
|
|
207
|
+
import { compile, detectFormat, CompileError } from "@emailens/engine/compile";
|
|
163
208
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
209
|
+
// Auto-detect format and compile
|
|
210
|
+
const format = detectFormat("email.tsx"); // "jsx"
|
|
211
|
+
const html = await compile(source, format);
|
|
212
|
+
|
|
213
|
+
// Or use specific compilers
|
|
214
|
+
import { compileReactEmail, compileMjml, compileMaizzle } from "@emailens/engine/compile";
|
|
168
215
|
```
|
|
169
216
|
|
|
170
|
-
### `
|
|
217
|
+
### `compile(source, format, filePath?): Promise<string>`
|
|
218
|
+
|
|
219
|
+
Compile source to HTML based on format. Lazily imports per-format compilers.
|
|
220
|
+
|
|
221
|
+
### `compileReactEmail(source, options?): Promise<string>`
|
|
171
222
|
|
|
172
|
-
|
|
223
|
+
Compile React Email JSX/TSX to HTML. Pipeline: validate → transpile (sucrase) → sandbox execute → render.
|
|
173
224
|
|
|
174
225
|
```typescript
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
scope: "all", // or "current" with selectedClientId
|
|
180
|
-
format: "jsx", // "html" | "jsx" | "mjml" | "maizzle"
|
|
226
|
+
import { compileReactEmail } from "@emailens/engine/compile";
|
|
227
|
+
|
|
228
|
+
const html = await compileReactEmail(jsxSource, {
|
|
229
|
+
sandbox: "isolated-vm", // "vm" | "isolated-vm" | "quickjs"
|
|
181
230
|
});
|
|
182
231
|
```
|
|
183
232
|
|
|
184
|
-
|
|
233
|
+
**Sandbox strategies:**
|
|
234
|
+
- `"isolated-vm"` (default) — Separate V8 isolate. True heap isolation. Requires `isolated-vm` native addon.
|
|
235
|
+
- `"vm"` — `node:vm` with hardened globals. Fast, zero-dependency, but NOT a true security boundary. Suitable for CLI/local use.
|
|
236
|
+
- `"quickjs"` — Validates code in WASM sandbox, then executes in `node:vm`. Security is equivalent to `"vm"`. No native addons needed.
|
|
185
237
|
|
|
186
|
-
|
|
238
|
+
**Peer dependencies:** `sucrase`, `react`, `@react-email/components`, `@react-email/render`. Plus `isolated-vm` or `quickjs-emscripten` depending on sandbox strategy.
|
|
187
239
|
|
|
188
|
-
|
|
189
|
-
|
|
240
|
+
### `compileMjml(source): Promise<string>`
|
|
241
|
+
|
|
242
|
+
Compile MJML to HTML. **Peer dependency:** `mjml`.
|
|
243
|
+
|
|
244
|
+
### `compileMaizzle(source): Promise<string>`
|
|
245
|
+
|
|
246
|
+
Compile Maizzle template to HTML. **Peer dependency:** `@maizzle/framework`.
|
|
247
|
+
|
|
248
|
+
**Security:** PostHTML file-system directives (`<extends>`, `<component>`, `<fetch>`, `<include>`, `<module>`, `<slot>`, `<fill>`, `<raw>`, `<block>`, `<yield>`) are rejected at validation time to prevent server-side file reads.
|
|
249
|
+
|
|
250
|
+
### `detectFormat(filePath): InputFormat`
|
|
251
|
+
|
|
252
|
+
Auto-detect input format from file extension (`.tsx`/`.jsx` → `"jsx"`, `.mjml` → `"mjml"`, `.html` → `"html"`).
|
|
253
|
+
|
|
254
|
+
### `CompileError`
|
|
190
255
|
|
|
191
|
-
for
|
|
192
|
-
|
|
256
|
+
Unified error class for all compilation failures. Available from both `@emailens/engine` and `@emailens/engine/compile`.
|
|
257
|
+
|
|
258
|
+
```typescript
|
|
259
|
+
import { CompileError } from "@emailens/engine";
|
|
260
|
+
|
|
261
|
+
try {
|
|
262
|
+
await compile(source, "jsx");
|
|
263
|
+
} catch (err) {
|
|
264
|
+
if (err instanceof CompileError) {
|
|
265
|
+
console.log(err.format); // "jsx" | "mjml" | "maizzle"
|
|
266
|
+
console.log(err.phase); // "validation" | "transpile" | "execution" | "render" | "compile"
|
|
267
|
+
}
|
|
193
268
|
}
|
|
194
|
-
// Gmail (webmail) — Gmail Web
|
|
195
|
-
// Outlook Windows (desktop) — Microsoft Word
|
|
196
|
-
// Apple Mail (desktop) — WebKit
|
|
197
|
-
// ...
|
|
198
269
|
```
|
|
199
270
|
|
|
200
|
-
|
|
271
|
+
---
|
|
201
272
|
|
|
202
|
-
|
|
273
|
+
## Security Considerations
|
|
203
274
|
|
|
204
|
-
|
|
275
|
+
### Input Size Limits
|
|
205
276
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
| Apple Mail iOS | `apple-mail-ios` | Mobile | WebKit | Yes |
|
|
215
|
-
| Yahoo Mail | `yahoo-mail` | Webmail | Yahoo | Yes |
|
|
216
|
-
| Samsung Mail | `samsung-mail` | Mobile | Samsung | Yes |
|
|
217
|
-
| Thunderbird | `thunderbird` | Desktop | Gecko | No |
|
|
218
|
-
| HEY Mail | `hey-mail` | Webmail | WebKit | Yes |
|
|
219
|
-
| Superhuman | `superhuman` | Desktop | Blink | Yes |
|
|
277
|
+
All public functions enforce a 2MB (`MAX_HTML_SIZE`) input limit. Inputs exceeding this limit throw immediately. The limit is exported so consumers can check before calling:
|
|
278
|
+
|
|
279
|
+
```typescript
|
|
280
|
+
import { MAX_HTML_SIZE } from "@emailens/engine";
|
|
281
|
+
if (html.length > MAX_HTML_SIZE) {
|
|
282
|
+
// handle oversized input
|
|
283
|
+
}
|
|
284
|
+
```
|
|
220
285
|
|
|
221
|
-
|
|
286
|
+
### Compile Module Security
|
|
222
287
|
|
|
223
|
-
|
|
288
|
+
- **React Email JSX**: User code runs in a sandboxed environment. The `"isolated-vm"` strategy provides true heap isolation. The `"vm"` and `"quickjs"` strategies use `node:vm` which is NOT a security boundary — suitable for CLI use where users run their own code. For server deployments accepting untrusted input, use `"isolated-vm"`.
|
|
289
|
+
- **Maizzle**: PostHTML directives that access the filesystem (`<extends>`, `<fetch>`, `<include>`, `<raw>`, `<block>`, `<yield>`, etc.) are rejected at validation time.
|
|
290
|
+
- **MJML**: Compiled through the `mjml` package with default settings.
|
|
224
291
|
|
|
225
|
-
|
|
292
|
+
---
|
|
226
293
|
|
|
227
|
-
|
|
294
|
+
## AI-Powered Fixes
|
|
228
295
|
|
|
229
|
-
|
|
296
|
+
The engine classifies every warning as either `css` (CSS-only swap) or `structural` (requires HTML restructuring). For structural issues, the engine can generate a prompt and delegate to an LLM.
|
|
297
|
+
|
|
298
|
+
### `generateAiFix(options): Promise<AiFixResult>`
|
|
230
299
|
|
|
231
300
|
```typescript
|
|
232
|
-
import
|
|
233
|
-
import {
|
|
234
|
-
analyzeEmail,
|
|
235
|
-
generateCompatibilityScore,
|
|
236
|
-
generateAiFix,
|
|
237
|
-
AI_FIX_SYSTEM_PROMPT,
|
|
238
|
-
} from "@emailens/engine";
|
|
239
|
-
|
|
240
|
-
const anthropic = new Anthropic();
|
|
241
|
-
const warnings = analyzeEmail(html, "jsx");
|
|
242
|
-
const scores = generateCompatibilityScore(warnings);
|
|
301
|
+
import { generateAiFix, AI_FIX_SYSTEM_PROMPT } from "@emailens/engine";
|
|
243
302
|
|
|
244
303
|
const result = await generateAiFix({
|
|
245
304
|
originalHtml: html,
|
|
246
305
|
warnings,
|
|
247
306
|
scores,
|
|
248
|
-
scope: "all",
|
|
307
|
+
scope: "all",
|
|
249
308
|
format: "jsx",
|
|
250
309
|
provider: async (prompt) => {
|
|
251
310
|
const msg = await anthropic.messages.create({
|
|
@@ -257,68 +316,34 @@ const result = await generateAiFix({
|
|
|
257
316
|
return msg.content[0].type === "text" ? msg.content[0].text : "";
|
|
258
317
|
},
|
|
259
318
|
});
|
|
260
|
-
|
|
261
|
-
console.log(result.code); // Fixed email code
|
|
262
|
-
console.log(result.targetedWarnings); // 23
|
|
263
|
-
console.log(result.structuralCount); // 5
|
|
264
319
|
```
|
|
265
320
|
|
|
266
321
|
### `estimateAiFixTokens(options): Promise<TokenEstimate>`
|
|
267
322
|
|
|
268
|
-
Estimate tokens
|
|
269
|
-
|
|
270
|
-
```typescript
|
|
271
|
-
import { estimateAiFixTokens } from "@emailens/engine";
|
|
272
|
-
|
|
273
|
-
const estimate = await estimateAiFixTokens({
|
|
274
|
-
originalHtml: html,
|
|
275
|
-
warnings,
|
|
276
|
-
scores,
|
|
277
|
-
scope: "all",
|
|
278
|
-
format: "jsx",
|
|
279
|
-
maxInputTokens: 16000, // optional, triggers smart truncation
|
|
280
|
-
});
|
|
281
|
-
|
|
282
|
-
console.log(`~${estimate.inputTokens} input tokens`);
|
|
283
|
-
console.log(`~${estimate.estimatedOutputTokens} output tokens`);
|
|
284
|
-
console.log(`${estimate.warningCount} warnings (${estimate.structuralCount} structural)`);
|
|
285
|
-
console.log(`Truncated: ${estimate.truncated}`);
|
|
286
|
-
```
|
|
287
|
-
|
|
288
|
-
**Smart truncation** kicks in when the prompt exceeds `maxInputTokens`:
|
|
289
|
-
1. Deduplicates warnings (same property × severity)
|
|
290
|
-
2. Removes `info`-level warnings
|
|
291
|
-
3. Removes CSS-only warnings (keeps structural + errors)
|
|
292
|
-
4. Trims long fix snippets
|
|
323
|
+
Estimate tokens before making an API call.
|
|
293
324
|
|
|
294
325
|
### `heuristicTokenCount(text): number`
|
|
295
326
|
|
|
296
|
-
Instant synchronous token estimate (~3.5 chars/token).
|
|
297
|
-
|
|
298
|
-
```typescript
|
|
299
|
-
import { heuristicTokenCount } from "@emailens/engine";
|
|
300
|
-
const tokens = heuristicTokenCount(html); // instant, no deps
|
|
301
|
-
```
|
|
302
|
-
|
|
303
|
-
### `AI_FIX_SYSTEM_PROMPT`
|
|
304
|
-
|
|
305
|
-
Expert system prompt for email compatibility fixes. Pass as the `system` parameter to your LLM call for best results. Includes structural fix patterns (table layouts, VML, MSO conditionals).
|
|
306
|
-
|
|
307
|
-
### `STRUCTURAL_FIX_PROPERTIES`
|
|
308
|
-
|
|
309
|
-
`Set<string>` of CSS properties that require HTML restructuring (not just CSS swaps). Includes `display:flex`, `display:grid`, `word-break`, `position`, `border-radius` (Outlook), `background-image` (Outlook), and more.
|
|
310
|
-
|
|
311
|
-
```typescript
|
|
312
|
-
import { STRUCTURAL_FIX_PROPERTIES } from "@emailens/engine";
|
|
313
|
-
STRUCTURAL_FIX_PROPERTIES.has("word-break"); // true
|
|
314
|
-
STRUCTURAL_FIX_PROPERTIES.has("color"); // false
|
|
315
|
-
```
|
|
327
|
+
Instant synchronous token estimate (~3.5 chars/token).
|
|
316
328
|
|
|
317
|
-
|
|
329
|
+
---
|
|
318
330
|
|
|
319
|
-
|
|
331
|
+
## Supported Email Clients
|
|
320
332
|
|
|
321
|
-
|
|
333
|
+
| Client | ID | Category | Engine | Dark Mode |
|
|
334
|
+
|---|---|---|---|---|
|
|
335
|
+
| Gmail | `gmail-web` | Webmail | Gmail Web | Yes |
|
|
336
|
+
| Gmail Android | `gmail-android` | Mobile | Gmail Mobile | Yes |
|
|
337
|
+
| Gmail iOS | `gmail-ios` | Mobile | Gmail Mobile | Yes |
|
|
338
|
+
| Outlook 365 | `outlook-web` | Webmail | Outlook Web | Yes |
|
|
339
|
+
| Outlook Windows | `outlook-windows` | Desktop | Microsoft Word | No |
|
|
340
|
+
| Apple Mail | `apple-mail-macos` | Desktop | WebKit | Yes |
|
|
341
|
+
| Apple Mail iOS | `apple-mail-ios` | Mobile | WebKit | Yes |
|
|
342
|
+
| Yahoo Mail | `yahoo-mail` | Webmail | Yahoo | Yes |
|
|
343
|
+
| Samsung Mail | `samsung-mail` | Mobile | Samsung | Yes |
|
|
344
|
+
| Thunderbird | `thunderbird` | Desktop | Gecko | No |
|
|
345
|
+
| HEY Mail | `hey-mail` | Webmail | WebKit | Yes |
|
|
346
|
+
| Superhuman | `superhuman` | Desktop | Blink | Yes |
|
|
322
347
|
|
|
323
348
|
## Types
|
|
324
349
|
|
|
@@ -327,16 +352,6 @@ type SupportLevel = "supported" | "partial" | "unsupported" | "unknown";
|
|
|
327
352
|
type Framework = "jsx" | "mjml" | "maizzle";
|
|
328
353
|
type InputFormat = "html" | Framework;
|
|
329
354
|
type FixType = "css" | "structural";
|
|
330
|
-
type AiProvider = (prompt: string) => Promise<string>;
|
|
331
|
-
|
|
332
|
-
interface EmailClient {
|
|
333
|
-
id: string;
|
|
334
|
-
name: string;
|
|
335
|
-
category: "webmail" | "desktop" | "mobile";
|
|
336
|
-
engine: string;
|
|
337
|
-
darkModeSupport: boolean;
|
|
338
|
-
icon: string;
|
|
339
|
-
}
|
|
340
355
|
|
|
341
356
|
interface CSSWarning {
|
|
342
357
|
severity: "error" | "warning" | "info";
|
|
@@ -345,50 +360,44 @@ interface CSSWarning {
|
|
|
345
360
|
message: string;
|
|
346
361
|
suggestion?: string;
|
|
347
362
|
fix?: CodeFix;
|
|
348
|
-
|
|
349
|
-
|
|
363
|
+
fixType?: FixType;
|
|
364
|
+
line?: number; // line number in <style> block
|
|
365
|
+
selector?: string; // element selector for inline styles
|
|
350
366
|
}
|
|
351
367
|
|
|
352
|
-
interface
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
368
|
+
interface AuditReport {
|
|
369
|
+
compatibility: {
|
|
370
|
+
warnings: CSSWarning[];
|
|
371
|
+
scores: Record<string, { score: number; errors: number; warnings: number; info: number }>;
|
|
372
|
+
};
|
|
373
|
+
spam: SpamReport;
|
|
374
|
+
links: LinkReport;
|
|
375
|
+
accessibility: AccessibilityReport;
|
|
376
|
+
images: ImageReport;
|
|
357
377
|
}
|
|
358
378
|
|
|
359
|
-
interface
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
structuralCount: number;
|
|
364
|
-
tokenEstimate: TokenEstimate;
|
|
379
|
+
interface SpamReport {
|
|
380
|
+
score: number; // 0–100 (100 = clean)
|
|
381
|
+
level: "low" | "medium" | "high";
|
|
382
|
+
issues: SpamIssue[];
|
|
365
383
|
}
|
|
366
384
|
|
|
367
|
-
interface
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
htmlCharacters: number;
|
|
372
|
-
warningCount: number;
|
|
373
|
-
structuralCount: number;
|
|
374
|
-
truncated: boolean;
|
|
375
|
-
warningsRemoved: number;
|
|
385
|
+
interface LinkReport {
|
|
386
|
+
totalLinks: number;
|
|
387
|
+
issues: LinkIssue[];
|
|
388
|
+
breakdown: { https: number; http: number; mailto: number; tel: number; ... };
|
|
376
389
|
}
|
|
377
390
|
|
|
378
|
-
interface
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
warnings: CSSWarning[];
|
|
391
|
+
interface AccessibilityReport {
|
|
392
|
+
score: number; // 0–100
|
|
393
|
+
issues: AccessibilityIssue[];
|
|
382
394
|
}
|
|
383
395
|
|
|
384
|
-
interface
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
fixed: CSSWarning[];
|
|
390
|
-
introduced: CSSWarning[];
|
|
391
|
-
unchanged: CSSWarning[];
|
|
396
|
+
interface ImageReport {
|
|
397
|
+
total: number;
|
|
398
|
+
totalDataUriBytes: number;
|
|
399
|
+
issues: ImageIssue[];
|
|
400
|
+
images: ImageInfo[];
|
|
392
401
|
}
|
|
393
402
|
```
|
|
394
403
|
|
|
@@ -398,7 +407,7 @@ interface DiffResult {
|
|
|
398
407
|
bun test
|
|
399
408
|
```
|
|
400
409
|
|
|
401
|
-
|
|
410
|
+
449 tests covering analysis, transformation, dark mode simulation, framework-aware fixes, AI fix generation, token estimation, spam scoring, link validation, accessibility checking, image analysis, security hardening, integration pipelines, and accuracy benchmarks.
|
|
402
411
|
|
|
403
412
|
## License
|
|
404
413
|
|