@parsrun/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,415 @@
1
+ // src/templates/index.ts
2
+ function renderTemplate(template, data) {
3
+ return template.replace(/\{\{(\w+(?:\.\w+)*)\}\}/g, (_, path) => {
4
+ const keys = path.split(".");
5
+ let value = data;
6
+ for (const key of keys) {
7
+ if (value && typeof value === "object" && key in value) {
8
+ value = value[key];
9
+ } else {
10
+ return `{{${path}}}`;
11
+ }
12
+ }
13
+ return String(value ?? "");
14
+ });
15
+ }
16
+ function wrapEmailHtml(content, options) {
17
+ const { brandName = "Pars", brandColor = "#0070f3", footerText } = options ?? {};
18
+ return `<!DOCTYPE html>
19
+ <html lang="en">
20
+ <head>
21
+ <meta charset="UTF-8">
22
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
23
+ <title>${brandName}</title>
24
+ <style>
25
+ body {
26
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
27
+ line-height: 1.6;
28
+ color: #333;
29
+ margin: 0;
30
+ padding: 0;
31
+ background-color: #f5f5f5;
32
+ }
33
+ .container {
34
+ max-width: 600px;
35
+ margin: 0 auto;
36
+ padding: 40px 20px;
37
+ }
38
+ .card {
39
+ background: #ffffff;
40
+ border-radius: 8px;
41
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
42
+ padding: 40px;
43
+ }
44
+ .header {
45
+ text-align: center;
46
+ margin-bottom: 32px;
47
+ }
48
+ .brand {
49
+ font-size: 24px;
50
+ font-weight: 700;
51
+ color: ${brandColor};
52
+ }
53
+ .content {
54
+ margin-bottom: 32px;
55
+ }
56
+ .code-box {
57
+ background: #f8f9fa;
58
+ border: 2px dashed #dee2e6;
59
+ border-radius: 8px;
60
+ padding: 24px;
61
+ text-align: center;
62
+ margin: 24px 0;
63
+ }
64
+ .code {
65
+ font-size: 36px;
66
+ font-weight: 700;
67
+ letter-spacing: 8px;
68
+ color: ${brandColor};
69
+ font-family: 'SF Mono', Monaco, 'Courier New', monospace;
70
+ }
71
+ .button {
72
+ display: inline-block;
73
+ background: ${brandColor};
74
+ color: #ffffff !important;
75
+ text-decoration: none;
76
+ padding: 14px 32px;
77
+ border-radius: 6px;
78
+ font-weight: 600;
79
+ margin: 16px 0;
80
+ }
81
+ .button:hover {
82
+ opacity: 0.9;
83
+ }
84
+ .footer {
85
+ text-align: center;
86
+ color: #666;
87
+ font-size: 13px;
88
+ margin-top: 32px;
89
+ padding-top: 24px;
90
+ border-top: 1px solid #eee;
91
+ }
92
+ .footer a {
93
+ color: ${brandColor};
94
+ }
95
+ .text-muted {
96
+ color: #666;
97
+ font-size: 14px;
98
+ }
99
+ .text-center {
100
+ text-align: center;
101
+ }
102
+ h1 {
103
+ font-size: 24px;
104
+ margin: 0 0 16px 0;
105
+ color: #111;
106
+ }
107
+ p {
108
+ margin: 0 0 16px 0;
109
+ }
110
+ </style>
111
+ </head>
112
+ <body>
113
+ <div class="container">
114
+ <div class="card">
115
+ <div class="header">
116
+ <div class="brand">${brandName}</div>
117
+ </div>
118
+ <div class="content">
119
+ ${content}
120
+ </div>
121
+ <div class="footer">
122
+ ${footerText ?? `&copy; ${(/* @__PURE__ */ new Date()).getFullYear()} ${brandName}. All rights reserved.`}
123
+ </div>
124
+ </div>
125
+ </div>
126
+ </body>
127
+ </html>`;
128
+ }
129
+ var otpTemplate = {
130
+ name: "otp",
131
+ subject: "Your verification code: {{code}}",
132
+ html: `
133
+ <h1>Your verification code</h1>
134
+ <p>Use the following code to verify your identity:</p>
135
+ <div class="code-box">
136
+ <span class="code">{{code}}</span>
137
+ </div>
138
+ <p class="text-muted">This code expires in {{expiresInMinutes}} minutes.</p>
139
+ <p class="text-muted">If you didn't request this code, you can safely ignore this email.</p>
140
+ `,
141
+ text: `Your verification code: {{code}}
142
+
143
+ This code expires in {{expiresInMinutes}} minutes.
144
+
145
+ If you didn't request this code, you can safely ignore this email.`
146
+ };
147
+ function renderOTPEmail(data) {
148
+ const templateData = {
149
+ ...data,
150
+ expiresInMinutes: data.expiresInMinutes ?? 10
151
+ };
152
+ return {
153
+ subject: renderTemplate(otpTemplate.subject, templateData),
154
+ html: wrapEmailHtml(renderTemplate(otpTemplate.html, templateData), {
155
+ brandName: data.brandName,
156
+ brandColor: data.brandColor
157
+ }),
158
+ text: renderTemplate(otpTemplate.text ?? "", templateData)
159
+ };
160
+ }
161
+ var magicLinkTemplate = {
162
+ name: "magic-link",
163
+ subject: "Sign in to {{brandName}}",
164
+ html: `
165
+ <h1>Sign in to your account</h1>
166
+ <p>Click the button below to securely sign in to your account:</p>
167
+ <div class="text-center">
168
+ <a href="{{url}}" class="button">Sign In</a>
169
+ </div>
170
+ <p class="text-muted">This link expires in {{expiresInMinutes}} minutes.</p>
171
+ <p class="text-muted">If you didn't request this link, you can safely ignore this email.</p>
172
+ <p class="text-muted" style="margin-top: 24px; font-size: 12px;">
173
+ If the button doesn't work, copy and paste this URL into your browser:<br>
174
+ <a href="{{url}}">{{url}}</a>
175
+ </p>
176
+ `,
177
+ text: `Sign in to {{brandName}}
178
+
179
+ Click this link to sign in:
180
+ {{url}}
181
+
182
+ This link expires in {{expiresInMinutes}} minutes.
183
+
184
+ If you didn't request this link, you can safely ignore this email.`
185
+ };
186
+ function renderMagicLinkEmail(data) {
187
+ const templateData = {
188
+ ...data,
189
+ brandName: data.brandName ?? "Pars",
190
+ expiresInMinutes: data.expiresInMinutes ?? 15
191
+ };
192
+ return {
193
+ subject: renderTemplate(magicLinkTemplate.subject, templateData),
194
+ html: wrapEmailHtml(renderTemplate(magicLinkTemplate.html, templateData), {
195
+ brandName: templateData.brandName,
196
+ brandColor: data.brandColor
197
+ }),
198
+ text: renderTemplate(magicLinkTemplate.text ?? "", templateData)
199
+ };
200
+ }
201
+ var verificationTemplate = {
202
+ name: "verification",
203
+ subject: "Verify your email address",
204
+ html: `
205
+ <h1>Verify your email</h1>
206
+ <p>Hi{{#name}} {{name}}{{/name}},</p>
207
+ <p>Please verify your email address by clicking the button below:</p>
208
+ <div class="text-center">
209
+ <a href="{{url}}" class="button">Verify Email</a>
210
+ </div>
211
+ <p class="text-muted">This link expires in {{expiresInHours}} hours.</p>
212
+ <p class="text-muted" style="margin-top: 24px; font-size: 12px;">
213
+ If the button doesn't work, copy and paste this URL into your browser:<br>
214
+ <a href="{{url}}">{{url}}</a>
215
+ </p>
216
+ `,
217
+ text: `Verify your email address
218
+
219
+ Hi{{#name}} {{name}}{{/name}},
220
+
221
+ Please verify your email address by clicking this link:
222
+ {{url}}
223
+
224
+ This link expires in {{expiresInHours}} hours.`
225
+ };
226
+ function renderVerificationEmail(data) {
227
+ const templateData = {
228
+ ...data,
229
+ expiresInHours: data.expiresInHours ?? 24
230
+ };
231
+ let html = verificationTemplate.html.replace(/\{\{#name\}\}(.*?)\{\{\/name\}\}/gs, data.name ? "$1" : "");
232
+ let text = (verificationTemplate.text ?? "").replace(/\{\{#name\}\}(.*?)\{\{\/name\}\}/gs, data.name ? "$1" : "");
233
+ html = renderTemplate(html, templateData);
234
+ text = renderTemplate(text, templateData);
235
+ return {
236
+ subject: renderTemplate(verificationTemplate.subject, templateData),
237
+ html: wrapEmailHtml(html, {
238
+ brandName: data.brandName,
239
+ brandColor: data.brandColor
240
+ }),
241
+ text
242
+ };
243
+ }
244
+ var welcomeTemplate = {
245
+ name: "welcome",
246
+ subject: "Welcome to {{brandName}}!",
247
+ html: `
248
+ <h1>Welcome to {{brandName}}!</h1>
249
+ <p>Hi{{#name}} {{name}}{{/name}},</p>
250
+ <p>Thank you for joining us. We're excited to have you on board!</p>
251
+ <p>Your account is now ready to use.</p>
252
+ {{#loginUrl}}
253
+ <div class="text-center">
254
+ <a href="{{loginUrl}}" class="button">Go to Dashboard</a>
255
+ </div>
256
+ {{/loginUrl}}
257
+ <p>If you have any questions, feel free to reach out to our support team.</p>
258
+ <p>Best regards,<br>The {{brandName}} Team</p>
259
+ `,
260
+ text: `Welcome to {{brandName}}!
261
+
262
+ Hi{{#name}} {{name}}{{/name}},
263
+
264
+ Thank you for joining us. We're excited to have you on board!
265
+
266
+ Your account is now ready to use.
267
+
268
+ {{#loginUrl}}Go to your dashboard: {{loginUrl}}{{/loginUrl}}
269
+
270
+ If you have any questions, feel free to reach out to our support team.
271
+
272
+ Best regards,
273
+ The {{brandName}} Team`
274
+ };
275
+ function renderWelcomeEmail(data) {
276
+ const brandName = data.brandName ?? "Pars";
277
+ const templateData = { ...data, brandName };
278
+ let html = welcomeTemplate.html.replace(/\{\{#name\}\}(.*?)\{\{\/name\}\}/gs, data.name ? "$1" : "").replace(/\{\{#loginUrl\}\}([\s\S]*?)\{\{\/loginUrl\}\}/g, data.loginUrl ? "$1" : "");
279
+ let text = (welcomeTemplate.text ?? "").replace(/\{\{#name\}\}(.*?)\{\{\/name\}\}/gs, data.name ? "$1" : "").replace(/\{\{#loginUrl\}\}([\s\S]*?)\{\{\/loginUrl\}\}/g, data.loginUrl ? "$1" : "");
280
+ html = renderTemplate(html, templateData);
281
+ text = renderTemplate(text, templateData);
282
+ return {
283
+ subject: renderTemplate(welcomeTemplate.subject, templateData),
284
+ html: wrapEmailHtml(html, {
285
+ brandName,
286
+ brandColor: data.brandColor
287
+ }),
288
+ text
289
+ };
290
+ }
291
+ var passwordResetTemplate = {
292
+ name: "password-reset",
293
+ subject: "Reset your password",
294
+ html: `
295
+ <h1>Reset your password</h1>
296
+ <p>We received a request to reset your password. Click the button below to choose a new password:</p>
297
+ <div class="text-center">
298
+ <a href="{{url}}" class="button">Reset Password</a>
299
+ </div>
300
+ <p class="text-muted">This link expires in {{expiresInMinutes}} minutes.</p>
301
+ <p class="text-muted">If you didn't request a password reset, you can safely ignore this email. Your password will remain unchanged.</p>
302
+ <p class="text-muted" style="margin-top: 24px; font-size: 12px;">
303
+ If the button doesn't work, copy and paste this URL into your browser:<br>
304
+ <a href="{{url}}">{{url}}</a>
305
+ </p>
306
+ `,
307
+ text: `Reset your password
308
+
309
+ We received a request to reset your password. Click this link to choose a new password:
310
+ {{url}}
311
+
312
+ This link expires in {{expiresInMinutes}} minutes.
313
+
314
+ If you didn't request a password reset, you can safely ignore this email. Your password will remain unchanged.`
315
+ };
316
+ function renderPasswordResetEmail(data) {
317
+ const templateData = {
318
+ ...data,
319
+ expiresInMinutes: data.expiresInMinutes ?? 60
320
+ };
321
+ return {
322
+ subject: renderTemplate(passwordResetTemplate.subject, templateData),
323
+ html: wrapEmailHtml(renderTemplate(passwordResetTemplate.html, templateData), {
324
+ brandName: data.brandName,
325
+ brandColor: data.brandColor
326
+ }),
327
+ text: renderTemplate(passwordResetTemplate.text ?? "", templateData)
328
+ };
329
+ }
330
+ var invitationTemplate = {
331
+ name: "invitation",
332
+ subject: "{{#inviterName}}{{inviterName}} invited you to join {{/inviterName}}{{organizationName}}",
333
+ html: `
334
+ <h1>You're invited!</h1>
335
+ {{#inviterName}}
336
+ <p><strong>{{inviterName}}</strong> has invited you to join <strong>{{organizationName}}</strong>{{#role}} as a {{role}}{{/role}}.</p>
337
+ {{/inviterName}}
338
+ {{^inviterName}}
339
+ <p>You've been invited to join <strong>{{organizationName}}</strong>{{#role}} as a {{role}}{{/role}}.</p>
340
+ {{/inviterName}}
341
+ <div class="text-center">
342
+ <a href="{{url}}" class="button">Accept Invitation</a>
343
+ </div>
344
+ <p class="text-muted">This invitation expires in {{expiresInDays}} days.</p>
345
+ <p class="text-muted" style="margin-top: 24px; font-size: 12px;">
346
+ If the button doesn't work, copy and paste this URL into your browser:<br>
347
+ <a href="{{url}}">{{url}}</a>
348
+ </p>
349
+ `,
350
+ text: `You're invited to join {{organizationName}}!
351
+
352
+ {{#inviterName}}{{inviterName}} has invited you to join {{organizationName}}{{#role}} as a {{role}}{{/role}}.{{/inviterName}}
353
+ {{^inviterName}}You've been invited to join {{organizationName}}{{#role}} as a {{role}}{{/role}}.{{/inviterName}}
354
+
355
+ Accept the invitation:
356
+ {{url}}
357
+
358
+ This invitation expires in {{expiresInDays}} days.`
359
+ };
360
+ function renderInvitationEmail(data) {
361
+ const templateData = {
362
+ ...data,
363
+ organizationName: data.organizationName ?? "the team",
364
+ expiresInDays: data.expiresInDays ?? 7
365
+ };
366
+ let html = invitationTemplate.html.replace(/\{\{#inviterName\}\}([\s\S]*?)\{\{\/inviterName\}\}/g, data.inviterName ? "$1" : "").replace(/\{\{\^inviterName\}\}([\s\S]*?)\{\{\/inviterName\}\}/g, data.inviterName ? "" : "$1").replace(/\{\{#role\}\}([\s\S]*?)\{\{\/role\}\}/g, data.role ? "$1" : "");
367
+ let text = (invitationTemplate.text ?? "").replace(/\{\{#inviterName\}\}([\s\S]*?)\{\{\/inviterName\}\}/g, data.inviterName ? "$1" : "").replace(/\{\{\^inviterName\}\}([\s\S]*?)\{\{\/inviterName\}\}/g, data.inviterName ? "" : "$1").replace(/\{\{#role\}\}([\s\S]*?)\{\{\/role\}\}/g, data.role ? "$1" : "");
368
+ let subject = invitationTemplate.subject.replace(/\{\{#inviterName\}\}([\s\S]*?)\{\{\/inviterName\}\}/g, data.inviterName ? "$1" : "You're invited to join ");
369
+ html = renderTemplate(html, templateData);
370
+ text = renderTemplate(text, templateData);
371
+ subject = renderTemplate(subject, templateData);
372
+ return {
373
+ subject,
374
+ html: wrapEmailHtml(html, {
375
+ brandName: data.brandName,
376
+ brandColor: data.brandColor
377
+ }),
378
+ text
379
+ };
380
+ }
381
+ var templates = {
382
+ otp: otpTemplate,
383
+ magicLink: magicLinkTemplate,
384
+ verification: verificationTemplate,
385
+ welcome: welcomeTemplate,
386
+ passwordReset: passwordResetTemplate,
387
+ invitation: invitationTemplate
388
+ };
389
+ var renderFunctions = {
390
+ otp: renderOTPEmail,
391
+ magicLink: renderMagicLinkEmail,
392
+ verification: renderVerificationEmail,
393
+ welcome: renderWelcomeEmail,
394
+ passwordReset: renderPasswordResetEmail,
395
+ invitation: renderInvitationEmail
396
+ };
397
+ export {
398
+ invitationTemplate,
399
+ magicLinkTemplate,
400
+ otpTemplate,
401
+ passwordResetTemplate,
402
+ renderFunctions,
403
+ renderInvitationEmail,
404
+ renderMagicLinkEmail,
405
+ renderOTPEmail,
406
+ renderPasswordResetEmail,
407
+ renderTemplate,
408
+ renderVerificationEmail,
409
+ renderWelcomeEmail,
410
+ templates,
411
+ verificationTemplate,
412
+ welcomeTemplate,
413
+ wrapEmailHtml
414
+ };
415
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/templates/index.ts"],"sourcesContent":["/**\n * @parsrun/email - Email Templates\n * Pre-built templates for common email use cases\n */\n\nimport type { EmailTemplate, TemplateData } from \"../types.js\";\n\n/**\n * Simple template engine - replaces {{key}} with values\n */\nexport function renderTemplate(template: string, data: TemplateData): string {\n return template.replace(/\\{\\{(\\w+(?:\\.\\w+)*)\\}\\}/g, (_, path: string) => {\n const keys = path.split(\".\");\n let value: unknown = data;\n\n for (const key of keys) {\n if (value && typeof value === \"object\" && key in value) {\n value = (value as Record<string, unknown>)[key];\n } else {\n return `{{${path}}}`; // Keep original if not found\n }\n }\n\n return String(value ?? \"\");\n });\n}\n\n/**\n * Base email wrapper with consistent styling\n */\nexport function wrapEmailHtml(content: string, options?: {\n brandName?: string | undefined;\n brandColor?: string | undefined;\n footerText?: string | undefined;\n}): string {\n const { brandName = \"Pars\", brandColor = \"#0070f3\", footerText } = options ?? {};\n\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>${brandName}</title>\n <style>\n body {\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;\n line-height: 1.6;\n color: #333;\n margin: 0;\n padding: 0;\n background-color: #f5f5f5;\n }\n .container {\n max-width: 600px;\n margin: 0 auto;\n padding: 40px 20px;\n }\n .card {\n background: #ffffff;\n border-radius: 8px;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);\n padding: 40px;\n }\n .header {\n text-align: center;\n margin-bottom: 32px;\n }\n .brand {\n font-size: 24px;\n font-weight: 700;\n color: ${brandColor};\n }\n .content {\n margin-bottom: 32px;\n }\n .code-box {\n background: #f8f9fa;\n border: 2px dashed #dee2e6;\n border-radius: 8px;\n padding: 24px;\n text-align: center;\n margin: 24px 0;\n }\n .code {\n font-size: 36px;\n font-weight: 700;\n letter-spacing: 8px;\n color: ${brandColor};\n font-family: 'SF Mono', Monaco, 'Courier New', monospace;\n }\n .button {\n display: inline-block;\n background: ${brandColor};\n color: #ffffff !important;\n text-decoration: none;\n padding: 14px 32px;\n border-radius: 6px;\n font-weight: 600;\n margin: 16px 0;\n }\n .button:hover {\n opacity: 0.9;\n }\n .footer {\n text-align: center;\n color: #666;\n font-size: 13px;\n margin-top: 32px;\n padding-top: 24px;\n border-top: 1px solid #eee;\n }\n .footer a {\n color: ${brandColor};\n }\n .text-muted {\n color: #666;\n font-size: 14px;\n }\n .text-center {\n text-align: center;\n }\n h1 {\n font-size: 24px;\n margin: 0 0 16px 0;\n color: #111;\n }\n p {\n margin: 0 0 16px 0;\n }\n </style>\n</head>\n<body>\n <div class=\"container\">\n <div class=\"card\">\n <div class=\"header\">\n <div class=\"brand\">${brandName}</div>\n </div>\n <div class=\"content\">\n ${content}\n </div>\n <div class=\"footer\">\n ${footerText ?? `&copy; ${new Date().getFullYear()} ${brandName}. All rights reserved.`}\n </div>\n </div>\n </div>\n</body>\n</html>`;\n}\n\n// ============================================================================\n// OTP Templates\n// ============================================================================\n\nexport interface OTPTemplateData extends TemplateData {\n code: string;\n expiresInMinutes?: number;\n brandName?: string;\n brandColor?: string;\n}\n\nexport const otpTemplate: EmailTemplate = {\n name: \"otp\",\n subject: \"Your verification code: {{code}}\",\n html: `\n<h1>Your verification code</h1>\n<p>Use the following code to verify your identity:</p>\n<div class=\"code-box\">\n <span class=\"code\">{{code}}</span>\n</div>\n<p class=\"text-muted\">This code expires in {{expiresInMinutes}} minutes.</p>\n<p class=\"text-muted\">If you didn't request this code, you can safely ignore this email.</p>\n`,\n text: `Your verification code: {{code}}\n\nThis code expires in {{expiresInMinutes}} minutes.\n\nIf you didn't request this code, you can safely ignore this email.`,\n};\n\nexport function renderOTPEmail(data: OTPTemplateData): { subject: string; html: string; text: string } {\n const templateData = {\n ...data,\n expiresInMinutes: data.expiresInMinutes ?? 10,\n };\n\n return {\n subject: renderTemplate(otpTemplate.subject, templateData),\n html: wrapEmailHtml(renderTemplate(otpTemplate.html, templateData), {\n brandName: data.brandName,\n brandColor: data.brandColor,\n }),\n text: renderTemplate(otpTemplate.text ?? \"\", templateData),\n };\n}\n\n// ============================================================================\n// Magic Link Templates\n// ============================================================================\n\nexport interface MagicLinkTemplateData extends TemplateData {\n url: string;\n expiresInMinutes?: number;\n brandName?: string;\n brandColor?: string;\n}\n\nexport const magicLinkTemplate: EmailTemplate = {\n name: \"magic-link\",\n subject: \"Sign in to {{brandName}}\",\n html: `\n<h1>Sign in to your account</h1>\n<p>Click the button below to securely sign in to your account:</p>\n<div class=\"text-center\">\n <a href=\"{{url}}\" class=\"button\">Sign In</a>\n</div>\n<p class=\"text-muted\">This link expires in {{expiresInMinutes}} minutes.</p>\n<p class=\"text-muted\">If you didn't request this link, you can safely ignore this email.</p>\n<p class=\"text-muted\" style=\"margin-top: 24px; font-size: 12px;\">\n If the button doesn't work, copy and paste this URL into your browser:<br>\n <a href=\"{{url}}\">{{url}}</a>\n</p>\n`,\n text: `Sign in to {{brandName}}\n\nClick this link to sign in:\n{{url}}\n\nThis link expires in {{expiresInMinutes}} minutes.\n\nIf you didn't request this link, you can safely ignore this email.`,\n};\n\nexport function renderMagicLinkEmail(data: MagicLinkTemplateData): { subject: string; html: string; text: string } {\n const templateData = {\n ...data,\n brandName: data.brandName ?? \"Pars\",\n expiresInMinutes: data.expiresInMinutes ?? 15,\n };\n\n return {\n subject: renderTemplate(magicLinkTemplate.subject, templateData),\n html: wrapEmailHtml(renderTemplate(magicLinkTemplate.html, templateData), {\n brandName: templateData.brandName,\n brandColor: data.brandColor,\n }),\n text: renderTemplate(magicLinkTemplate.text ?? \"\", templateData),\n };\n}\n\n// ============================================================================\n// Email Verification Templates\n// ============================================================================\n\nexport interface VerificationTemplateData extends TemplateData {\n url: string;\n name?: string;\n expiresInHours?: number;\n brandName?: string;\n brandColor?: string;\n}\n\nexport const verificationTemplate: EmailTemplate = {\n name: \"verification\",\n subject: \"Verify your email address\",\n html: `\n<h1>Verify your email</h1>\n<p>Hi{{#name}} {{name}}{{/name}},</p>\n<p>Please verify your email address by clicking the button below:</p>\n<div class=\"text-center\">\n <a href=\"{{url}}\" class=\"button\">Verify Email</a>\n</div>\n<p class=\"text-muted\">This link expires in {{expiresInHours}} hours.</p>\n<p class=\"text-muted\" style=\"margin-top: 24px; font-size: 12px;\">\n If the button doesn't work, copy and paste this URL into your browser:<br>\n <a href=\"{{url}}\">{{url}}</a>\n</p>\n`,\n text: `Verify your email address\n\nHi{{#name}} {{name}}{{/name}},\n\nPlease verify your email address by clicking this link:\n{{url}}\n\nThis link expires in {{expiresInHours}} hours.`,\n};\n\nexport function renderVerificationEmail(data: VerificationTemplateData): { subject: string; html: string; text: string } {\n const templateData = {\n ...data,\n expiresInHours: data.expiresInHours ?? 24,\n };\n\n // Handle conditional name\n let html = verificationTemplate.html\n .replace(/\\{\\{#name\\}\\}(.*?)\\{\\{\\/name\\}\\}/gs, data.name ? \"$1\" : \"\");\n let text = (verificationTemplate.text ?? \"\")\n .replace(/\\{\\{#name\\}\\}(.*?)\\{\\{\\/name\\}\\}/gs, data.name ? \"$1\" : \"\");\n\n html = renderTemplate(html, templateData);\n text = renderTemplate(text, templateData);\n\n return {\n subject: renderTemplate(verificationTemplate.subject, templateData),\n html: wrapEmailHtml(html, {\n brandName: data.brandName,\n brandColor: data.brandColor,\n }),\n text,\n };\n}\n\n// ============================================================================\n// Welcome Templates\n// ============================================================================\n\nexport interface WelcomeTemplateData extends TemplateData {\n name?: string;\n loginUrl?: string;\n brandName?: string;\n brandColor?: string;\n}\n\nexport const welcomeTemplate: EmailTemplate = {\n name: \"welcome\",\n subject: \"Welcome to {{brandName}}!\",\n html: `\n<h1>Welcome to {{brandName}}!</h1>\n<p>Hi{{#name}} {{name}}{{/name}},</p>\n<p>Thank you for joining us. We're excited to have you on board!</p>\n<p>Your account is now ready to use.</p>\n{{#loginUrl}}\n<div class=\"text-center\">\n <a href=\"{{loginUrl}}\" class=\"button\">Go to Dashboard</a>\n</div>\n{{/loginUrl}}\n<p>If you have any questions, feel free to reach out to our support team.</p>\n<p>Best regards,<br>The {{brandName}} Team</p>\n`,\n text: `Welcome to {{brandName}}!\n\nHi{{#name}} {{name}}{{/name}},\n\nThank you for joining us. We're excited to have you on board!\n\nYour account is now ready to use.\n\n{{#loginUrl}}Go to your dashboard: {{loginUrl}}{{/loginUrl}}\n\nIf you have any questions, feel free to reach out to our support team.\n\nBest regards,\nThe {{brandName}} Team`,\n};\n\nexport function renderWelcomeEmail(data: WelcomeTemplateData): { subject: string; html: string; text: string } {\n const brandName = data.brandName ?? \"Pars\";\n const templateData = { ...data, brandName };\n\n // Handle conditionals\n let html = welcomeTemplate.html\n .replace(/\\{\\{#name\\}\\}(.*?)\\{\\{\\/name\\}\\}/gs, data.name ? \"$1\" : \"\")\n .replace(/\\{\\{#loginUrl\\}\\}([\\s\\S]*?)\\{\\{\\/loginUrl\\}\\}/g, data.loginUrl ? \"$1\" : \"\");\n\n let text = (welcomeTemplate.text ?? \"\")\n .replace(/\\{\\{#name\\}\\}(.*?)\\{\\{\\/name\\}\\}/gs, data.name ? \"$1\" : \"\")\n .replace(/\\{\\{#loginUrl\\}\\}([\\s\\S]*?)\\{\\{\\/loginUrl\\}\\}/g, data.loginUrl ? \"$1\" : \"\");\n\n html = renderTemplate(html, templateData);\n text = renderTemplate(text, templateData);\n\n return {\n subject: renderTemplate(welcomeTemplate.subject, templateData),\n html: wrapEmailHtml(html, {\n brandName,\n brandColor: data.brandColor,\n }),\n text,\n };\n}\n\n// ============================================================================\n// Password Reset Templates\n// ============================================================================\n\nexport interface PasswordResetTemplateData extends TemplateData {\n url: string;\n expiresInMinutes?: number;\n brandName?: string;\n brandColor?: string;\n}\n\nexport const passwordResetTemplate: EmailTemplate = {\n name: \"password-reset\",\n subject: \"Reset your password\",\n html: `\n<h1>Reset your password</h1>\n<p>We received a request to reset your password. Click the button below to choose a new password:</p>\n<div class=\"text-center\">\n <a href=\"{{url}}\" class=\"button\">Reset Password</a>\n</div>\n<p class=\"text-muted\">This link expires in {{expiresInMinutes}} minutes.</p>\n<p class=\"text-muted\">If you didn't request a password reset, you can safely ignore this email. Your password will remain unchanged.</p>\n<p class=\"text-muted\" style=\"margin-top: 24px; font-size: 12px;\">\n If the button doesn't work, copy and paste this URL into your browser:<br>\n <a href=\"{{url}}\">{{url}}</a>\n</p>\n`,\n text: `Reset your password\n\nWe received a request to reset your password. Click this link to choose a new password:\n{{url}}\n\nThis link expires in {{expiresInMinutes}} minutes.\n\nIf you didn't request a password reset, you can safely ignore this email. Your password will remain unchanged.`,\n};\n\nexport function renderPasswordResetEmail(data: PasswordResetTemplateData): { subject: string; html: string; text: string } {\n const templateData = {\n ...data,\n expiresInMinutes: data.expiresInMinutes ?? 60,\n };\n\n return {\n subject: renderTemplate(passwordResetTemplate.subject, templateData),\n html: wrapEmailHtml(renderTemplate(passwordResetTemplate.html, templateData), {\n brandName: data.brandName,\n brandColor: data.brandColor,\n }),\n text: renderTemplate(passwordResetTemplate.text ?? \"\", templateData),\n };\n}\n\n// ============================================================================\n// Invitation Templates\n// ============================================================================\n\nexport interface InvitationTemplateData extends TemplateData {\n url: string;\n inviterName?: string;\n organizationName?: string;\n role?: string;\n expiresInDays?: number;\n brandName?: string;\n brandColor?: string;\n}\n\nexport const invitationTemplate: EmailTemplate = {\n name: \"invitation\",\n subject: \"{{#inviterName}}{{inviterName}} invited you to join {{/inviterName}}{{organizationName}}\",\n html: `\n<h1>You're invited!</h1>\n{{#inviterName}}\n<p><strong>{{inviterName}}</strong> has invited you to join <strong>{{organizationName}}</strong>{{#role}} as a {{role}}{{/role}}.</p>\n{{/inviterName}}\n{{^inviterName}}\n<p>You've been invited to join <strong>{{organizationName}}</strong>{{#role}} as a {{role}}{{/role}}.</p>\n{{/inviterName}}\n<div class=\"text-center\">\n <a href=\"{{url}}\" class=\"button\">Accept Invitation</a>\n</div>\n<p class=\"text-muted\">This invitation expires in {{expiresInDays}} days.</p>\n<p class=\"text-muted\" style=\"margin-top: 24px; font-size: 12px;\">\n If the button doesn't work, copy and paste this URL into your browser:<br>\n <a href=\"{{url}}\">{{url}}</a>\n</p>\n`,\n text: `You're invited to join {{organizationName}}!\n\n{{#inviterName}}{{inviterName}} has invited you to join {{organizationName}}{{#role}} as a {{role}}{{/role}}.{{/inviterName}}\n{{^inviterName}}You've been invited to join {{organizationName}}{{#role}} as a {{role}}{{/role}}.{{/inviterName}}\n\nAccept the invitation:\n{{url}}\n\nThis invitation expires in {{expiresInDays}} days.`,\n};\n\nexport function renderInvitationEmail(data: InvitationTemplateData): { subject: string; html: string; text: string } {\n const templateData = {\n ...data,\n organizationName: data.organizationName ?? \"the team\",\n expiresInDays: data.expiresInDays ?? 7,\n };\n\n // Handle conditionals (mustache-like syntax)\n let html = invitationTemplate.html\n .replace(/\\{\\{#inviterName\\}\\}([\\s\\S]*?)\\{\\{\\/inviterName\\}\\}/g, data.inviterName ? \"$1\" : \"\")\n .replace(/\\{\\{\\^inviterName\\}\\}([\\s\\S]*?)\\{\\{\\/inviterName\\}\\}/g, data.inviterName ? \"\" : \"$1\")\n .replace(/\\{\\{#role\\}\\}([\\s\\S]*?)\\{\\{\\/role\\}\\}/g, data.role ? \"$1\" : \"\");\n\n let text = (invitationTemplate.text ?? \"\")\n .replace(/\\{\\{#inviterName\\}\\}([\\s\\S]*?)\\{\\{\\/inviterName\\}\\}/g, data.inviterName ? \"$1\" : \"\")\n .replace(/\\{\\{\\^inviterName\\}\\}([\\s\\S]*?)\\{\\{\\/inviterName\\}\\}/g, data.inviterName ? \"\" : \"$1\")\n .replace(/\\{\\{#role\\}\\}([\\s\\S]*?)\\{\\{\\/role\\}\\}/g, data.role ? \"$1\" : \"\");\n\n let subject = invitationTemplate.subject\n .replace(/\\{\\{#inviterName\\}\\}([\\s\\S]*?)\\{\\{\\/inviterName\\}\\}/g, data.inviterName ? \"$1\" : \"You're invited to join \");\n\n html = renderTemplate(html, templateData);\n text = renderTemplate(text, templateData);\n subject = renderTemplate(subject, templateData);\n\n return {\n subject,\n html: wrapEmailHtml(html, {\n brandName: data.brandName,\n brandColor: data.brandColor,\n }),\n text,\n };\n}\n\n// ============================================================================\n// Export all templates\n// ============================================================================\n\nexport const templates = {\n otp: otpTemplate,\n magicLink: magicLinkTemplate,\n verification: verificationTemplate,\n welcome: welcomeTemplate,\n passwordReset: passwordResetTemplate,\n invitation: invitationTemplate,\n} as const;\n\nexport const renderFunctions = {\n otp: renderOTPEmail,\n magicLink: renderMagicLinkEmail,\n verification: renderVerificationEmail,\n welcome: renderWelcomeEmail,\n passwordReset: renderPasswordResetEmail,\n invitation: renderInvitationEmail,\n} as const;\n"],"mappings":";AAUO,SAAS,eAAe,UAAkB,MAA4B;AAC3E,SAAO,SAAS,QAAQ,4BAA4B,CAAC,GAAG,SAAiB;AACvE,UAAM,OAAO,KAAK,MAAM,GAAG;AAC3B,QAAI,QAAiB;AAErB,eAAW,OAAO,MAAM;AACtB,UAAI,SAAS,OAAO,UAAU,YAAY,OAAO,OAAO;AACtD,gBAAS,MAAkC,GAAG;AAAA,MAChD,OAAO;AACL,eAAO,KAAK,IAAI;AAAA,MAClB;AAAA,IACF;AAEA,WAAO,OAAO,SAAS,EAAE;AAAA,EAC3B,CAAC;AACH;AAKO,SAAS,cAAc,SAAiB,SAIpC;AACT,QAAM,EAAE,YAAY,QAAQ,aAAa,WAAW,WAAW,IAAI,WAAW,CAAC;AAE/E,SAAO;AAAA;AAAA;AAAA;AAAA;AAAA,WAKE,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,eA4BL,UAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,eAiBV,UAAU;AAAA;AAAA;AAAA;AAAA;AAAA,oBAKL,UAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,eAoBf,UAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,6BAuBI,SAAS;AAAA;AAAA;AAAA,UAG5B,OAAO;AAAA;AAAA;AAAA,UAGP,cAAc,WAAU,oBAAI,KAAK,GAAE,YAAY,CAAC,IAAI,SAAS,wBAAwB;AAAA;AAAA;AAAA;AAAA;AAAA;AAM/F;AAaO,IAAM,cAA6B;AAAA,EACxC,MAAM;AAAA,EACN,SAAS;AAAA,EACT,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASN,MAAM;AAAA;AAAA;AAAA;AAAA;AAKR;AAEO,SAAS,eAAe,MAAwE;AACrG,QAAM,eAAe;AAAA,IACnB,GAAG;AAAA,IACH,kBAAkB,KAAK,oBAAoB;AAAA,EAC7C;AAEA,SAAO;AAAA,IACL,SAAS,eAAe,YAAY,SAAS,YAAY;AAAA,IACzD,MAAM,cAAc,eAAe,YAAY,MAAM,YAAY,GAAG;AAAA,MAClE,WAAW,KAAK;AAAA,MAChB,YAAY,KAAK;AAAA,IACnB,CAAC;AAAA,IACD,MAAM,eAAe,YAAY,QAAQ,IAAI,YAAY;AAAA,EAC3D;AACF;AAaO,IAAM,oBAAmC;AAAA,EAC9C,MAAM;AAAA,EACN,SAAS;AAAA,EACT,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaN,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAQR;AAEO,SAAS,qBAAqB,MAA8E;AACjH,QAAM,eAAe;AAAA,IACnB,GAAG;AAAA,IACH,WAAW,KAAK,aAAa;AAAA,IAC7B,kBAAkB,KAAK,oBAAoB;AAAA,EAC7C;AAEA,SAAO;AAAA,IACL,SAAS,eAAe,kBAAkB,SAAS,YAAY;AAAA,IAC/D,MAAM,cAAc,eAAe,kBAAkB,MAAM,YAAY,GAAG;AAAA,MACxE,WAAW,aAAa;AAAA,MACxB,YAAY,KAAK;AAAA,IACnB,CAAC;AAAA,IACD,MAAM,eAAe,kBAAkB,QAAQ,IAAI,YAAY;AAAA,EACjE;AACF;AAcO,IAAM,uBAAsC;AAAA,EACjD,MAAM;AAAA,EACN,SAAS;AAAA,EACT,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaN,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAQR;AAEO,SAAS,wBAAwB,MAAiF;AACvH,QAAM,eAAe;AAAA,IACnB,GAAG;AAAA,IACH,gBAAgB,KAAK,kBAAkB;AAAA,EACzC;AAGA,MAAI,OAAO,qBAAqB,KAC7B,QAAQ,sCAAsC,KAAK,OAAO,OAAO,EAAE;AACtE,MAAI,QAAQ,qBAAqB,QAAQ,IACtC,QAAQ,sCAAsC,KAAK,OAAO,OAAO,EAAE;AAEtE,SAAO,eAAe,MAAM,YAAY;AACxC,SAAO,eAAe,MAAM,YAAY;AAExC,SAAO;AAAA,IACL,SAAS,eAAe,qBAAqB,SAAS,YAAY;AAAA,IAClE,MAAM,cAAc,MAAM;AAAA,MACxB,WAAW,KAAK;AAAA,MAChB,YAAY,KAAK;AAAA,IACnB,CAAC;AAAA,IACD;AAAA,EACF;AACF;AAaO,IAAM,kBAAiC;AAAA,EAC5C,MAAM;AAAA,EACN,SAAS;AAAA,EACT,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaN,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAcR;AAEO,SAAS,mBAAmB,MAA4E;AAC7G,QAAM,YAAY,KAAK,aAAa;AACpC,QAAM,eAAe,EAAE,GAAG,MAAM,UAAU;AAG1C,MAAI,OAAO,gBAAgB,KACxB,QAAQ,sCAAsC,KAAK,OAAO,OAAO,EAAE,EACnE,QAAQ,kDAAkD,KAAK,WAAW,OAAO,EAAE;AAEtF,MAAI,QAAQ,gBAAgB,QAAQ,IACjC,QAAQ,sCAAsC,KAAK,OAAO,OAAO,EAAE,EACnE,QAAQ,kDAAkD,KAAK,WAAW,OAAO,EAAE;AAEtF,SAAO,eAAe,MAAM,YAAY;AACxC,SAAO,eAAe,MAAM,YAAY;AAExC,SAAO;AAAA,IACL,SAAS,eAAe,gBAAgB,SAAS,YAAY;AAAA,IAC7D,MAAM,cAAc,MAAM;AAAA,MACxB;AAAA,MACA,YAAY,KAAK;AAAA,IACnB,CAAC;AAAA,IACD;AAAA,EACF;AACF;AAaO,IAAM,wBAAuC;AAAA,EAClD,MAAM;AAAA,EACN,SAAS;AAAA,EACT,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaN,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAQR;AAEO,SAAS,yBAAyB,MAAkF;AACzH,QAAM,eAAe;AAAA,IACnB,GAAG;AAAA,IACH,kBAAkB,KAAK,oBAAoB;AAAA,EAC7C;AAEA,SAAO;AAAA,IACL,SAAS,eAAe,sBAAsB,SAAS,YAAY;AAAA,IACnE,MAAM,cAAc,eAAe,sBAAsB,MAAM,YAAY,GAAG;AAAA,MAC5E,WAAW,KAAK;AAAA,MAChB,YAAY,KAAK;AAAA,IACnB,CAAC;AAAA,IACD,MAAM,eAAe,sBAAsB,QAAQ,IAAI,YAAY;AAAA,EACrE;AACF;AAgBO,IAAM,qBAAoC;AAAA,EAC/C,MAAM;AAAA,EACN,SAAS;AAAA,EACT,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAiBN,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AASR;AAEO,SAAS,sBAAsB,MAA+E;AACnH,QAAM,eAAe;AAAA,IACnB,GAAG;AAAA,IACH,kBAAkB,KAAK,oBAAoB;AAAA,IAC3C,eAAe,KAAK,iBAAiB;AAAA,EACvC;AAGA,MAAI,OAAO,mBAAmB,KAC3B,QAAQ,wDAAwD,KAAK,cAAc,OAAO,EAAE,EAC5F,QAAQ,yDAAyD,KAAK,cAAc,KAAK,IAAI,EAC7F,QAAQ,0CAA0C,KAAK,OAAO,OAAO,EAAE;AAE1E,MAAI,QAAQ,mBAAmB,QAAQ,IACpC,QAAQ,wDAAwD,KAAK,cAAc,OAAO,EAAE,EAC5F,QAAQ,yDAAyD,KAAK,cAAc,KAAK,IAAI,EAC7F,QAAQ,0CAA0C,KAAK,OAAO,OAAO,EAAE;AAE1E,MAAI,UAAU,mBAAmB,QAC9B,QAAQ,wDAAwD,KAAK,cAAc,OAAO,yBAAyB;AAEtH,SAAO,eAAe,MAAM,YAAY;AACxC,SAAO,eAAe,MAAM,YAAY;AACxC,YAAU,eAAe,SAAS,YAAY;AAE9C,SAAO;AAAA,IACL;AAAA,IACA,MAAM,cAAc,MAAM;AAAA,MACxB,WAAW,KAAK;AAAA,MAChB,YAAY,KAAK;AAAA,IACnB,CAAC;AAAA,IACD;AAAA,EACF;AACF;AAMO,IAAM,YAAY;AAAA,EACvB,KAAK;AAAA,EACL,WAAW;AAAA,EACX,cAAc;AAAA,EACd,SAAS;AAAA,EACT,eAAe;AAAA,EACf,YAAY;AACd;AAEO,IAAM,kBAAkB;AAAA,EAC7B,KAAK;AAAA,EACL,WAAW;AAAA,EACX,cAAc;AAAA,EACd,SAAS;AAAA,EACT,eAAe;AAAA,EACf,YAAY;AACd;","names":[]}
@@ -0,0 +1,186 @@
1
+ export { EmailConfig, EmailSendResult, EmailAddress as ParsEmailAddress, EmailAttachment as ParsEmailAttachment, PostmarkConfig, ResendConfig, SendEmailOptions, SendTemplateEmailOptions, SendgridConfig, SesConfig, SmtpConfig, emailAddress, emailAttachment, emailConfig, emailSendResult, postmarkConfig, resendConfig, sendEmailOptions, sendTemplateEmailOptions, sendgridConfig, sesConfig, smtpConfig, type } from '@parsrun/types';
2
+
3
+ /**
4
+ * @parsrun/email - Type Definitions
5
+ * Email types and interfaces
6
+ */
7
+
8
+ /**
9
+ * Email provider type
10
+ */
11
+ type EmailProviderType = "resend" | "sendgrid" | "postmark" | "ses" | "console" | "mailgun";
12
+ /**
13
+ * Email address with optional name
14
+ */
15
+ interface EmailAddress {
16
+ email: string;
17
+ name?: string | undefined;
18
+ }
19
+ /**
20
+ * Email attachment
21
+ */
22
+ interface EmailAttachment {
23
+ /** File name */
24
+ filename: string;
25
+ /** File content (base64 encoded or Buffer) */
26
+ content: string | Uint8Array;
27
+ /** Content type (MIME type) */
28
+ contentType?: string | undefined;
29
+ /** Content ID for inline attachments */
30
+ contentId?: string | undefined;
31
+ }
32
+ /**
33
+ * Email options
34
+ */
35
+ interface EmailOptions {
36
+ /** Recipient email address(es) */
37
+ to: string | string[] | EmailAddress | EmailAddress[];
38
+ /** Email subject */
39
+ subject: string;
40
+ /** HTML content */
41
+ html?: string | undefined;
42
+ /** Plain text content */
43
+ text?: string | undefined;
44
+ /** From address (overrides default) */
45
+ from?: string | EmailAddress | undefined;
46
+ /** Reply-to address */
47
+ replyTo?: string | EmailAddress | undefined;
48
+ /** CC recipients */
49
+ cc?: string | string[] | EmailAddress | EmailAddress[] | undefined;
50
+ /** BCC recipients */
51
+ bcc?: string | string[] | EmailAddress | EmailAddress[] | undefined;
52
+ /** Attachments */
53
+ attachments?: EmailAttachment[] | undefined;
54
+ /** Custom headers */
55
+ headers?: Record<string, string> | undefined;
56
+ /** Tags for tracking */
57
+ tags?: Record<string, string> | undefined;
58
+ /** Schedule send time */
59
+ scheduledAt?: Date | undefined;
60
+ }
61
+ /**
62
+ * Email send result
63
+ */
64
+ interface EmailResult {
65
+ /** Whether send was successful */
66
+ success: boolean;
67
+ /** Message ID from provider */
68
+ messageId?: string | undefined;
69
+ /** Error message if failed */
70
+ error?: string | undefined;
71
+ /** Provider-specific response data */
72
+ data?: unknown;
73
+ }
74
+ /**
75
+ * Batch email options
76
+ */
77
+ interface BatchEmailOptions {
78
+ /** List of emails to send */
79
+ emails: EmailOptions[];
80
+ /** Whether to stop on first error */
81
+ stopOnError?: boolean | undefined;
82
+ }
83
+ /**
84
+ * Batch email result
85
+ */
86
+ interface BatchEmailResult {
87
+ /** Total emails attempted */
88
+ total: number;
89
+ /** Successful sends */
90
+ successful: number;
91
+ /** Failed sends */
92
+ failed: number;
93
+ /** Individual results */
94
+ results: EmailResult[];
95
+ }
96
+ /**
97
+ * Email provider configuration
98
+ */
99
+ interface EmailProviderConfig {
100
+ /** API key for the provider */
101
+ apiKey: string;
102
+ /** Default from email */
103
+ fromEmail: string;
104
+ /** Default from name */
105
+ fromName?: string | undefined;
106
+ /** Provider-specific options */
107
+ options?: Record<string, unknown> | undefined;
108
+ }
109
+ /**
110
+ * Email provider interface
111
+ */
112
+ interface EmailProvider {
113
+ /** Provider type */
114
+ readonly type: EmailProviderType;
115
+ /**
116
+ * Send a single email
117
+ */
118
+ send(options: EmailOptions): Promise<EmailResult>;
119
+ /**
120
+ * Send multiple emails
121
+ */
122
+ sendBatch?(options: BatchEmailOptions): Promise<BatchEmailResult>;
123
+ /**
124
+ * Verify provider configuration
125
+ */
126
+ verify?(): Promise<boolean>;
127
+ }
128
+ /**
129
+ * Email service configuration
130
+ */
131
+ interface EmailServiceConfig {
132
+ /** Provider type */
133
+ provider: EmailProviderType;
134
+ /** API key */
135
+ apiKey: string;
136
+ /** Default from email */
137
+ fromEmail: string;
138
+ /** Default from name */
139
+ fromName?: string | undefined;
140
+ /** Enable debug logging */
141
+ debug?: boolean | undefined;
142
+ /** Provider-specific options */
143
+ providerOptions?: Record<string, unknown> | undefined;
144
+ }
145
+ /**
146
+ * Template data for email templates
147
+ */
148
+ interface TemplateData {
149
+ [key: string]: string | number | boolean | undefined | null | TemplateData | TemplateData[];
150
+ }
151
+ /**
152
+ * Email template
153
+ */
154
+ interface EmailTemplate {
155
+ /** Template name */
156
+ name: string;
157
+ /** Subject template */
158
+ subject: string;
159
+ /** HTML template */
160
+ html: string;
161
+ /** Plain text template */
162
+ text?: string | undefined;
163
+ }
164
+ /**
165
+ * Email error
166
+ */
167
+ declare class EmailError extends Error {
168
+ readonly code: string;
169
+ readonly cause?: unknown | undefined;
170
+ constructor(message: string, code: string, cause?: unknown | undefined);
171
+ }
172
+ /**
173
+ * Common email error codes
174
+ */
175
+ declare const EmailErrorCodes: {
176
+ readonly INVALID_CONFIG: "INVALID_CONFIG";
177
+ readonly INVALID_RECIPIENT: "INVALID_RECIPIENT";
178
+ readonly INVALID_CONTENT: "INVALID_CONTENT";
179
+ readonly SEND_FAILED: "SEND_FAILED";
180
+ readonly RATE_LIMITED: "RATE_LIMITED";
181
+ readonly PROVIDER_ERROR: "PROVIDER_ERROR";
182
+ readonly TEMPLATE_ERROR: "TEMPLATE_ERROR";
183
+ readonly ATTACHMENT_ERROR: "ATTACHMENT_ERROR";
184
+ };
185
+
186
+ export { type BatchEmailOptions, type BatchEmailResult, type EmailAddress, type EmailAttachment, EmailError, EmailErrorCodes, type EmailOptions, type EmailProvider, type EmailProviderConfig, type EmailProviderType, type EmailResult, type EmailServiceConfig, type EmailTemplate, type TemplateData };