@kumix/email 0.1.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/README.md +395 -0
- package/dist/components.d.ts +3 -0
- package/dist/components.js +11 -0
- package/dist/components.js.map +1 -0
- package/dist/helpers.d.ts +325 -0
- package/dist/helpers.d.ts.map +1 -0
- package/dist/helpers.js +447 -0
- package/dist/helpers.js.map +1 -0
- package/dist/index.d.ts +401 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +454 -0
- package/dist/index.js.map +1 -0
- package/package.json +77 -0
package/dist/helpers.js
ADDED
|
@@ -0,0 +1,447 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { render } from "react-email";
|
|
3
|
+
//#region src/helpers.ts
|
|
4
|
+
/**
|
|
5
|
+
* @fileoverview Email Helper Utilities
|
|
6
|
+
*
|
|
7
|
+
* This module provides utility functions for email operations including:
|
|
8
|
+
* - Template rendering and text conversion
|
|
9
|
+
* - Email address validation and formatting
|
|
10
|
+
* - Content processing and sanitization
|
|
11
|
+
* - Attachment handling
|
|
12
|
+
* - Email list management
|
|
13
|
+
*
|
|
14
|
+
* Import from '@kumix/email/helpers' to access these utilities.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```typescript
|
|
18
|
+
* import {
|
|
19
|
+
* renderEmailTemplate,
|
|
20
|
+
* htmlToText,
|
|
21
|
+
* isValidEmail,
|
|
22
|
+
* formatEmailAddress,
|
|
23
|
+
* sanitizeHtml,
|
|
24
|
+
* generateUnsubscribeLink
|
|
25
|
+
* } from '@kumix/email/helpers';
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
/**
|
|
29
|
+
* Render React component to HTML string
|
|
30
|
+
*
|
|
31
|
+
* Converts a React email template component to HTML string that can be sent via email.
|
|
32
|
+
*
|
|
33
|
+
* @param Template - React component to render
|
|
34
|
+
* @param props - Props to pass to the component
|
|
35
|
+
* @returns Promise resolving to HTML string
|
|
36
|
+
* @example
|
|
37
|
+
* ```typescript
|
|
38
|
+
* const WelcomeEmail = ({ name }: { name: string }) => <h1>Welcome {name}!</h1>;
|
|
39
|
+
*
|
|
40
|
+
* const html = await renderEmailTemplate(WelcomeEmail, { name: 'John' });
|
|
41
|
+
* console.log(html); // "<h1>Welcome John!</h1>"
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
44
|
+
async function renderEmailTemplate(Template, props) {
|
|
45
|
+
return render(React.createElement(Template, props));
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Generate plain text from HTML
|
|
49
|
+
*
|
|
50
|
+
* Converts HTML content to plain text by removing tags and normalizing whitespace.
|
|
51
|
+
* Useful for creating text versions of HTML emails.
|
|
52
|
+
*
|
|
53
|
+
* @param html - HTML string to convert
|
|
54
|
+
* @returns Plain text version of the HTML
|
|
55
|
+
* @example
|
|
56
|
+
* ```typescript
|
|
57
|
+
* const html = '<h1>Hello</h1><p>Welcome to our <strong>platform</strong>!</p>';
|
|
58
|
+
* const text = htmlToText(html);
|
|
59
|
+
* console.log(text); // "Hello Welcome to our platform!"
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
function htmlToText(html) {
|
|
63
|
+
return html.replace(/<br\s*\/?>/gi, "\n").replace(/<\/p>/gi, "\n\n").replace(/<\/h[1-6]>/gi, "\n\n").replace(/<[^>]*>/g, " ").replace(/\s+/g, " ").replace(/\n\s+/g, "\n").trim();
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Validate email address format
|
|
67
|
+
*
|
|
68
|
+
* Checks if an email address has a valid format using RFC 5322 compliant regex.
|
|
69
|
+
*
|
|
70
|
+
* @param email - Email address to validate
|
|
71
|
+
* @returns true if email format is valid, false otherwise
|
|
72
|
+
* @example
|
|
73
|
+
* ```typescript
|
|
74
|
+
* console.log(isValidEmail('user@example.com')); // true
|
|
75
|
+
* console.log(isValidEmail('invalid-email')); // false
|
|
76
|
+
* console.log(isValidEmail('user+tag@example.co.uk')); // true
|
|
77
|
+
* ```
|
|
78
|
+
*/
|
|
79
|
+
function isValidEmail(email) {
|
|
80
|
+
return /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+$/.test(email);
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Validate multiple email addresses
|
|
84
|
+
*
|
|
85
|
+
* Validates an array of email addresses or a single email address.
|
|
86
|
+
*
|
|
87
|
+
* @param emails - Single email or array of emails to validate
|
|
88
|
+
* @returns true if all emails are valid, false if any are invalid
|
|
89
|
+
* @example
|
|
90
|
+
* ```typescript
|
|
91
|
+
* console.log(validateEmails('user@example.com')); // true
|
|
92
|
+
* console.log(validateEmails(['user1@example.com', 'user2@example.com'])); // true
|
|
93
|
+
* console.log(validateEmails(['valid@example.com', 'invalid-email'])); // false
|
|
94
|
+
* ```
|
|
95
|
+
*/
|
|
96
|
+
function validateEmails(emails) {
|
|
97
|
+
return (Array.isArray(emails) ? emails : [emails]).every((email) => isValidEmail(email.trim()));
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Filter valid emails from a list
|
|
101
|
+
*
|
|
102
|
+
* Returns only the valid email addresses from a list, filtering out invalid ones.
|
|
103
|
+
*
|
|
104
|
+
* @param emails - Array of email addresses to filter
|
|
105
|
+
* @returns Array containing only valid email addresses
|
|
106
|
+
* @example
|
|
107
|
+
* ```typescript
|
|
108
|
+
* const emails = ['valid@example.com', 'invalid-email', 'another@example.com'];
|
|
109
|
+
* const validEmails = filterValidEmails(emails);
|
|
110
|
+
* console.log(validEmails); // ['valid@example.com', 'another@example.com']
|
|
111
|
+
* ```
|
|
112
|
+
*/
|
|
113
|
+
function filterValidEmails(emails) {
|
|
114
|
+
return emails.filter((email) => isValidEmail(email.trim()));
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Format email address with display name
|
|
118
|
+
*
|
|
119
|
+
* Combines a display name with an email address in the standard format.
|
|
120
|
+
*
|
|
121
|
+
* @param name - Display name for the email address
|
|
122
|
+
* @param email - Email address
|
|
123
|
+
* @returns Formatted email address string
|
|
124
|
+
* @example
|
|
125
|
+
* ```typescript
|
|
126
|
+
* const formatted = formatEmailAddress('John Doe', 'john@example.com');
|
|
127
|
+
* console.log(formatted); // "John Doe <john@example.com>"
|
|
128
|
+
* ```
|
|
129
|
+
*/
|
|
130
|
+
function formatEmailAddress(name, email) {
|
|
131
|
+
return `${name} <${email}>`;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Extract email address from formatted string
|
|
135
|
+
*
|
|
136
|
+
* Extracts the email address from a formatted "Name <email>" string.
|
|
137
|
+
*
|
|
138
|
+
* @param formattedAddress - Formatted email address string
|
|
139
|
+
* @returns Just the email address part
|
|
140
|
+
* @example
|
|
141
|
+
* ```typescript
|
|
142
|
+
* const email = extractEmail('John Doe <john@example.com>');
|
|
143
|
+
* console.log(email); // "john@example.com"
|
|
144
|
+
*
|
|
145
|
+
* const email2 = extractEmail('simple@example.com');
|
|
146
|
+
* console.log(email2); // "simple@example.com"
|
|
147
|
+
* ```
|
|
148
|
+
*/
|
|
149
|
+
function extractEmail(formattedAddress) {
|
|
150
|
+
const match = formattedAddress.match(/<([^>]*)>/);
|
|
151
|
+
return match ? match[1] : formattedAddress.trim();
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Extract display name from formatted email address
|
|
155
|
+
*
|
|
156
|
+
* Extracts the display name from a formatted "Name <email>" string.
|
|
157
|
+
*
|
|
158
|
+
* @param formattedAddress - Formatted email address string
|
|
159
|
+
* @returns Display name or empty string if none found
|
|
160
|
+
* @example
|
|
161
|
+
* ```typescript
|
|
162
|
+
* const name = extractDisplayName('John Doe <john@example.com>');
|
|
163
|
+
* console.log(name); // "John Doe"
|
|
164
|
+
*
|
|
165
|
+
* const name2 = extractDisplayName('simple@example.com');
|
|
166
|
+
* console.log(name2); // ""
|
|
167
|
+
* ```
|
|
168
|
+
*/
|
|
169
|
+
function extractDisplayName(formattedAddress) {
|
|
170
|
+
const match = formattedAddress.match(/^(.+?)\s*<[^>]*>$/);
|
|
171
|
+
return match ? match[1].trim().replace(/^["']|["']$/g, "") : "";
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Sanitize HTML content for email
|
|
175
|
+
*
|
|
176
|
+
* Removes potentially dangerous HTML elements and attributes while preserving
|
|
177
|
+
* email-safe formatting.
|
|
178
|
+
*
|
|
179
|
+
* @param html - HTML content to sanitize
|
|
180
|
+
* @returns Sanitized HTML safe for email
|
|
181
|
+
* @example
|
|
182
|
+
* ```typescript
|
|
183
|
+
* const unsafeHtml = '<script>alert("xss")<\/script><p>Safe content</p>';
|
|
184
|
+
* const safeHtml = sanitizeHtml(unsafeHtml);
|
|
185
|
+
* console.log(safeHtml); // "<p>Safe content</p>"
|
|
186
|
+
* ```
|
|
187
|
+
*/
|
|
188
|
+
function sanitizeHtml(html) {
|
|
189
|
+
let sanitized = html.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, "");
|
|
190
|
+
sanitized = sanitized.replace(/\s*on\w+\s*=\s*["'][^"']*["']/gi, "");
|
|
191
|
+
sanitized = sanitized.replace(/\s*javascript\s*:/gi, "");
|
|
192
|
+
sanitized = sanitized.replace(/\s*style\s*=\s*["'][^"']*expression\([^"']*\)["']/gi, "");
|
|
193
|
+
return sanitized;
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Truncate text with ellipsis
|
|
197
|
+
*
|
|
198
|
+
* Truncates text to a specified length and adds ellipsis if needed.
|
|
199
|
+
*
|
|
200
|
+
* @param text - Text to truncate
|
|
201
|
+
* @param maxLength - Maximum length before truncation
|
|
202
|
+
* @param ellipsis - String to append when truncated (default: '...')
|
|
203
|
+
* @returns Truncated text
|
|
204
|
+
* @example
|
|
205
|
+
* ```typescript
|
|
206
|
+
* const text = 'This is a very long email subject line';
|
|
207
|
+
* const truncated = truncateText(text, 20);
|
|
208
|
+
* console.log(truncated); // "This is a very long..."
|
|
209
|
+
* ```
|
|
210
|
+
*/
|
|
211
|
+
function truncateText(text, maxLength, ellipsis = "...") {
|
|
212
|
+
if (text.length <= maxLength) return text;
|
|
213
|
+
return text.slice(0, maxLength - ellipsis.length) + ellipsis;
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Generate email preview text
|
|
217
|
+
*
|
|
218
|
+
* Creates a preview text from HTML content, suitable for email preview panes.
|
|
219
|
+
*
|
|
220
|
+
* @param html - HTML content to generate preview from
|
|
221
|
+
* @param maxLength - Maximum length of preview text (default: 150)
|
|
222
|
+
* @returns Preview text
|
|
223
|
+
* @example
|
|
224
|
+
* ```typescript
|
|
225
|
+
* const html = '<h1>Welcome!</h1><p>Thank you for joining our platform.</p>';
|
|
226
|
+
* const preview = generatePreviewText(html);
|
|
227
|
+
* console.log(preview); // "Welcome! Thank you for joining our platform."
|
|
228
|
+
* ```
|
|
229
|
+
*/
|
|
230
|
+
function generatePreviewText(html, maxLength = 150) {
|
|
231
|
+
return truncateText(htmlToText(html), maxLength);
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Generate unsubscribe link
|
|
235
|
+
*
|
|
236
|
+
* Creates an unsubscribe URL with proper parameters.
|
|
237
|
+
*
|
|
238
|
+
* @param baseUrl - Base URL for unsubscribe endpoint
|
|
239
|
+
* @param email - Email address to unsubscribe
|
|
240
|
+
* @param token - Optional security token
|
|
241
|
+
* @returns Complete unsubscribe URL
|
|
242
|
+
* @example
|
|
243
|
+
* ```typescript
|
|
244
|
+
* const link = generateUnsubscribeLink(
|
|
245
|
+
* 'https://example.com/unsubscribe',
|
|
246
|
+
* 'user@example.com',
|
|
247
|
+
* 'secure-token-123'
|
|
248
|
+
* );
|
|
249
|
+
* console.log(link); // "https://example.com/unsubscribe?email=user%40example.com&token=secure-token-123"
|
|
250
|
+
* ```
|
|
251
|
+
*/
|
|
252
|
+
function generateUnsubscribeLink(baseUrl, email, token) {
|
|
253
|
+
const url = new URL(baseUrl);
|
|
254
|
+
url.searchParams.set("email", email);
|
|
255
|
+
if (token) url.searchParams.set("token", token);
|
|
256
|
+
return url.toString();
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Generate tracking pixel URL
|
|
260
|
+
*
|
|
261
|
+
* Creates a tracking pixel URL for email open tracking.
|
|
262
|
+
*
|
|
263
|
+
* @param baseUrl - Base URL for tracking endpoint
|
|
264
|
+
* @param emailId - Unique identifier for the email
|
|
265
|
+
* @param recipientId - Unique identifier for the recipient
|
|
266
|
+
* @returns Tracking pixel URL
|
|
267
|
+
* @example
|
|
268
|
+
* ```typescript
|
|
269
|
+
* const pixelUrl = generateTrackingPixel(
|
|
270
|
+
* 'https://example.com/track',
|
|
271
|
+
* 'email-123',
|
|
272
|
+
* 'user-456'
|
|
273
|
+
* );
|
|
274
|
+
* // Use in email: <img src="${pixelUrl}" width="1" height="1" />
|
|
275
|
+
* ```
|
|
276
|
+
*/
|
|
277
|
+
function generateTrackingPixel(baseUrl, emailId, recipientId) {
|
|
278
|
+
const url = new URL(baseUrl);
|
|
279
|
+
url.searchParams.set("email_id", emailId);
|
|
280
|
+
url.searchParams.set("recipient_id", recipientId);
|
|
281
|
+
url.searchParams.set("t", Date.now().toString());
|
|
282
|
+
return url.toString();
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Add UTM parameters to URL
|
|
286
|
+
*
|
|
287
|
+
* Adds UTM tracking parameters to a URL for campaign tracking.
|
|
288
|
+
*
|
|
289
|
+
* @param url - Original URL
|
|
290
|
+
* @param utmParams - UTM parameters object
|
|
291
|
+
* @returns URL with UTM parameters added
|
|
292
|
+
* @example
|
|
293
|
+
* ```typescript
|
|
294
|
+
* const trackedUrl = addUtmParams('https://example.com/product', {
|
|
295
|
+
* source: 'email',
|
|
296
|
+
* medium: 'newsletter',
|
|
297
|
+
* campaign: 'summer_sale',
|
|
298
|
+
* content: 'header_button'
|
|
299
|
+
* });
|
|
300
|
+
* ```
|
|
301
|
+
*/
|
|
302
|
+
function addUtmParams(url, utmParams) {
|
|
303
|
+
const urlObj = new URL(url);
|
|
304
|
+
if (utmParams.source) urlObj.searchParams.set("utm_source", utmParams.source);
|
|
305
|
+
if (utmParams.medium) urlObj.searchParams.set("utm_medium", utmParams.medium);
|
|
306
|
+
if (utmParams.campaign) urlObj.searchParams.set("utm_campaign", utmParams.campaign);
|
|
307
|
+
if (utmParams.term) urlObj.searchParams.set("utm_term", utmParams.term);
|
|
308
|
+
if (utmParams.content) urlObj.searchParams.set("utm_content", utmParams.content);
|
|
309
|
+
return urlObj.toString();
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Parse email list from string
|
|
313
|
+
*
|
|
314
|
+
* Parses a comma or semicolon separated string of email addresses.
|
|
315
|
+
*
|
|
316
|
+
* @param emailString - String containing multiple email addresses
|
|
317
|
+
* @returns Array of trimmed email addresses
|
|
318
|
+
* @example
|
|
319
|
+
* ```typescript
|
|
320
|
+
* const emails = parseEmailList('user1@example.com, user2@example.com; user3@example.com');
|
|
321
|
+
* console.log(emails); // ['user1@example.com', 'user2@example.com', 'user3@example.com']
|
|
322
|
+
* ```
|
|
323
|
+
*/
|
|
324
|
+
function parseEmailList(emailString) {
|
|
325
|
+
return emailString.split(/[,;]/).map((email) => email.trim()).filter((email) => email.length > 0);
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* Deduplicate email list
|
|
329
|
+
*
|
|
330
|
+
* Removes duplicate email addresses from a list (case-insensitive).
|
|
331
|
+
*
|
|
332
|
+
* @param emails - Array of email addresses
|
|
333
|
+
* @returns Array with duplicates removed
|
|
334
|
+
* @example
|
|
335
|
+
* ```typescript
|
|
336
|
+
* const emails = ['user@example.com', 'USER@EXAMPLE.COM', 'other@example.com'];
|
|
337
|
+
* const unique = deduplicateEmails(emails);
|
|
338
|
+
* console.log(unique); // ['user@example.com', 'other@example.com']
|
|
339
|
+
* ```
|
|
340
|
+
*/
|
|
341
|
+
function deduplicateEmails(emails) {
|
|
342
|
+
const seen = /* @__PURE__ */ new Set();
|
|
343
|
+
return emails.filter((email) => {
|
|
344
|
+
const normalized = email.toLowerCase().trim();
|
|
345
|
+
if (seen.has(normalized)) return false;
|
|
346
|
+
seen.add(normalized);
|
|
347
|
+
return true;
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* Chunk email list for batch sending
|
|
352
|
+
*
|
|
353
|
+
* Splits a large email list into smaller chunks for batch processing.
|
|
354
|
+
*
|
|
355
|
+
* @param emails - Array of email addresses
|
|
356
|
+
* @param chunkSize - Size of each chunk (default: 100)
|
|
357
|
+
* @returns Array of email chunks
|
|
358
|
+
* @example
|
|
359
|
+
* ```typescript
|
|
360
|
+
* const emails = ['user1@example.com', 'user2@example.com', ...]; // 250 emails
|
|
361
|
+
* const chunks = chunkEmails(emails, 100);
|
|
362
|
+
* console.log(chunks.length); // 3 chunks
|
|
363
|
+
* console.log(chunks[0].length); // 100
|
|
364
|
+
* console.log(chunks[2].length); // 50
|
|
365
|
+
* ```
|
|
366
|
+
*/
|
|
367
|
+
function chunkEmails(emails, chunkSize = 100) {
|
|
368
|
+
const chunks = [];
|
|
369
|
+
for (let i = 0; i < emails.length; i += chunkSize) chunks.push(emails.slice(i, i + chunkSize));
|
|
370
|
+
return chunks;
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* Get MIME type from file extension
|
|
374
|
+
*
|
|
375
|
+
* Returns the appropriate MIME type for common file extensions.
|
|
376
|
+
*
|
|
377
|
+
* @param filename - Filename or extension
|
|
378
|
+
* @returns MIME type string
|
|
379
|
+
* @example
|
|
380
|
+
* ```typescript
|
|
381
|
+
* console.log(getMimeType('document.pdf')); // 'application/pdf'
|
|
382
|
+
* console.log(getMimeType('image.jpg')); // 'image/jpeg'
|
|
383
|
+
* console.log(getMimeType('.png')); // 'image/png'
|
|
384
|
+
* ```
|
|
385
|
+
*/
|
|
386
|
+
function getMimeType(filename) {
|
|
387
|
+
return {
|
|
388
|
+
jpg: "image/jpeg",
|
|
389
|
+
jpeg: "image/jpeg",
|
|
390
|
+
png: "image/png",
|
|
391
|
+
gif: "image/gif",
|
|
392
|
+
webp: "image/webp",
|
|
393
|
+
svg: "image/svg+xml",
|
|
394
|
+
pdf: "application/pdf",
|
|
395
|
+
doc: "application/msword",
|
|
396
|
+
docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
397
|
+
xls: "application/vnd.ms-excel",
|
|
398
|
+
xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
399
|
+
ppt: "application/vnd.ms-powerpoint",
|
|
400
|
+
pptx: "application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
401
|
+
txt: "text/plain",
|
|
402
|
+
csv: "text/csv",
|
|
403
|
+
html: "text/html",
|
|
404
|
+
css: "text/css",
|
|
405
|
+
js: "text/javascript",
|
|
406
|
+
json: "application/json",
|
|
407
|
+
xml: "application/xml",
|
|
408
|
+
zip: "application/zip",
|
|
409
|
+
rar: "application/x-rar-compressed",
|
|
410
|
+
"7z": "application/x-7z-compressed",
|
|
411
|
+
tar: "application/x-tar",
|
|
412
|
+
gz: "application/gzip"
|
|
413
|
+
}[filename.toLowerCase().split(".").pop() || ""] || "application/octet-stream";
|
|
414
|
+
}
|
|
415
|
+
/**
|
|
416
|
+
* Format file size for display
|
|
417
|
+
*
|
|
418
|
+
* Converts file size in bytes to human-readable format.
|
|
419
|
+
*
|
|
420
|
+
* @param bytes - File size in bytes
|
|
421
|
+
* @param decimals - Number of decimal places (default: 2)
|
|
422
|
+
* @returns Formatted file size string
|
|
423
|
+
* @example
|
|
424
|
+
* ```typescript
|
|
425
|
+
* console.log(formatFileSize(1024)); // "1.00 KB"
|
|
426
|
+
* console.log(formatFileSize(1048576)); // "1.00 MB"
|
|
427
|
+
* console.log(formatFileSize(1073741824, 1)); // "1.0 GB"
|
|
428
|
+
* ```
|
|
429
|
+
*/
|
|
430
|
+
function formatFileSize(bytes, decimals = 2) {
|
|
431
|
+
if (bytes === 0) return "0 Bytes";
|
|
432
|
+
const k = 1024;
|
|
433
|
+
const dm = decimals < 0 ? 0 : decimals;
|
|
434
|
+
const sizes = [
|
|
435
|
+
"Bytes",
|
|
436
|
+
"KB",
|
|
437
|
+
"MB",
|
|
438
|
+
"GB",
|
|
439
|
+
"TB"
|
|
440
|
+
];
|
|
441
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
442
|
+
return `${(bytes / k ** i).toFixed(dm)} ${sizes[i]}`;
|
|
443
|
+
}
|
|
444
|
+
//#endregion
|
|
445
|
+
export { addUtmParams, chunkEmails, deduplicateEmails, extractDisplayName, extractEmail, filterValidEmails, formatEmailAddress, formatFileSize, generatePreviewText, generateTrackingPixel, generateUnsubscribeLink, getMimeType, htmlToText, isValidEmail, parseEmailList, renderEmailTemplate, sanitizeHtml, truncateText, validateEmails };
|
|
446
|
+
|
|
447
|
+
//# sourceMappingURL=helpers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"helpers.js","names":[],"sources":["../src/helpers.ts"],"sourcesContent":["/**\n * @fileoverview Email Helper Utilities\n *\n * This module provides utility functions for email operations including:\n * - Template rendering and text conversion\n * - Email address validation and formatting\n * - Content processing and sanitization\n * - Attachment handling\n * - Email list management\n *\n * Import from '@kumix/email/helpers' to access these utilities.\n *\n * @example\n * ```typescript\n * import {\n * renderEmailTemplate,\n * htmlToText,\n * isValidEmail,\n * formatEmailAddress,\n * sanitizeHtml,\n * generateUnsubscribeLink\n * } from '@kumix/email/helpers';\n * ```\n */\n\nimport React from \"react\";\nimport { render } from \"react-email\";\n\n// =============================================================================\n// TEMPLATE RENDERING\n// =============================================================================\n\n/**\n * Render React component to HTML string\n *\n * Converts a React email template component to HTML string that can be sent via email.\n *\n * @param Template - React component to render\n * @param props - Props to pass to the component\n * @returns Promise resolving to HTML string\n * @example\n * ```typescript\n * const WelcomeEmail = ({ name }: { name: string }) => <h1>Welcome {name}!</h1>;\n *\n * const html = await renderEmailTemplate(WelcomeEmail, { name: 'John' });\n * console.log(html); // \"<h1>Welcome John!</h1>\"\n * ```\n */\nexport async function renderEmailTemplate<T extends Record<string, unknown>>(\n Template: React.ComponentType<T>,\n props: T,\n): Promise<string> {\n return render(React.createElement(Template, props));\n}\n\n/**\n * Generate plain text from HTML\n *\n * Converts HTML content to plain text by removing tags and normalizing whitespace.\n * Useful for creating text versions of HTML emails.\n *\n * @param html - HTML string to convert\n * @returns Plain text version of the HTML\n * @example\n * ```typescript\n * const html = '<h1>Hello</h1><p>Welcome to our <strong>platform</strong>!</p>';\n * const text = htmlToText(html);\n * console.log(text); // \"Hello Welcome to our platform!\"\n * ```\n */\nexport function htmlToText(html: string): string {\n return html\n .replace(/<br\\s*\\/?>/gi, \"\\n\") // Convert <br> to newlines\n .replace(/<\\/p>/gi, \"\\n\\n\") // Convert </p> to double newlines\n .replace(/<\\/h[1-6]>/gi, \"\\n\\n\") // Convert heading endings to double newlines\n .replace(/<[^>]*>/g, \" \") // Remove all other HTML tags\n .replace(/\\s+/g, \" \") // Normalize whitespace\n .replace(/\\n\\s+/g, \"\\n\") // Remove spaces after newlines\n .trim();\n}\n\n// =============================================================================\n// EMAIL VALIDATION\n// =============================================================================\n\n/**\n * Validate email address format\n *\n * Checks if an email address has a valid format using RFC 5322 compliant regex.\n *\n * @param email - Email address to validate\n * @returns true if email format is valid, false otherwise\n * @example\n * ```typescript\n * console.log(isValidEmail('user@example.com')); // true\n * console.log(isValidEmail('invalid-email')); // false\n * console.log(isValidEmail('user+tag@example.co.uk')); // true\n * ```\n */\nexport function isValidEmail(email: string): boolean {\n const emailRegex =\n /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+$/;\n return emailRegex.test(email);\n}\n\n/**\n * Validate multiple email addresses\n *\n * Validates an array of email addresses or a single email address.\n *\n * @param emails - Single email or array of emails to validate\n * @returns true if all emails are valid, false if any are invalid\n * @example\n * ```typescript\n * console.log(validateEmails('user@example.com')); // true\n * console.log(validateEmails(['user1@example.com', 'user2@example.com'])); // true\n * console.log(validateEmails(['valid@example.com', 'invalid-email'])); // false\n * ```\n */\nexport function validateEmails(emails: string | string[]): boolean {\n const emailArray = Array.isArray(emails) ? emails : [emails];\n return emailArray.every((email) => isValidEmail(email.trim()));\n}\n\n/**\n * Filter valid emails from a list\n *\n * Returns only the valid email addresses from a list, filtering out invalid ones.\n *\n * @param emails - Array of email addresses to filter\n * @returns Array containing only valid email addresses\n * @example\n * ```typescript\n * const emails = ['valid@example.com', 'invalid-email', 'another@example.com'];\n * const validEmails = filterValidEmails(emails);\n * console.log(validEmails); // ['valid@example.com', 'another@example.com']\n * ```\n */\nexport function filterValidEmails(emails: string[]): string[] {\n return emails.filter((email) => isValidEmail(email.trim()));\n}\n\n// =============================================================================\n// EMAIL FORMATTING\n// =============================================================================\n\n/**\n * Format email address with display name\n *\n * Combines a display name with an email address in the standard format.\n *\n * @param name - Display name for the email address\n * @param email - Email address\n * @returns Formatted email address string\n * @example\n * ```typescript\n * const formatted = formatEmailAddress('John Doe', 'john@example.com');\n * console.log(formatted); // \"John Doe <john@example.com>\"\n * ```\n */\nexport function formatEmailAddress(name: string, email: string): string {\n // Don't escape quotes - keep them as is for standard email format\n return `${name} <${email}>`;\n}\n\n/**\n * Extract email address from formatted string\n *\n * Extracts the email address from a formatted \"Name <email>\" string.\n *\n * @param formattedAddress - Formatted email address string\n * @returns Just the email address part\n * @example\n * ```typescript\n * const email = extractEmail('John Doe <john@example.com>');\n * console.log(email); // \"john@example.com\"\n *\n * const email2 = extractEmail('simple@example.com');\n * console.log(email2); // \"simple@example.com\"\n * ```\n */\nexport function extractEmail(formattedAddress: string): string {\n const match = formattedAddress.match(/<([^>]*)>/);\n return match ? match[1] : formattedAddress.trim();\n}\n\n/**\n * Extract display name from formatted email address\n *\n * Extracts the display name from a formatted \"Name <email>\" string.\n *\n * @param formattedAddress - Formatted email address string\n * @returns Display name or empty string if none found\n * @example\n * ```typescript\n * const name = extractDisplayName('John Doe <john@example.com>');\n * console.log(name); // \"John Doe\"\n *\n * const name2 = extractDisplayName('simple@example.com');\n * console.log(name2); // \"\"\n * ```\n */\nexport function extractDisplayName(formattedAddress: string): string {\n const match = formattedAddress.match(/^(.+?)\\s*<[^>]*>$/);\n return match ? match[1].trim().replace(/^[\"']|[\"']$/g, \"\") : \"\";\n}\n\n// =============================================================================\n// CONTENT PROCESSING\n// =============================================================================\n\n/**\n * Sanitize HTML content for email\n *\n * Removes potentially dangerous HTML elements and attributes while preserving\n * email-safe formatting.\n *\n * @param html - HTML content to sanitize\n * @returns Sanitized HTML safe for email\n * @example\n * ```typescript\n * const unsafeHtml = '<script>alert(\"xss\")</script><p>Safe content</p>';\n * const safeHtml = sanitizeHtml(unsafeHtml);\n * console.log(safeHtml); // \"<p>Safe content</p>\"\n * ```\n */\nexport function sanitizeHtml(html: string): string {\n // Remove script tags and their content\n let sanitized = html.replace(/<script\\b[^<]*(?:(?!<\\/script>)<[^<]*)*<\\/script>/gi, \"\");\n\n // Remove dangerous attributes\n sanitized = sanitized.replace(/\\s*on\\w+\\s*=\\s*[\"'][^\"']*[\"']/gi, \"\"); // onclick, onload, etc.\n sanitized = sanitized.replace(/\\s*javascript\\s*:/gi, \"\"); // javascript: urls\n\n // Remove style attributes that could be dangerous\n sanitized = sanitized.replace(/\\s*style\\s*=\\s*[\"'][^\"']*expression\\([^\"']*\\)[\"']/gi, \"\");\n\n return sanitized;\n}\n\n/**\n * Truncate text with ellipsis\n *\n * Truncates text to a specified length and adds ellipsis if needed.\n *\n * @param text - Text to truncate\n * @param maxLength - Maximum length before truncation\n * @param ellipsis - String to append when truncated (default: '...')\n * @returns Truncated text\n * @example\n * ```typescript\n * const text = 'This is a very long email subject line';\n * const truncated = truncateText(text, 20);\n * console.log(truncated); // \"This is a very long...\"\n * ```\n */\nexport function truncateText(text: string, maxLength: number, ellipsis: string = \"...\"): string {\n if (text.length <= maxLength) return text;\n return text.slice(0, maxLength - ellipsis.length) + ellipsis;\n}\n\n/**\n * Generate email preview text\n *\n * Creates a preview text from HTML content, suitable for email preview panes.\n *\n * @param html - HTML content to generate preview from\n * @param maxLength - Maximum length of preview text (default: 150)\n * @returns Preview text\n * @example\n * ```typescript\n * const html = '<h1>Welcome!</h1><p>Thank you for joining our platform.</p>';\n * const preview = generatePreviewText(html);\n * console.log(preview); // \"Welcome! Thank you for joining our platform.\"\n * ```\n */\nexport function generatePreviewText(html: string, maxLength: number = 150): string {\n const text = htmlToText(html);\n return truncateText(text, maxLength);\n}\n\n// =============================================================================\n// URL AND LINK UTILITIES\n// =============================================================================\n\n/**\n * Generate unsubscribe link\n *\n * Creates an unsubscribe URL with proper parameters.\n *\n * @param baseUrl - Base URL for unsubscribe endpoint\n * @param email - Email address to unsubscribe\n * @param token - Optional security token\n * @returns Complete unsubscribe URL\n * @example\n * ```typescript\n * const link = generateUnsubscribeLink(\n * 'https://example.com/unsubscribe',\n * 'user@example.com',\n * 'secure-token-123'\n * );\n * console.log(link); // \"https://example.com/unsubscribe?email=user%40example.com&token=secure-token-123\"\n * ```\n */\nexport function generateUnsubscribeLink(baseUrl: string, email: string, token?: string): string {\n const url = new URL(baseUrl);\n url.searchParams.set(\"email\", email);\n if (token) {\n url.searchParams.set(\"token\", token);\n }\n return url.toString();\n}\n\n/**\n * Generate tracking pixel URL\n *\n * Creates a tracking pixel URL for email open tracking.\n *\n * @param baseUrl - Base URL for tracking endpoint\n * @param emailId - Unique identifier for the email\n * @param recipientId - Unique identifier for the recipient\n * @returns Tracking pixel URL\n * @example\n * ```typescript\n * const pixelUrl = generateTrackingPixel(\n * 'https://example.com/track',\n * 'email-123',\n * 'user-456'\n * );\n * // Use in email: <img src=\"${pixelUrl}\" width=\"1\" height=\"1\" />\n * ```\n */\nexport function generateTrackingPixel(\n baseUrl: string,\n emailId: string,\n recipientId: string,\n): string {\n const url = new URL(baseUrl);\n url.searchParams.set(\"email_id\", emailId);\n url.searchParams.set(\"recipient_id\", recipientId);\n url.searchParams.set(\"t\", Date.now().toString());\n return url.toString();\n}\n\n/**\n * Add UTM parameters to URL\n *\n * Adds UTM tracking parameters to a URL for campaign tracking.\n *\n * @param url - Original URL\n * @param utmParams - UTM parameters object\n * @returns URL with UTM parameters added\n * @example\n * ```typescript\n * const trackedUrl = addUtmParams('https://example.com/product', {\n * source: 'email',\n * medium: 'newsletter',\n * campaign: 'summer_sale',\n * content: 'header_button'\n * });\n * ```\n */\nexport function addUtmParams(\n url: string,\n utmParams: {\n source?: string;\n medium?: string;\n campaign?: string;\n term?: string;\n content?: string;\n },\n): string {\n const urlObj = new URL(url);\n\n if (utmParams.source) urlObj.searchParams.set(\"utm_source\", utmParams.source);\n if (utmParams.medium) urlObj.searchParams.set(\"utm_medium\", utmParams.medium);\n if (utmParams.campaign) urlObj.searchParams.set(\"utm_campaign\", utmParams.campaign);\n if (utmParams.term) urlObj.searchParams.set(\"utm_term\", utmParams.term);\n if (utmParams.content) urlObj.searchParams.set(\"utm_content\", utmParams.content);\n\n return urlObj.toString();\n}\n\n// =============================================================================\n// EMAIL LIST UTILITIES\n// =============================================================================\n\n/**\n * Parse email list from string\n *\n * Parses a comma or semicolon separated string of email addresses.\n *\n * @param emailString - String containing multiple email addresses\n * @returns Array of trimmed email addresses\n * @example\n * ```typescript\n * const emails = parseEmailList('user1@example.com, user2@example.com; user3@example.com');\n * console.log(emails); // ['user1@example.com', 'user2@example.com', 'user3@example.com']\n * ```\n */\nexport function parseEmailList(emailString: string): string[] {\n return emailString\n .split(/[,;]/)\n .map((email) => email.trim())\n .filter((email) => email.length > 0);\n}\n\n/**\n * Deduplicate email list\n *\n * Removes duplicate email addresses from a list (case-insensitive).\n *\n * @param emails - Array of email addresses\n * @returns Array with duplicates removed\n * @example\n * ```typescript\n * const emails = ['user@example.com', 'USER@EXAMPLE.COM', 'other@example.com'];\n * const unique = deduplicateEmails(emails);\n * console.log(unique); // ['user@example.com', 'other@example.com']\n * ```\n */\nexport function deduplicateEmails(emails: string[]): string[] {\n const seen = new Set<string>();\n return emails.filter((email) => {\n const normalized = email.toLowerCase().trim();\n if (seen.has(normalized)) {\n return false;\n }\n seen.add(normalized);\n return true;\n });\n}\n\n/**\n * Chunk email list for batch sending\n *\n * Splits a large email list into smaller chunks for batch processing.\n *\n * @param emails - Array of email addresses\n * @param chunkSize - Size of each chunk (default: 100)\n * @returns Array of email chunks\n * @example\n * ```typescript\n * const emails = ['user1@example.com', 'user2@example.com', ...]; // 250 emails\n * const chunks = chunkEmails(emails, 100);\n * console.log(chunks.length); // 3 chunks\n * console.log(chunks[0].length); // 100\n * console.log(chunks[2].length); // 50\n * ```\n */\nexport function chunkEmails(emails: string[], chunkSize: number = 100): string[][] {\n const chunks: string[][] = [];\n for (let i = 0; i < emails.length; i += chunkSize) {\n chunks.push(emails.slice(i, i + chunkSize));\n }\n return chunks;\n}\n\n// =============================================================================\n// ATTACHMENT UTILITIES\n// =============================================================================\n\n/**\n * Get MIME type from file extension\n *\n * Returns the appropriate MIME type for common file extensions.\n *\n * @param filename - Filename or extension\n * @returns MIME type string\n * @example\n * ```typescript\n * console.log(getMimeType('document.pdf')); // 'application/pdf'\n * console.log(getMimeType('image.jpg')); // 'image/jpeg'\n * console.log(getMimeType('.png')); // 'image/png'\n * ```\n */\nexport function getMimeType(filename: string): string {\n const ext = filename.toLowerCase().split(\".\").pop() || \"\";\n\n const mimeTypes: Record<string, string> = {\n // Images\n jpg: \"image/jpeg\",\n jpeg: \"image/jpeg\",\n png: \"image/png\",\n gif: \"image/gif\",\n webp: \"image/webp\",\n svg: \"image/svg+xml\",\n\n // Documents\n pdf: \"application/pdf\",\n doc: \"application/msword\",\n docx: \"application/vnd.openxmlformats-officedocument.wordprocessingml.document\",\n xls: \"application/vnd.ms-excel\",\n xlsx: \"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet\",\n ppt: \"application/vnd.ms-powerpoint\",\n pptx: \"application/vnd.openxmlformats-officedocument.presentationml.presentation\",\n\n // Text\n txt: \"text/plain\",\n csv: \"text/csv\",\n html: \"text/html\",\n css: \"text/css\",\n js: \"text/javascript\",\n json: \"application/json\",\n xml: \"application/xml\",\n\n // Archives\n zip: \"application/zip\",\n rar: \"application/x-rar-compressed\",\n \"7z\": \"application/x-7z-compressed\",\n tar: \"application/x-tar\",\n gz: \"application/gzip\",\n };\n\n return mimeTypes[ext] || \"application/octet-stream\";\n}\n\n/**\n * Format file size for display\n *\n * Converts file size in bytes to human-readable format.\n *\n * @param bytes - File size in bytes\n * @param decimals - Number of decimal places (default: 2)\n * @returns Formatted file size string\n * @example\n * ```typescript\n * console.log(formatFileSize(1024)); // \"1.00 KB\"\n * console.log(formatFileSize(1048576)); // \"1.00 MB\"\n * console.log(formatFileSize(1073741824, 1)); // \"1.0 GB\"\n * ```\n */\nexport function formatFileSize(bytes: number, decimals: number = 2): string {\n if (bytes === 0) return \"0 Bytes\";\n\n const k = 1024;\n const dm = decimals < 0 ? 0 : decimals;\n const sizes = [\"Bytes\", \"KB\", \"MB\", \"GB\", \"TB\"];\n\n const i = Math.floor(Math.log(bytes) / Math.log(k));\n\n return `${(bytes / k ** i).toFixed(dm)} ${sizes[i]}`;\n}\n"],"mappings":"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"}
|