@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.
@@ -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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"}