@zyther/tzmail 0.1.4 → 0.5.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.
Files changed (132) hide show
  1. package/README.md +411 -78
  2. package/dist/core/enums/theme.enum.d.ts +11 -1
  3. package/dist/core/enums/theme.enum.d.ts.map +1 -1
  4. package/dist/core/enums/theme.enum.js +10 -0
  5. package/dist/core/interfaces/email.interface.d.ts +95 -5
  6. package/dist/core/interfaces/email.interface.d.ts.map +1 -1
  7. package/dist/core/interfaces/template.interface.d.ts +12 -1
  8. package/dist/core/interfaces/template.interface.d.ts.map +1 -1
  9. package/dist/factories/email-factory.d.ts +32 -6
  10. package/dist/factories/email-factory.d.ts.map +1 -1
  11. package/dist/factories/email-factory.js +110 -6
  12. package/dist/factories/template-factory.d.ts.map +1 -1
  13. package/dist/factories/template-factory.js +170 -60
  14. package/dist/index.d.ts +16 -3
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +23 -1
  17. package/dist/providers/mailgun.transport.d.ts +10 -0
  18. package/dist/providers/mailgun.transport.d.ts.map +1 -0
  19. package/dist/providers/mailgun.transport.js +85 -0
  20. package/dist/providers/nodemailer.transport.d.ts +11 -0
  21. package/dist/providers/nodemailer.transport.d.ts.map +1 -0
  22. package/dist/providers/nodemailer.transport.js +23 -0
  23. package/dist/providers/provider-utils.d.ts +17 -0
  24. package/dist/providers/provider-utils.d.ts.map +1 -0
  25. package/dist/providers/provider-utils.js +89 -0
  26. package/dist/providers/provider.interface.d.ts +30 -0
  27. package/dist/providers/provider.interface.d.ts.map +1 -0
  28. package/dist/providers/provider.interface.js +2 -0
  29. package/dist/providers/sendgrid.transport.d.ts +10 -0
  30. package/dist/providers/sendgrid.transport.d.ts.map +1 -0
  31. package/dist/providers/sendgrid.transport.js +76 -0
  32. package/dist/queues/bullmq-email-queue.d.ts +153 -0
  33. package/dist/queues/bullmq-email-queue.d.ts.map +1 -0
  34. package/dist/queues/bullmq-email-queue.js +292 -0
  35. package/dist/services/attachment.service.d.ts +15 -37
  36. package/dist/services/attachment.service.d.ts.map +1 -1
  37. package/dist/services/attachment.service.js +58 -46
  38. package/dist/services/email.service.d.ts +22 -22
  39. package/dist/services/email.service.d.ts.map +1 -1
  40. package/dist/services/email.service.js +117 -33
  41. package/dist/services/template.service.d.ts +24 -111
  42. package/dist/services/template.service.d.ts.map +1 -1
  43. package/dist/services/template.service.js +273 -362
  44. package/dist/templates/base/color-accessibility.d.ts +4 -0
  45. package/dist/templates/base/color-accessibility.d.ts.map +1 -0
  46. package/dist/templates/base/color-accessibility.js +38 -0
  47. package/dist/templates/base/template-builder.d.ts +4 -1
  48. package/dist/templates/base/template-builder.d.ts.map +1 -1
  49. package/dist/templates/base/template-builder.js +276 -527
  50. package/dist/templates/themes/aurora.theme.d.ts +12 -0
  51. package/dist/templates/themes/aurora.theme.d.ts.map +1 -0
  52. package/dist/templates/themes/aurora.theme.js +88 -0
  53. package/dist/templates/themes/corporate.theme.d.ts +2 -1
  54. package/dist/templates/themes/corporate.theme.d.ts.map +1 -1
  55. package/dist/templates/themes/corporate.theme.js +139 -102
  56. package/dist/templates/themes/editorial.theme.d.ts +12 -0
  57. package/dist/templates/themes/editorial.theme.d.ts.map +1 -0
  58. package/dist/templates/themes/editorial.theme.js +90 -0
  59. package/dist/templates/themes/forest.theme.d.ts +12 -0
  60. package/dist/templates/themes/forest.theme.d.ts.map +1 -0
  61. package/dist/templates/themes/forest.theme.js +89 -0
  62. package/dist/templates/themes/glass.theme.d.ts +12 -0
  63. package/dist/templates/themes/glass.theme.d.ts.map +1 -0
  64. package/dist/templates/themes/glass.theme.js +88 -0
  65. package/dist/templates/themes/lavender.theme.d.ts +12 -0
  66. package/dist/templates/themes/lavender.theme.d.ts.map +1 -0
  67. package/dist/templates/themes/lavender.theme.js +91 -0
  68. package/dist/templates/themes/midnight.theme.d.ts +12 -0
  69. package/dist/templates/themes/midnight.theme.d.ts.map +1 -0
  70. package/dist/templates/themes/midnight.theme.js +91 -0
  71. package/dist/templates/themes/minimal.theme.d.ts +2 -1
  72. package/dist/templates/themes/minimal.theme.d.ts.map +1 -1
  73. package/dist/templates/themes/minimal.theme.js +142 -107
  74. package/dist/templates/themes/modern.theme.d.ts +2 -1
  75. package/dist/templates/themes/modern.theme.d.ts.map +1 -1
  76. package/dist/templates/themes/modern.theme.js +109 -74
  77. package/dist/templates/themes/monokai.theme.d.ts +2 -1
  78. package/dist/templates/themes/monokai.theme.d.ts.map +1 -1
  79. package/dist/templates/themes/monokai.theme.js +102 -66
  80. package/dist/templates/themes/neobrutal.theme.d.ts +12 -0
  81. package/dist/templates/themes/neobrutal.theme.d.ts.map +1 -0
  82. package/dist/templates/themes/neobrutal.theme.js +91 -0
  83. package/dist/templates/themes/ocean.theme.d.ts +12 -0
  84. package/dist/templates/themes/ocean.theme.d.ts.map +1 -0
  85. package/dist/templates/themes/ocean.theme.js +90 -0
  86. package/dist/templates/themes/pastel.theme.d.ts +12 -0
  87. package/dist/templates/themes/pastel.theme.d.ts.map +1 -0
  88. package/dist/templates/themes/pastel.theme.js +89 -0
  89. package/dist/templates/themes/sunset.theme.d.ts +12 -0
  90. package/dist/templates/themes/sunset.theme.d.ts.map +1 -0
  91. package/dist/templates/themes/sunset.theme.js +90 -0
  92. package/dist/templates/themes/system.theme.d.ts +2 -1
  93. package/dist/templates/themes/system.theme.d.ts.map +1 -1
  94. package/dist/templates/themes/system.theme.js +81 -47
  95. package/dist/templates/themes/theme.interface.d.ts +48 -0
  96. package/dist/templates/themes/theme.interface.d.ts.map +1 -1
  97. package/package.json +36 -14
  98. package/src/core/enums/template-part.enum.ts +7 -0
  99. package/src/core/enums/theme.enum.ts +18 -0
  100. package/src/core/interfaces/email.interface.ts +116 -0
  101. package/src/core/interfaces/template.interface.ts +71 -0
  102. package/src/factories/email-factory.ts +208 -0
  103. package/src/factories/template-factory.ts +201 -0
  104. package/src/index.ts +53 -0
  105. package/src/providers/mailgun.transport.ts +80 -0
  106. package/src/providers/nodemailer.transport.ts +20 -0
  107. package/src/providers/provider-utils.ts +91 -0
  108. package/src/providers/provider.interface.ts +26 -0
  109. package/src/providers/sendgrid.transport.ts +80 -0
  110. package/src/queues/bullmq-email-queue.ts +418 -0
  111. package/src/services/attachment.service.ts +98 -0
  112. package/src/services/email.service.ts +151 -0
  113. package/src/services/template.service.ts +431 -0
  114. package/src/templates/base/color-accessibility.ts +31 -0
  115. package/src/templates/base/template-builder.ts +438 -0
  116. package/src/templates/themes/aurora.theme.ts +91 -0
  117. package/src/templates/themes/corporate.theme.ts +156 -0
  118. package/src/templates/themes/editorial.theme.ts +93 -0
  119. package/src/templates/themes/forest.theme.ts +92 -0
  120. package/src/templates/themes/glass.theme.ts +91 -0
  121. package/src/templates/themes/lavender.theme.ts +94 -0
  122. package/src/templates/themes/midnight.theme.ts +94 -0
  123. package/src/templates/themes/minimal.theme.ts +158 -0
  124. package/src/templates/themes/modern.theme.ts +124 -0
  125. package/src/templates/themes/monokai.theme.ts +117 -0
  126. package/src/templates/themes/neobrutal.theme.ts +94 -0
  127. package/src/templates/themes/ocean.theme.ts +93 -0
  128. package/src/templates/themes/pastel.theme.ts +92 -0
  129. package/src/templates/themes/sunset.theme.ts +93 -0
  130. package/src/templates/themes/system.theme.ts +92 -0
  131. package/src/templates/themes/theme.interface.ts +95 -0
  132. package/src/tests/template.service.test.ts +1 -0
