@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.
- package/README.md +145 -0
- package/dist/index.d.ts +103 -0
- package/dist/index.js +1192 -0
- package/dist/index.js.map +1 -0
- package/dist/providers/console.d.ts +47 -0
- package/dist/providers/console.js +126 -0
- package/dist/providers/console.js.map +1 -0
- package/dist/providers/index.d.ts +6 -0
- package/dist/providers/index.js +621 -0
- package/dist/providers/index.js.map +1 -0
- package/dist/providers/postmark.d.ts +47 -0
- package/dist/providers/postmark.js +202 -0
- package/dist/providers/postmark.js.map +1 -0
- package/dist/providers/resend.d.ts +47 -0
- package/dist/providers/resend.js +174 -0
- package/dist/providers/resend.js.map +1 -0
- package/dist/providers/sendgrid.d.ts +47 -0
- package/dist/providers/sendgrid.js +193 -0
- package/dist/providers/sendgrid.js.map +1 -0
- package/dist/templates/index.d.ts +114 -0
- package/dist/templates/index.js +415 -0
- package/dist/templates/index.js.map +1 -0
- package/dist/types.d.ts +186 -0
- package/dist/types.js +50 -0
- package/dist/types.js.map +1 -0
- package/package.json +80 -0
|
@@ -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 ?? `© ${(/* @__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 ?? `© ${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":"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","names":[]}
|
package/dist/types.d.ts
ADDED
|
@@ -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 };
|