@@ -7,12 +7,125 @@ const monokai_theme_1 = require("../templates/themes/monokai.theme");
7
7
  const modern_theme_1 = require("../templates/themes/modern.theme");
8
8
  const corporate_theme_1 = require("../templates/themes/corporate.theme");
9
9
  const minimal_theme_1 = require("../templates/themes/minimal.theme");
10
+ const aurora_theme_1 = require("../templates/themes/aurora.theme");
11
+ const ocean_theme_1 = require("../templates/themes/ocean.theme");
12
+ const sunset_theme_1 = require("../templates/themes/sunset.theme");
13
+ const lavender_theme_1 = require("../templates/themes/lavender.theme");
14
+ const forest_theme_1 = require("../templates/themes/forest.theme");
15
+ const editorial_theme_1 = require("../templates/themes/editorial.theme");
16
+ const pastel_theme_1 = require("../templates/themes/pastel.theme");
17
+ const neobrutal_theme_1 = require("../templates/themes/neobrutal.theme");
18
+ const glass_theme_1 = require("../templates/themes/glass.theme");
19
+ const midnight_theme_1 = require("../templates/themes/midnight.theme");
10
20
  const template_builder_1 = require("../templates/base/template-builder");
21
+ const color_accessibility_1 = require("../templates/base/color-accessibility");
22
+ const SAFE_COLOR = /^(#[\da-f]{3,8}|[a-z]{1,24}|(?:rgb|rgba|hsl|hsla)\([\d.%\s,+/-]+\))$/i;
23
+ const SAFE_LANGUAGE_TAG = /^[A-Za-z]{2,8}(?:-[A-Za-z0-9]{1,8})*$/;
24
+ const SAFE_NAMED_COLORS = new Set(('transparent inherit currentcolor aliceblue antiquewhite aqua aquamarine azure beige bisque black blanchedalmond blue blueviolet brown burlywood cadetblue chartreuse chocolate coral cornflowerblue cornsilk crimson cyan darkblue darkcyan darkgoldenrod darkgray darkgrey darkgreen darkkhaki darkmagenta darkolivegreen darkorange darkorchid darkred darksalmon darkseagreen darkslateblue darkslategray darkslategrey darkturquoise darkviolet deeppink deepskyblue dimgray dimgrey dodgerblue firebrick floralwhite forestgreen fuchsia gainsboro ghostwhite gold goldenrod gray grey green greenyellow honeydew hotpink indianred indigo ivory khaki lavender lavenderblush lawngreen lemonchiffon lightblue lightcoral lightcyan lightgoldenrodyellow lightgray lightgrey lightgreen lightpink lightsalmon lightseagreen lightskyblue lightslategray lightslategrey lightsteelblue lightyellow lime limegreen linen magenta maroon mediumaquamarine mediumblue mediumorchid mediumpurple mediumseagreen mediumslateblue mediumspringgreen mediumturquoise mediumvioletred midnightblue mintcream mistyrose moccasin navajowhite navy oldlace olive olivedrab orange orangered orchid palegoldenrod palegreen paleturquoise palevioletred papayawhip peachpuff peru pink plum powderblue purple rebeccapurple red rosybrown royalblue saddlebrown salmon sandybrown seagreen seashell sienna silver skyblue slateblue slategray slategrey snow springgreen steelblue tan teal thistle tomato turquoise violet wheat white whitesmoke yellow yellowgreen').split(' '));
25
+ function cssColor(value, fallback) {
26
+ if (value === undefined)
27
+ return fallback;
28
+ const trimmed = value.trim();
29
+ if (!SAFE_COLOR.test(trimmed) || (!trimmed.startsWith('#') && !/^(rgb|rgba|hsl|hsla)\(/i.test(trimmed) && !SAFE_NAMED_COLORS.has(trimmed.toLowerCase()))) {
30
+ throw new Error('Invalid template color value');
31
+ }
32
+ return trimmed;
33
+ }
34
+ function escapeHtml(value) {
35
+ return String(value ?? '').replace(/[&<>"']/g, character => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[character]));
36
+ }
37
+ function interpolate(value, variables, escapeValues = false) {
38
+ if (value === undefined)
39
+ return undefined;
40
+ return value.replace(/\{\{\s*([A-Za-z0-9_.-]+)\s*\}\}/g, (_placeholder, path) => {
41
+ let resolved = variables;
42
+ for (const segment of path.split('.')) {
43
+ if (resolved === null || typeof resolved !== 'object')
44
+ throw new Error(`Missing template variable: ${path}`);
45
+ resolved = resolved[segment];
46
+ }
47
+ if (resolved === undefined)
48
+ throw new Error(`Missing template variable: ${path}`);
49
+ if (resolved !== null && !['string', 'number', 'boolean'].includes(typeof resolved)) {
50
+ throw new Error(`Template variable ${path} must be a string, number, boolean, or null`);
51
+ }
52
+ const text = resolved === null ? '' : String(resolved);
53
+ return escapeValues ? escapeHtml(text) : text;
54
+ });
55
+ }
56
+ function resolveTemplateConfig(config, variables) {
57
+ return {
58
+ ...config,
59
+ preheader: interpolate(config.preheader, variables),
60
+ header: config.header ? {
61
+ ...config.header,
62
+ logo: config.header.logo ? {
63
+ ...config.header.logo,
64
+ text: interpolate(config.header.logo.text, variables),
65
+ imageUrl: interpolate(config.header.logo.imageUrl, variables),
66
+ alt: interpolate(config.header.logo.alt, variables)
67
+ } : undefined
68
+ } : undefined,
69
+ body: config.body ? {
70
+ ...config.body,
71
+ title: interpolate(config.body.title, variables),
72
+ message: interpolate(config.body.message, variables),
73
+ // Substitutions inside trusted HTML are escaped before insertion.
74
+ content: interpolate(config.body.content, variables, true),
75
+ buttonText: interpolate(config.body.buttonText, variables),
76
+ buttonUrl: interpolate(config.body.buttonUrl, variables)
77
+ } : undefined,
78
+ footer: config.footer ? {
79
+ ...config.footer,
80
+ links: config.footer.links?.map(link => ({
81
+ text: interpolate(link.text, variables),
82
+ url: interpolate(link.url, variables)
83
+ })),
84
+ socialLinks: config.footer.socialLinks?.map(link => ({
85
+ ...link,
86
+ url: interpolate(link.url, variables)
87
+ })),
88
+ copyrightText: interpolate(config.footer.copyrightText, variables),
89
+ unsubscribeText: interpolate(config.footer.unsubscribeText, variables),
90
+ unsubscribeUrl: interpolate(config.footer.unsubscribeUrl, variables)
91
+ } : undefined
92
+ };
93
+ }
11
94
  class TemplateFactory {
95
+ static themes = new Map([
96
+ [theme_enum_1.ThemeType.SYSTEM, new system_theme_1.SystemTheme()],
97
+ [theme_enum_1.ThemeType.MONOKAI, new monokai_theme_1.MonokaiTheme()],
98
+ [theme_enum_1.ThemeType.MODERN, new modern_theme_1.ModernTheme()],
99
+ [theme_enum_1.ThemeType.CORPORATE, new corporate_theme_1.CorporateTheme()],
100
+ [theme_enum_1.ThemeType.MINIMAL, new minimal_theme_1.MinimalTheme()],
101
+ [theme_enum_1.ThemeType.AURORA, new aurora_theme_1.AuroraTheme()],
102
+ [theme_enum_1.ThemeType.OCEAN, new ocean_theme_1.OceanTheme()],
103
+ [theme_enum_1.ThemeType.SUNSET, new sunset_theme_1.SunsetTheme()],
104
+ [theme_enum_1.ThemeType.LAVENDER, new lavender_theme_1.LavenderTheme()],
105
+ [theme_enum_1.ThemeType.FOREST, new forest_theme_1.ForestTheme()],
106
+ [theme_enum_1.ThemeType.EDITORIAL, new editorial_theme_1.EditorialTheme()],
107
+ [theme_enum_1.ThemeType.PASTEL, new pastel_theme_1.PastelTheme()],
108
+ [theme_enum_1.ThemeType.NEOBRUTAL, new neobrutal_theme_1.NeoBrutalTheme()],
109
+ [theme_enum_1.ThemeType.GLASS, new glass_theme_1.GlassTheme()],
110
+ [theme_enum_1.ThemeType.MIDNIGHT, new midnight_theme_1.MidnightTheme()]
111
+ ]);
12
112
  static createTemplate(themeType, variant, config) {
13
113
  const theme = this.themes.get(themeType);
14
- if (!theme) {
114
+ if (!theme)
15
115
  throw new Error(`Theme ${themeType} not found`);
116
+ if (variant !== 'light' && variant !== 'dark')
117
+ throw new Error(`Invalid template variant: ${variant}`);
118
+ if (!config || typeof config !== 'object')
119
+ throw new Error('Template config must be an object');
120
+ if (config.language !== undefined && (typeof config.language !== 'string' || config.language.length > 63 || !SAFE_LANGUAGE_TAG.test(config.language))) {
121
+ throw new Error('Invalid template language tag');
122
+ }
123
+ if (config.direction !== undefined && !['ltr', 'rtl', 'auto'].includes(config.direction))
124
+ throw new Error('Invalid template text direction');
125
+ if (config.linkDecoration !== undefined && !['none', 'underline'].includes(config.linkDecoration))
126
+ throw new Error('Invalid template linkDecoration');
127
+ if (config.preheader !== undefined && (typeof config.preheader !== 'string' || config.preheader.length > 500 || /[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F]/.test(config.preheader))) {
128
+ throw new Error('preheader must be a string of at most 500 printable characters');
16
129
  }
17
130
  return {
18
131
  name: `${themeType}_${variant}`,
@@ -20,77 +133,74 @@ class TemplateFactory {
20
133
  variant,
21
134
  config,
22
135
  render: async (data) => {
23
- const builder = new template_builder_1.TemplateBuilder(theme, config, variant);
24
- const body = data.template.config.body || {};
25
- let bodyContent = body.content || `
136
+ const templateData = data?.templateData && typeof data.templateData === 'object' && !Array.isArray(data.templateData)
137
+ ? data.templateData
138
+ : {};
139
+ const renderConfig = resolveTemplateConfig(config, templateData);
140
+ const builder = new template_builder_1.TemplateBuilder(theme, renderConfig, variant);
141
+ // Renderer usa sempre a configuração capturada. `data` é somente conteúdo variável,
142
+ // nunca uma fonte alternativa de configuração/template.
143
+ const body = renderConfig.body || {};
144
+ const alignment = body.alignment || 'left';
145
+ if (!['left', 'center', 'right'].includes(alignment))
146
+ throw new Error('Invalid body alignment');
147
+ const fontSize = body.fontSize ?? 14;
148
+ if (!Number.isFinite(fontSize) || fontSize < 8 || fontSize > 72)
149
+ throw new Error('Invalid body font size');
150
+ const background = cssColor(body.backgroundColor, theme[variant].background);
151
+ const requestedTextColor = cssColor(body.textColor, theme[variant].text);
152
+ const textColor = (0, color_accessibility_1.readableTextColor)(requestedTextColor, background, theme[variant].text);
153
+ cssColor(renderConfig.header?.backgroundColor, 'transparent');
154
+ cssColor(renderConfig.header?.textColor, 'inherit');
155
+ cssColor(renderConfig.footer?.backgroundColor, 'transparent');
156
+ cssColor(renderConfig.footer?.textColor, 'inherit');
157
+ const titleFontSize = fontSize + 7;
158
+ const bodyCellStyle = `padding: 0; text-align: ${alignment}; background-color: ${background}; color: ${textColor}; font-size: ${fontSize}px; font-family: ${theme.typography.fontFamily};`;
159
+ const titleStyle = `margin: 0 0 16px 0; color: ${textColor}; font-family: ${theme.typography.fontFamily}; font-size: ${titleFontSize}px; font-weight: ${theme.typography.fontWeights.bold}; line-height: 1.3; text-align: ${alignment};`;
160
+ const messageStyle = `margin: 0; color: ${textColor}; font-family: ${theme.typography.fontFamily}; font-size: ${fontSize}px; line-height: 1.6; text-align: ${alignment};`;
161
+ // body.content é o único campo da configuração intencionalmente tratado como HTML confiável.
162
+ // Título, mensagem e valores de estilo são sempre escapados/validados e não viram HTML.
163
+ const bodyContent = body.content ?? `
26
164
  <table width="100%" cellpadding="0" cellspacing="0" role="presentation">
27
165
  <tr>
28
- <td style="padding: 20px; text-align: ${body.alignment || 'left'}; background-color: ${body.backgroundColor || 'transparent'}; color: ${body.textColor || 'inherit'}; font-size: ${body.fontSize || 14}px;">
29
-
30
- <h2 style="margin: 0 0 16px 0; font-size: ${(body.fontSize + 7) || 21}px; line-height: 1.3;">
31
- ${body.title || 'Hello!'}
32
- </h2>
33
-
34
- <p style="margin: 0; line-height: 1.6;">
35
- ${body.message || 'This is an email generated with tzMail.'}
36
- </p>
37
-
166
+ <td align="${alignment}" style="${escapeHtml(bodyCellStyle)}">
167
+ <h2 style="${escapeHtml(titleStyle)}">${escapeHtml(body.title ?? 'Hello!')}</h2>
168
+ <p style="${escapeHtml(messageStyle)}">${escapeHtml(body.message ?? 'This is an email generated with tzMail.')}</p>
38
169
  </td>
39
170
  </tr>
40
- </table>
41
- `;
42
- builder.buildHeader(data.headerContent).buildBody(bodyContent);
171
+ </table>`;
172
+ builder.buildPreheader().buildHeader(typeof data?.headerContent === 'string' ? data.headerContent : '').buildBody(bodyContent);
43
173
  if (body.buttonText && body.buttonUrl) {
44
- builder.buildButton(body.buttonText, body.buttonUrl, body.buttonVariant);
174
+ builder.buildButton(body.buttonText, body.buttonUrl, body.buttonVariant || 'primary');
45
175
  }
46
- return builder
47
- .buildFooter()
48
- .build();
176
+ return builder.buildFooter().build();
49
177
  }
50
178
  };
51
179
  }
52
- static getTheme(themeType) {
53
- return this.themes.get(themeType);
54
- }
55
- static listThemes() {
56
- return Array.from(this.themes.keys());
57
- }
180
+ static getTheme(themeType) { return this.themes.get(themeType); }
181
+ static listThemes() { return Array.from(this.themes.keys()); }
58
182
  static getThemeInfo(themeType) {
59
183
  const themeInfo = {
60
- [theme_enum_1.ThemeType.SYSTEM]: {
61
- name: 'System',
62
- description: 'Tema limpo e profissional com cores adaptativas',
63
- features: ['Design minimalista', 'Alta acessibilidade', 'Compatibilidade total']
64
- },
65
- [theme_enum_1.ThemeType.MONOKAI]: {
66
- name: 'Monokai',
67
- description: 'Inspirado no famoso tema de código, ideal para conteúdo técnico',
68
- features: ['Cores vibrantes', 'Destaque de sintaxe', 'Efeitos glow']
69
- },
70
- [theme_enum_1.ThemeType.MODERN]: {
71
- name: 'Modern',
72
- description: 'Design contemporâneo com gradientes e efeitos modernos',
73
- features: ['Gradientes elegantes', 'Glassmorphism', 'Animações suaves']
74
- },
75
- [theme_enum_1.ThemeType.CORPORATE]: {
76
- name: 'Corporate',
77
- description: 'Design profissional e elegante para empresas',
78
- features: ['Tipografia serifada', 'Detalhes em dourado', 'Layout estruturado']
79
- },
80
- [theme_enum_1.ThemeType.MINIMAL]: {
81
- name: 'Minimal',
82
- description: 'Design clean e focado no conteúdo',
83
- features: ['Sem distrações', 'Espaçamento generoso', 'Tipografia limpa']
84
- }
184
+ [theme_enum_1.ThemeType.SYSTEM]: { name: 'System', description: 'Clean, professional palette with adaptive light and dark variants', features: ['Minimal design', 'Accessible contrast', 'Email-client friendly'] },
185
+ [theme_enum_1.ThemeType.MONOKAI]: { name: 'Monokai', description: 'Vibrant, code-inspired colors for technical content', features: ['Vivid accents', 'Developer aesthetic', 'Dark-mode variant'] },
186
+ [theme_enum_1.ThemeType.MODERN]: { name: 'Modern', description: 'Contemporary layout with refined colors and typography', features: ['Modern palette', 'Clean hierarchy', 'Responsive structure'] },
187
+ [theme_enum_1.ThemeType.CORPORATE]: { name: 'Corporate', description: 'Professional, structured styling for business communication', features: ['Serif typography', 'Gold details', 'Formal hierarchy'] },
188
+ [theme_enum_1.ThemeType.MINIMAL]: { name: 'Minimal', description: 'Content-first design with generous whitespace', features: ['Low visual noise', 'Generous spacing', 'Clean typography'] },
189
+ [theme_enum_1.ThemeType.AURORA]: { name: 'Aurora', description: 'Cool northern-lights colors with teal and violet accents', features: ['Teal and violet palette', 'Balanced light and dark modes', 'System-font typography'] },
190
+ [theme_enum_1.ThemeType.OCEAN]: { name: 'Ocean', description: 'Calm marine blues and cyan for clear, trustworthy messages', features: ['Navy and cyan palette', 'Readable contrast', 'Email-safe fonts'] },
191
+ [theme_enum_1.ThemeType.SUNSET]: { name: 'Sunset', description: 'Warm coral, terracotta, and amber for an energetic impression', features: ['Warm accents', 'Polished surfaces', 'Light and dark modes'] },
192
+ [theme_enum_1.ThemeType.LAVENDER]: { name: 'Lavender', description: 'Elegant violet and rose accents on soft, readable surfaces', features: ['Lavender palette', 'Soft visual tone', 'Accessible contrast'] },
193
+ [theme_enum_1.ThemeType.FOREST]: { name: 'Forest', description: 'Natural evergreen, moss, sage, and warm cream colors', features: ['Nature-inspired palette', 'Calm serif typography', 'Light and dark modes'] },
194
+ [theme_enum_1.ThemeType.EDITORIAL]: { name: 'Editorial', description: 'Magazine-inspired typography with warm paper and ink tones', features: ['Serif hierarchy', 'Brick-red accent', 'Content-led layout'] },
195
+ [theme_enum_1.ThemeType.PASTEL]: { name: 'Pastel', description: 'Soft peach, mint, and lavender accents with readable contrast', features: ['Gentle pastel palette', 'Bright, friendly tone', 'Accessible text colors'] },
196
+ [theme_enum_1.ThemeType.NEOBRUTAL]: { name: 'Neo Brutal', description: 'Bold black-and-white styling with electric color accents', features: ['High contrast', 'Strong typography', 'Distinctive cobalt and yellow'] },
197
+ [theme_enum_1.ThemeType.GLASS]: { name: 'Glass', description: 'Cool, glass-inspired colors implemented with solid email-safe surfaces', features: ['Cool blue-gray palette', 'Solid client-compatible colors', 'Clean typography'] },
198
+ [theme_enum_1.ThemeType.MIDNIGHT]: { name: 'Midnight', description: 'Premium midnight navy and champagne-gold accents', features: ['Luxe dark palette', 'Warm paper light mode', 'Serif typography'] }
85
199
  };
86
- return themeInfo[themeType];
200
+ const info = themeInfo[themeType];
201
+ if (!info)
202
+ throw new Error(`Theme ${themeType} not found`);
203
+ return info;
87
204
  }
88
205
  }
89
206
  exports.TemplateFactory = TemplateFactory;
90
- TemplateFactory.themes = new Map([
91
- [theme_enum_1.ThemeType.SYSTEM, new system_theme_1.SystemTheme()],
92
- [theme_enum_1.ThemeType.MONOKAI, new monokai_theme_1.MonokaiTheme()],
93
- [theme_enum_1.ThemeType.MODERN, new modern_theme_1.ModernTheme()],
94
- [theme_enum_1.ThemeType.CORPORATE, new corporate_theme_1.CorporateTheme()],
95
- [theme_enum_1.ThemeType.MINIMAL, new minimal_theme_1.MinimalTheme()]
96
- ]);
package/dist/index.d.ts CHANGED
@@ -2,14 +2,27 @@ export { EmailFactory } from './factories/email-factory';
2
2
  export { TemplateFactory } from './factories/template-factory';
3
3
  export { ITemplate, ITemplateConfig } from './core/interfaces/template.interface';
4
4
  export { ThemeType } from './core/enums/theme.enum';
5
- export { ITheme } from './templates/themes/theme.interface';
6
- export { TemplateService } from './services/template.service';
5
+ export { ITheme, IThemeColorToken, IThemeEmailStyles, IThemeTextTransform } from './templates/themes/theme.interface';
6
+ export { TemplateService, ITemplateServiceOptions } from './services/template.service';
7
7
  export { EmailService } from './services/email.service';
8
- export { AttachmentService } from './services/attachment.service';
8
+ export { AttachmentService, IAttachmentServiceOptions } from './services/attachment.service';
9
+ export { IAmazonSesConfig, IAttachment, IEmailAuth, IEmailConfig, IEmailOptions, IEmailSendResult, IMailgunConfig, ISendGridConfig } from './core/interfaces/email.interface';
10
+ export { IEmailTransport, ITransportMessage, ITransportSendInfo } from './providers/provider.interface';
11
+ export { BullMQEmailQueue, IBullMQBackoffOptions, IBullMQEmailQueueConfig, IBullMQJobLike, IBullMQQueueEventPayload, IBullMQQueueEventsLike, IBullMQQueueMetrics, IBullMQRateLimiterOptions, IBullMQRuntime, IBullMQWorkerMetricsOptions, IEmailQueueJobData, IEmailQueueJobOptions, IEmailQueueSender, ISerializedEmailAttachment, IQueuedEmailJob } from './queues/bullmq-email-queue';
9
12
  export { TemplateBuilder } from './templates/base/template-builder';
10
13
  export { CorporateTheme } from './templates/themes/corporate.theme';
11
14
  export { MinimalTheme } from './templates/themes/minimal.theme';
12
15
  export { ModernTheme } from './templates/themes/modern.theme';
13
16
  export { MonokaiTheme } from './templates/themes/monokai.theme';
14
17
  export { SystemTheme } from './templates/themes/system.theme';
18
+ export { AuroraTheme } from './templates/themes/aurora.theme';
19
+ export { OceanTheme } from './templates/themes/ocean.theme';
20
+ export { SunsetTheme } from './templates/themes/sunset.theme';
21
+ export { LavenderTheme } from './templates/themes/lavender.theme';
22
+ export { ForestTheme } from './templates/themes/forest.theme';
23
+ export { EditorialTheme } from './templates/themes/editorial.theme';
24
+ export { PastelTheme } from './templates/themes/pastel.theme';
25
+ export { NeoBrutalTheme } from './templates/themes/neobrutal.theme';
26
+ export { GlassTheme } from './templates/themes/glass.theme';
27
+ export { MidnightTheme } from './templates/themes/midnight.theme';
15
28
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AACzD,OAAO,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAC/D,OAAO,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,sCAAsC,CAAC;AAClF,OAAO,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AACpD,OAAO,EAAE,MAAM,EAAE,MAAM,oCAAoC,CAAC;AAC5D,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAC9D,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AACxD,OAAO,EAAE,iBAAiB,EAAE,MAAM,+BAA+B,CAAC;AAClE,OAAO,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAC;AACpE,OAAO,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AACpE,OAAO,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAC;AAChE,OAAO,EAAE,WAAW,EAAE,MAAM,iCAAiC,CAAC;AAC9D,OAAO,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAC;AAChE,OAAO,EAAE,WAAW,EAAE,MAAM,iCAAiC,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AACzD,OAAO,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAC/D,OAAO,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,sCAAsC,CAAC;AAClF,OAAO,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AACpD,OAAO,EAAE,MAAM,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,oCAAoC,CAAC;AACtH,OAAO,EAAE,eAAe,EAAE,uBAAuB,EAAE,MAAM,6BAA6B,CAAC;AACvF,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AACxD,OAAO,EAAE,iBAAiB,EAAE,yBAAyB,EAAE,MAAM,+BAA+B,CAAC;AAC7F,OAAO,EACL,gBAAgB,EAChB,WAAW,EACX,UAAU,EACV,YAAY,EACZ,aAAa,EACb,gBAAgB,EAChB,cAAc,EACd,eAAe,EAChB,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EAAE,eAAe,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAC;AACxG,OAAO,EACL,gBAAgB,EAChB,qBAAqB,EACrB,uBAAuB,EACvB,cAAc,EACd,wBAAwB,EACxB,sBAAsB,EACtB,mBAAmB,EACnB,yBAAyB,EACzB,cAAc,EACd,2BAA2B,EAC3B,kBAAkB,EAClB,qBAAqB,EACrB,iBAAiB,EACjB,0BAA0B,EAC1B,eAAe,EAChB,MAAM,6BAA6B,CAAC;AACrC,OAAO,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAC;AACpE,OAAO,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AACpE,OAAO,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAC;AAChE,OAAO,EAAE,WAAW,EAAE,MAAM,iCAAiC,CAAC;AAC9D,OAAO,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAC;AAChE,OAAO,EAAE,WAAW,EAAE,MAAM,iCAAiC,CAAC;AAC9D,OAAO,EAAE,WAAW,EAAE,MAAM,iCAAiC,CAAC;AAC9D,OAAO,EAAE,UAAU,EAAE,MAAM,gCAAgC,CAAC;AAC5D,OAAO,EAAE,WAAW,EAAE,MAAM,iCAAiC,CAAC;AAC9D,OAAO,EAAE,aAAa,EAAE,MAAM,mCAAmC,CAAC;AAClE,OAAO,EAAE,WAAW,EAAE,MAAM,iCAAiC,CAAC;AAC9D,OAAO,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AACpE,OAAO,EAAE,WAAW,EAAE,MAAM,iCAAiC,CAAC;AAC9D,OAAO,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AACpE,OAAO,EAAE,UAAU,EAAE,MAAM,gCAAgC,CAAC;AAC5D,OAAO,EAAE,aAAa,EAAE,MAAM,mCAAmC,CAAC"}
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.SystemTheme = exports.MonokaiTheme = exports.ModernTheme = exports.MinimalTheme = exports.CorporateTheme = exports.TemplateBuilder = exports.AttachmentService = exports.EmailService = exports.TemplateService = exports.ThemeType = exports.TemplateFactory = exports.EmailFactory = void 0;
3
+ exports.MidnightTheme = exports.GlassTheme = exports.NeoBrutalTheme = exports.PastelTheme = exports.EditorialTheme = exports.ForestTheme = exports.LavenderTheme = exports.SunsetTheme = exports.OceanTheme = exports.AuroraTheme = exports.SystemTheme = exports.MonokaiTheme = exports.ModernTheme = exports.MinimalTheme = exports.CorporateTheme = exports.TemplateBuilder = exports.BullMQEmailQueue = exports.AttachmentService = exports.EmailService = exports.TemplateService = exports.ThemeType = exports.TemplateFactory = exports.EmailFactory = void 0;
4
4
  var email_factory_1 = require("./factories/email-factory");
5
5
  Object.defineProperty(exports, "EmailFactory", { enumerable: true, get: function () { return email_factory_1.EmailFactory; } });
6
6
  var template_factory_1 = require("./factories/template-factory");
@@ -13,6 +13,8 @@ var email_service_1 = require("./services/email.service");
13
13
  Object.defineProperty(exports, "EmailService", { enumerable: true, get: function () { return email_service_1.EmailService; } });
14
14
  var attachment_service_1 = require("./services/attachment.service");
15
15
  Object.defineProperty(exports, "AttachmentService", { enumerable: true, get: function () { return attachment_service_1.AttachmentService; } });
16
+ var bullmq_email_queue_1 = require("./queues/bullmq-email-queue");
17
+ Object.defineProperty(exports, "BullMQEmailQueue", { enumerable: true, get: function () { return bullmq_email_queue_1.BullMQEmailQueue; } });
16
18
  var template_builder_1 = require("./templates/base/template-builder");
17
19
  Object.defineProperty(exports, "TemplateBuilder", { enumerable: true, get: function () { return template_builder_1.TemplateBuilder; } });
18
20
  var corporate_theme_1 = require("./templates/themes/corporate.theme");
@@ -25,3 +27,23 @@ var monokai_theme_1 = require("./templates/themes/monokai.theme");
25
27
  Object.defineProperty(exports, "MonokaiTheme", { enumerable: true, get: function () { return monokai_theme_1.MonokaiTheme; } });
26
28
  var system_theme_1 = require("./templates/themes/system.theme");
27
29
  Object.defineProperty(exports, "SystemTheme", { enumerable: true, get: function () { return system_theme_1.SystemTheme; } });
30
+ var aurora_theme_1 = require("./templates/themes/aurora.theme");
31
+ Object.defineProperty(exports, "AuroraTheme", { enumerable: true, get: function () { return aurora_theme_1.AuroraTheme; } });
32
+ var ocean_theme_1 = require("./templates/themes/ocean.theme");
33
+ Object.defineProperty(exports, "OceanTheme", { enumerable: true, get: function () { return ocean_theme_1.OceanTheme; } });
34
+ var sunset_theme_1 = require("./templates/themes/sunset.theme");
35
+ Object.defineProperty(exports, "SunsetTheme", { enumerable: true, get: function () { return sunset_theme_1.SunsetTheme; } });
36
+ var lavender_theme_1 = require("./templates/themes/lavender.theme");
37
+ Object.defineProperty(exports, "LavenderTheme", { enumerable: true, get: function () { return lavender_theme_1.LavenderTheme; } });
38
+ var forest_theme_1 = require("./templates/themes/forest.theme");
39
+ Object.defineProperty(exports, "ForestTheme", { enumerable: true, get: function () { return forest_theme_1.ForestTheme; } });
40
+ var editorial_theme_1 = require("./templates/themes/editorial.theme");
41
+ Object.defineProperty(exports, "EditorialTheme", { enumerable: true, get: function () { return editorial_theme_1.EditorialTheme; } });
42
+ var pastel_theme_1 = require("./templates/themes/pastel.theme");
43
+ Object.defineProperty(exports, "PastelTheme", { enumerable: true, get: function () { return pastel_theme_1.PastelTheme; } });
44
+ var neobrutal_theme_1 = require("./templates/themes/neobrutal.theme");
45
+ Object.defineProperty(exports, "NeoBrutalTheme", { enumerable: true, get: function () { return neobrutal_theme_1.NeoBrutalTheme; } });
46
+ var glass_theme_1 = require("./templates/themes/glass.theme");
47
+ Object.defineProperty(exports, "GlassTheme", { enumerable: true, get: function () { return glass_theme_1.GlassTheme; } });
48
+ var midnight_theme_1 = require("./templates/themes/midnight.theme");
49
+ Object.defineProperty(exports, "MidnightTheme", { enumerable: true, get: function () { return midnight_theme_1.MidnightTheme; } });
@@ -0,0 +1,10 @@
1
+ import { IMailgunConfig } from '../core/interfaces/email.interface';
2
+ import { ITransportMessage, ITransportSendInfo, IEmailTransport } from './provider.interface';
3
+ export declare class MailgunTransport implements IEmailTransport {
4
+ private readonly config;
5
+ private readonly apiUrl;
6
+ private readonly fetchImpl;
7
+ constructor(config: IMailgunConfig, fetchImpl?: typeof fetch);
8
+ sendMail(message: ITransportMessage): Promise<ITransportSendInfo>;
9
+ }
10
+ //# sourceMappingURL=mailgun.transport.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mailgun.transport.d.ts","sourceRoot":"","sources":["../../src/providers/mailgun.transport.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AACpE,OAAO,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAG9F,qBAAa,gBAAiB,YAAW,eAAe;IAI1C,OAAO,CAAC,QAAQ,CAAC,MAAM;IAHnC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAe;gBAEZ,MAAM,EAAE,cAAc,EAAE,SAAS,GAAE,OAAO,KAAa;IAc9E,QAAQ,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,kBAAkB,CAAC;CAyDxE"}
@@ -0,0 +1,85 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MailgunTransport = void 0;
4
+ const provider_utils_1 = require("./provider-utils");
5
+ class MailgunTransport {
6
+ config;
7
+ apiUrl;
8
+ fetchImpl;
9
+ constructor(config, fetchImpl = fetch) {
10
+ this.config = config;
11
+ (0, provider_utils_1.validateApiConfig)(config);
12
+ if (!config.domain.trim() || /[\u0000-\u001f\u007f]/.test(config.domain)) {
13
+ throw new Error('Mailgun domain is required and must not contain control characters');
14
+ }
15
+ const defaultApiUrl = config.region === 'eu' ? 'https://api.eu.mailgun.net' : 'https://api.mailgun.net';
16
+ this.apiUrl = (config.apiUrl ?? defaultApiUrl).replace(/\/+$/, '');
17
+ const parsedUrl = new URL(this.apiUrl);
18
+ if (parsedUrl.protocol !== 'https:' && parsedUrl.hostname !== 'localhost' && parsedUrl.hostname !== '127.0.0.1') {
19
+ throw new Error('Mailgun apiUrl must use HTTPS');
20
+ }
21
+ this.fetchImpl = fetchImpl;
22
+ }
23
+ async sendMail(message) {
24
+ const recipients = (0, provider_utils_1.splitAddresses)(message.to);
25
+ if (recipients.length === 0)
26
+ throw new Error('Mailgun requires at least one recipient');
27
+ const from = message.from ?? this.config.defaultFrom;
28
+ const body = new FormData();
29
+ body.set('from', from);
30
+ for (const recipient of recipients)
31
+ body.append('to', recipient);
32
+ for (const recipient of (0, provider_utils_1.splitAddresses)(message.cc))
33
+ body.append('cc', recipient);
34
+ for (const recipient of (0, provider_utils_1.splitAddresses)(message.bcc))
35
+ body.append('bcc', recipient);
36
+ body.set('subject', message.subject);
37
+ if (message.text !== undefined)
38
+ body.set('text', message.text);
39
+ if (message.text === undefined && message.html === undefined) {
40
+ throw new Error('Mailgun requires a text or HTML body');
41
+ }
42
+ let html = message.html;
43
+ for (const attachment of message.attachments ?? []) {
44
+ const data = await (0, provider_utils_1.attachmentToBuffer)(attachment, this.config.maxAttachmentBytes);
45
+ const file = new Blob([new Uint8Array(data)], { type: attachment.contentType ?? 'application/octet-stream' });
46
+ body.append(attachment.cid ? 'inline' : 'attachment', file, attachment.filename);
47
+ if (attachment.cid && html !== undefined) {
48
+ html = html.split(`cid:${attachment.cid}`).join(`cid:${attachment.filename}`);
49
+ }
50
+ }
51
+ if (html !== undefined)
52
+ body.set('html', html);
53
+ const url = `${this.apiUrl}/v3/${encodeURIComponent(this.config.domain)}/messages`;
54
+ const response = await this.fetchImpl(url, {
55
+ method: 'POST',
56
+ headers: {
57
+ authorization: `Basic ${Buffer.from(`api:${this.config.apiKey}`).toString('base64')}`
58
+ },
59
+ body,
60
+ redirect: 'error',
61
+ signal: AbortSignal.timeout(this.config.requestTimeoutMs ?? 30_000)
62
+ });
63
+ const responseBody = await response.text();
64
+ if (response.status !== 200) {
65
+ const error = new Error(`Mailgun API returned HTTP ${response.status}${responseBody ? `: ${responseBody}` : ''}`);
66
+ Object.assign(error, { statusCode: response.status, responseBody });
67
+ throw error;
68
+ }
69
+ let result;
70
+ try {
71
+ result = responseBody ? JSON.parse(responseBody) : {};
72
+ }
73
+ catch {
74
+ throw new Error('Mailgun API returned an invalid JSON response');
75
+ }
76
+ const accepted = [...recipients, ...(0, provider_utils_1.splitAddresses)(message.cc), ...(0, provider_utils_1.splitAddresses)(message.bcc)];
77
+ return {
78
+ ...(result.id ? { messageId: result.id } : {}),
79
+ response: result.message ?? '200 Accepted',
80
+ accepted,
81
+ rejected: []
82
+ };
83
+ }
84
+ }
85
+ exports.MailgunTransport = MailgunTransport;
@@ -0,0 +1,11 @@
1
+ import { Transporter } from 'nodemailer';
2
+ import { IEmailTransport, ITransportMessage, ITransportSendInfo } from './provider.interface';
3
+ export declare class NodemailerTransport implements IEmailTransport {
4
+ private readonly transporter;
5
+ private readonly closeExtra?;
6
+ constructor(transporter: Transporter, closeExtra?: (() => void) | undefined);
7
+ sendMail(message: ITransportMessage): Promise<ITransportSendInfo>;
8
+ verify(): Promise<true>;
9
+ close(): void;
10
+ }
11
+ //# sourceMappingURL=nodemailer.transport.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nodemailer.transport.d.ts","sourceRoot":"","sources":["../../src/providers/nodemailer.transport.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,EAAE,eAAe,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAE9F,qBAAa,mBAAoB,YAAW,eAAe;IAC7C,OAAO,CAAC,QAAQ,CAAC,WAAW;IAAe,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC;gBAAtD,WAAW,EAAE,WAAW,EAAmB,UAAU,CAAC,GAAE,MAAM,IAAI,aAAA;IAEzF,QAAQ,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAKvE,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAIvB,KAAK,IAAI,IAAI;CAId"}
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.NodemailerTransport = void 0;
4
+ class NodemailerTransport {
5
+ transporter;
6
+ closeExtra;
7
+ constructor(transporter, closeExtra) {
8
+ this.transporter = transporter;
9
+ this.closeExtra = closeExtra;
10
+ }
11
+ async sendMail(message) {
12
+ const info = await this.transporter.sendMail(message);
13
+ return info;
14
+ }
15
+ verify() {
16
+ return this.transporter.verify();
17
+ }
18
+ close() {
19
+ this.transporter.close();
20
+ this.closeExtra?.();
21
+ }
22
+ }
23
+ exports.NodemailerTransport = NodemailerTransport;
@@ -0,0 +1,17 @@
1
+ import { IAttachment } from '../core/interfaces/email.interface';
2
+ export interface IProviderMailbox {
3
+ email: string;
4
+ name?: string;
5
+ }
6
+ /** Splits a header address list without splitting commas inside quotes or angle brackets. */
7
+ export declare function splitAddresses(value: string | string[] | undefined): string[];
8
+ export declare function parseMailbox(value: string): IProviderMailbox;
9
+ export declare function parseMailboxes(value: string | string[] | undefined): IProviderMailbox[];
10
+ export declare function attachmentToBuffer(attachment: IAttachment, maxAttachmentBytes?: number): Promise<Buffer>;
11
+ export declare function validateApiConfig(options: {
12
+ apiKey: string;
13
+ defaultFrom: string;
14
+ requestTimeoutMs?: number;
15
+ maxAttachmentBytes?: number;
16
+ }): void;
17
+ //# sourceMappingURL=provider-utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider-utils.d.ts","sourceRoot":"","sources":["../../src/providers/provider-utils.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,oCAAoC,CAAC;AAEjE,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,6FAA6F;AAC7F,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,GAAG,MAAM,EAAE,CAyB7E;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,gBAAgB,CAO5D;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,GAAG,gBAAgB,EAAE,CAEvF;AAED,wBAAsB,kBAAkB,CAAC,UAAU,EAAE,WAAW,EAAE,kBAAkB,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAyB9G;AAED,wBAAgB,iBAAiB,CAAC,OAAO,EAAE;IACzC,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B,GAAG,IAAI,CASP"}
@@ -0,0 +1,89 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.splitAddresses = splitAddresses;
4
+ exports.parseMailbox = parseMailbox;
5
+ exports.parseMailboxes = parseMailboxes;
6
+ exports.attachmentToBuffer = attachmentToBuffer;
7
+ exports.validateApiConfig = validateApiConfig;
8
+ const promises_1 = require("fs/promises");
9
+ /** Splits a header address list without splitting commas inside quotes or angle brackets. */
10
+ function splitAddresses(value) {
11
+ if (value === undefined)
12
+ return [];
13
+ const entries = Array.isArray(value) ? value : [value];
14
+ const result = [];
15
+ for (const entry of entries) {
16
+ let start = 0;
17
+ let quoted = false;
18
+ let angleDepth = 0;
19
+ for (let index = 0; index < entry.length; index += 1) {
20
+ const char = entry[index];
21
+ if (char === '"' && entry[index - 1] !== '\\')
22
+ quoted = !quoted;
23
+ if (!quoted && char === '<')
24
+ angleDepth += 1;
25
+ if (!quoted && char === '>' && angleDepth > 0)
26
+ angleDepth -= 1;
27
+ if (!quoted && angleDepth === 0 && char === ',') {
28
+ const address = entry.slice(start, index).trim();
29
+ if (address)
30
+ result.push(address);
31
+ start = index + 1;
32
+ }
33
+ }
34
+ const address = entry.slice(start).trim();
35
+ if (address)
36
+ result.push(address);
37
+ }
38
+ return result;
39
+ }
40
+ function parseMailbox(value) {
41
+ const normalized = value.trim();
42
+ const match = /^(.*?)\s*<([^<>]+)>$/.exec(normalized);
43
+ if (!match)
44
+ return { email: normalized };
45
+ const name = match[1].trim().replace(/^"|"$/g, '');
46
+ return name ? { email: match[2].trim(), name } : { email: match[2].trim() };
47
+ }
48
+ function parseMailboxes(value) {
49
+ return splitAddresses(value).map(parseMailbox);
50
+ }
51
+ async function attachmentToBuffer(attachment, maxAttachmentBytes) {
52
+ let content;
53
+ if (attachment.content !== undefined) {
54
+ content = Buffer.isBuffer(attachment.content)
55
+ ? attachment.content
56
+ : Buffer.from(attachment.content);
57
+ }
58
+ else if (attachment.path) {
59
+ if (maxAttachmentBytes !== undefined) {
60
+ const fileInfo = await (0, promises_1.stat)(attachment.path);
61
+ if (fileInfo.size > maxAttachmentBytes) {
62
+ throw new Error(`Attachment exceeds configured maxAttachmentBytes (${maxAttachmentBytes})`);
63
+ }
64
+ }
65
+ content = await (0, promises_1.readFile)(attachment.path);
66
+ }
67
+ else {
68
+ throw new Error(`Attachment "${attachment.filename}" requires content or path`);
69
+ }
70
+ if (maxAttachmentBytes !== undefined && content.length > maxAttachmentBytes) {
71
+ throw new Error(`Attachment exceeds configured maxAttachmentBytes (${maxAttachmentBytes})`);
72
+ }
73
+ if (!attachment.filename || /[\u0000-\u001f\u007f]/.test(attachment.filename)) {
74
+ throw new Error('Attachment filename must be non-empty and contain no control characters');
75
+ }
76
+ return content;
77
+ }
78
+ function validateApiConfig(options) {
79
+ if (!options.apiKey.trim())
80
+ throw new Error('Provider apiKey is required');
81
+ if (!options.defaultFrom.trim())
82
+ throw new Error('Provider defaultFrom is required');
83
+ if (options.requestTimeoutMs !== undefined && (!Number.isSafeInteger(options.requestTimeoutMs) || options.requestTimeoutMs <= 0)) {
84
+ throw new Error('requestTimeoutMs must be a positive safe integer');
85
+ }
86
+ if (options.maxAttachmentBytes !== undefined && (!Number.isSafeInteger(options.maxAttachmentBytes) || options.maxAttachmentBytes <= 0)) {
87
+ throw new Error('maxAttachmentBytes must be a positive safe integer');
88
+ }
89
+ }