@parsrun/email 0.1.29 → 0.1.31
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/dist/index.d.ts +64 -6
- package/dist/index.js +153 -4
- package/dist/index.js.map +1 -1
- package/dist/providers/console.d.ts +29 -1
- package/dist/providers/console.js +25 -0
- package/dist/providers/console.js.map +1 -1
- package/dist/providers/index.js +130 -0
- package/dist/providers/index.js.map +1 -1
- package/dist/providers/postmark.d.ts +37 -1
- package/dist/providers/postmark.js +40 -0
- package/dist/providers/postmark.js.map +1 -1
- package/dist/providers/resend.d.ts +36 -1
- package/dist/providers/resend.js +39 -0
- package/dist/providers/resend.js.map +1 -1
- package/dist/providers/sendgrid.d.ts +37 -1
- package/dist/providers/sendgrid.js +40 -0
- package/dist/providers/sendgrid.js.map +1 -1
- package/dist/templates/index.d.ts +219 -2
- package/dist/templates/index.js.map +1 -1
- package/dist/types.d.ts +21 -1
- package/dist/types.js +7 -0
- package/dist/types.js.map +1 -1
- package/package.json +3 -3
|
@@ -7,93 +7,298 @@ import '@parsrun/types';
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
|
-
*
|
|
10
|
+
* Renders a template string by replacing placeholders with data values.
|
|
11
|
+
*
|
|
12
|
+
* Uses a simple mustache-like syntax where `{{key}}` is replaced with the value
|
|
13
|
+
* of `data.key`. Supports nested keys like `{{user.name}}`.
|
|
14
|
+
*
|
|
15
|
+
* @param template - The template string containing placeholders
|
|
16
|
+
* @param data - The data object containing values to substitute
|
|
17
|
+
* @returns The rendered string with placeholders replaced by values
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```typescript
|
|
21
|
+
* const result = renderTemplate("Hello {{name}}!", { name: "World" });
|
|
22
|
+
* // result: "Hello World!"
|
|
23
|
+
* ```
|
|
11
24
|
*/
|
|
12
25
|
declare function renderTemplate(template: string, data: TemplateData): string;
|
|
13
26
|
/**
|
|
14
|
-
*
|
|
27
|
+
* Wraps email content in a responsive HTML template with consistent styling.
|
|
28
|
+
*
|
|
29
|
+
* Provides a professional email layout with header branding, content area,
|
|
30
|
+
* and footer. Styles are inlined for maximum email client compatibility.
|
|
31
|
+
*
|
|
32
|
+
* @param content - The HTML content to wrap
|
|
33
|
+
* @param options - Optional branding configuration
|
|
34
|
+
* @param options.brandName - The brand name to display in header (defaults to "Pars")
|
|
35
|
+
* @param options.brandColor - The primary brand color for styling (defaults to "#0070f3")
|
|
36
|
+
* @param options.footerText - Custom footer text (defaults to copyright notice)
|
|
37
|
+
* @returns Complete HTML email document
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```typescript
|
|
41
|
+
* const html = wrapEmailHtml("<h1>Hello!</h1>", {
|
|
42
|
+
* brandName: "My App",
|
|
43
|
+
* brandColor: "#ff0000",
|
|
44
|
+
* });
|
|
45
|
+
* ```
|
|
15
46
|
*/
|
|
16
47
|
declare function wrapEmailHtml(content: string, options?: {
|
|
17
48
|
brandName?: string | undefined;
|
|
18
49
|
brandColor?: string | undefined;
|
|
19
50
|
footerText?: string | undefined;
|
|
20
51
|
}): string;
|
|
52
|
+
/**
|
|
53
|
+
* Data required for rendering OTP (One-Time Password) email templates.
|
|
54
|
+
*/
|
|
21
55
|
interface OTPTemplateData extends TemplateData {
|
|
56
|
+
/** The verification code to display */
|
|
22
57
|
code: string;
|
|
58
|
+
/** Expiration time in minutes (defaults to 10) */
|
|
23
59
|
expiresInMinutes?: number;
|
|
60
|
+
/** Brand name for email header */
|
|
24
61
|
brandName?: string;
|
|
62
|
+
/** Brand color for styling */
|
|
25
63
|
brandColor?: string;
|
|
26
64
|
}
|
|
65
|
+
/**
|
|
66
|
+
* Pre-built email template for OTP verification codes.
|
|
67
|
+
*
|
|
68
|
+
* Displays a large, easily copyable verification code with expiration notice.
|
|
69
|
+
*/
|
|
27
70
|
declare const otpTemplate: EmailTemplate;
|
|
71
|
+
/**
|
|
72
|
+
* Renders an OTP verification email with the provided data.
|
|
73
|
+
*
|
|
74
|
+
* @param data - The template data including verification code and branding options
|
|
75
|
+
* @returns Rendered email with subject, HTML body, and plain text body
|
|
76
|
+
*
|
|
77
|
+
* @example
|
|
78
|
+
* ```typescript
|
|
79
|
+
* const email = renderOTPEmail({
|
|
80
|
+
* code: "123456",
|
|
81
|
+
* expiresInMinutes: 15,
|
|
82
|
+
* brandName: "My App",
|
|
83
|
+
* });
|
|
84
|
+
* await emailService.send({
|
|
85
|
+
* to: "user@example.com",
|
|
86
|
+
* ...email,
|
|
87
|
+
* });
|
|
88
|
+
* ```
|
|
89
|
+
*/
|
|
28
90
|
declare function renderOTPEmail(data: OTPTemplateData): {
|
|
29
91
|
subject: string;
|
|
30
92
|
html: string;
|
|
31
93
|
text: string;
|
|
32
94
|
};
|
|
95
|
+
/**
|
|
96
|
+
* Data required for rendering magic link email templates.
|
|
97
|
+
*/
|
|
33
98
|
interface MagicLinkTemplateData extends TemplateData {
|
|
99
|
+
/** The magic link URL for sign-in */
|
|
34
100
|
url: string;
|
|
101
|
+
/** Expiration time in minutes (defaults to 15) */
|
|
35
102
|
expiresInMinutes?: number;
|
|
103
|
+
/** Brand name for email header and subject */
|
|
36
104
|
brandName?: string;
|
|
105
|
+
/** Brand color for styling */
|
|
37
106
|
brandColor?: string;
|
|
38
107
|
}
|
|
108
|
+
/**
|
|
109
|
+
* Pre-built email template for passwordless magic link sign-in.
|
|
110
|
+
*
|
|
111
|
+
* Provides a prominent sign-in button with fallback URL text.
|
|
112
|
+
*/
|
|
39
113
|
declare const magicLinkTemplate: EmailTemplate;
|
|
114
|
+
/**
|
|
115
|
+
* Renders a magic link sign-in email with the provided data.
|
|
116
|
+
*
|
|
117
|
+
* @param data - The template data including magic link URL and branding options
|
|
118
|
+
* @returns Rendered email with subject, HTML body, and plain text body
|
|
119
|
+
*
|
|
120
|
+
* @example
|
|
121
|
+
* ```typescript
|
|
122
|
+
* const email = renderMagicLinkEmail({
|
|
123
|
+
* url: "https://example.com/auth/verify?token=abc123",
|
|
124
|
+
* brandName: "My App",
|
|
125
|
+
* });
|
|
126
|
+
* ```
|
|
127
|
+
*/
|
|
40
128
|
declare function renderMagicLinkEmail(data: MagicLinkTemplateData): {
|
|
41
129
|
subject: string;
|
|
42
130
|
html: string;
|
|
43
131
|
text: string;
|
|
44
132
|
};
|
|
133
|
+
/**
|
|
134
|
+
* Data required for rendering email verification templates.
|
|
135
|
+
*/
|
|
45
136
|
interface VerificationTemplateData extends TemplateData {
|
|
137
|
+
/** The verification URL */
|
|
46
138
|
url: string;
|
|
139
|
+
/** User's name for personalized greeting (optional) */
|
|
47
140
|
name?: string;
|
|
141
|
+
/** Expiration time in hours (defaults to 24) */
|
|
48
142
|
expiresInHours?: number;
|
|
143
|
+
/** Brand name for email header */
|
|
49
144
|
brandName?: string;
|
|
145
|
+
/** Brand color for styling */
|
|
50
146
|
brandColor?: string;
|
|
51
147
|
}
|
|
148
|
+
/**
|
|
149
|
+
* Pre-built email template for email address verification.
|
|
150
|
+
*
|
|
151
|
+
* Used when users need to confirm their email address after registration.
|
|
152
|
+
*/
|
|
52
153
|
declare const verificationTemplate: EmailTemplate;
|
|
154
|
+
/**
|
|
155
|
+
* Renders an email verification email with the provided data.
|
|
156
|
+
*
|
|
157
|
+
* @param data - The template data including verification URL and optional user name
|
|
158
|
+
* @returns Rendered email with subject, HTML body, and plain text body
|
|
159
|
+
*
|
|
160
|
+
* @example
|
|
161
|
+
* ```typescript
|
|
162
|
+
* const email = renderVerificationEmail({
|
|
163
|
+
* url: "https://example.com/verify?token=abc123",
|
|
164
|
+
* name: "John",
|
|
165
|
+
* });
|
|
166
|
+
* ```
|
|
167
|
+
*/
|
|
53
168
|
declare function renderVerificationEmail(data: VerificationTemplateData): {
|
|
54
169
|
subject: string;
|
|
55
170
|
html: string;
|
|
56
171
|
text: string;
|
|
57
172
|
};
|
|
173
|
+
/**
|
|
174
|
+
* Data required for rendering welcome email templates.
|
|
175
|
+
*/
|
|
58
176
|
interface WelcomeTemplateData extends TemplateData {
|
|
177
|
+
/** User's name for personalized greeting (optional) */
|
|
59
178
|
name?: string;
|
|
179
|
+
/** URL to the user's dashboard or login page (optional) */
|
|
60
180
|
loginUrl?: string;
|
|
181
|
+
/** Brand name for email header */
|
|
61
182
|
brandName?: string;
|
|
183
|
+
/** Brand color for styling */
|
|
62
184
|
brandColor?: string;
|
|
63
185
|
}
|
|
186
|
+
/**
|
|
187
|
+
* Pre-built email template for welcoming new users.
|
|
188
|
+
*
|
|
189
|
+
* Sent after successful registration to greet users and provide next steps.
|
|
190
|
+
*/
|
|
64
191
|
declare const welcomeTemplate: EmailTemplate;
|
|
192
|
+
/**
|
|
193
|
+
* Renders a welcome email with the provided data.
|
|
194
|
+
*
|
|
195
|
+
* @param data - The template data including optional user name and dashboard URL
|
|
196
|
+
* @returns Rendered email with subject, HTML body, and plain text body
|
|
197
|
+
*
|
|
198
|
+
* @example
|
|
199
|
+
* ```typescript
|
|
200
|
+
* const email = renderWelcomeEmail({
|
|
201
|
+
* name: "John",
|
|
202
|
+
* loginUrl: "https://example.com/dashboard",
|
|
203
|
+
* brandName: "My App",
|
|
204
|
+
* });
|
|
205
|
+
* ```
|
|
206
|
+
*/
|
|
65
207
|
declare function renderWelcomeEmail(data: WelcomeTemplateData): {
|
|
66
208
|
subject: string;
|
|
67
209
|
html: string;
|
|
68
210
|
text: string;
|
|
69
211
|
};
|
|
212
|
+
/**
|
|
213
|
+
* Data required for rendering password reset email templates.
|
|
214
|
+
*/
|
|
70
215
|
interface PasswordResetTemplateData extends TemplateData {
|
|
216
|
+
/** The password reset URL */
|
|
71
217
|
url: string;
|
|
218
|
+
/** Expiration time in minutes (defaults to 60) */
|
|
72
219
|
expiresInMinutes?: number;
|
|
220
|
+
/** Brand name for email header */
|
|
73
221
|
brandName?: string;
|
|
222
|
+
/** Brand color for styling */
|
|
74
223
|
brandColor?: string;
|
|
75
224
|
}
|
|
225
|
+
/**
|
|
226
|
+
* Pre-built email template for password reset requests.
|
|
227
|
+
*
|
|
228
|
+
* Includes security notice that the link can be ignored if not requested.
|
|
229
|
+
*/
|
|
76
230
|
declare const passwordResetTemplate: EmailTemplate;
|
|
231
|
+
/**
|
|
232
|
+
* Renders a password reset email with the provided data.
|
|
233
|
+
*
|
|
234
|
+
* @param data - The template data including reset URL and branding options
|
|
235
|
+
* @returns Rendered email with subject, HTML body, and plain text body
|
|
236
|
+
*
|
|
237
|
+
* @example
|
|
238
|
+
* ```typescript
|
|
239
|
+
* const email = renderPasswordResetEmail({
|
|
240
|
+
* url: "https://example.com/reset?token=abc123",
|
|
241
|
+
* expiresInMinutes: 30,
|
|
242
|
+
* });
|
|
243
|
+
* ```
|
|
244
|
+
*/
|
|
77
245
|
declare function renderPasswordResetEmail(data: PasswordResetTemplateData): {
|
|
78
246
|
subject: string;
|
|
79
247
|
html: string;
|
|
80
248
|
text: string;
|
|
81
249
|
};
|
|
250
|
+
/**
|
|
251
|
+
* Data required for rendering invitation email templates.
|
|
252
|
+
*/
|
|
82
253
|
interface InvitationTemplateData extends TemplateData {
|
|
254
|
+
/** The invitation acceptance URL */
|
|
83
255
|
url: string;
|
|
256
|
+
/** Name of the person who sent the invitation (optional) */
|
|
84
257
|
inviterName?: string;
|
|
258
|
+
/** Name of the organization being invited to (defaults to "the team") */
|
|
85
259
|
organizationName?: string;
|
|
260
|
+
/** Role the user is being invited to (optional) */
|
|
86
261
|
role?: string;
|
|
262
|
+
/** Expiration time in days (defaults to 7) */
|
|
87
263
|
expiresInDays?: number;
|
|
264
|
+
/** Brand name for email header */
|
|
88
265
|
brandName?: string;
|
|
266
|
+
/** Brand color for styling */
|
|
89
267
|
brandColor?: string;
|
|
90
268
|
}
|
|
269
|
+
/**
|
|
270
|
+
* Pre-built email template for team/organization invitations.
|
|
271
|
+
*
|
|
272
|
+
* Supports inviter name, organization name, and role customization.
|
|
273
|
+
*/
|
|
91
274
|
declare const invitationTemplate: EmailTemplate;
|
|
275
|
+
/**
|
|
276
|
+
* Renders an invitation email with the provided data.
|
|
277
|
+
*
|
|
278
|
+
* @param data - The template data including invitation URL and organization details
|
|
279
|
+
* @returns Rendered email with subject, HTML body, and plain text body
|
|
280
|
+
*
|
|
281
|
+
* @example
|
|
282
|
+
* ```typescript
|
|
283
|
+
* const email = renderInvitationEmail({
|
|
284
|
+
* url: "https://example.com/invite?token=abc123",
|
|
285
|
+
* inviterName: "Jane",
|
|
286
|
+
* organizationName: "Acme Corp",
|
|
287
|
+
* role: "developer",
|
|
288
|
+
* });
|
|
289
|
+
* ```
|
|
290
|
+
*/
|
|
92
291
|
declare function renderInvitationEmail(data: InvitationTemplateData): {
|
|
93
292
|
subject: string;
|
|
94
293
|
html: string;
|
|
95
294
|
text: string;
|
|
96
295
|
};
|
|
296
|
+
/**
|
|
297
|
+
* Collection of all pre-built email templates.
|
|
298
|
+
*
|
|
299
|
+
* Use these templates with the corresponding render functions to generate
|
|
300
|
+
* complete email content with proper styling.
|
|
301
|
+
*/
|
|
97
302
|
declare const templates: {
|
|
98
303
|
readonly otp: EmailTemplate;
|
|
99
304
|
readonly magicLink: EmailTemplate;
|
|
@@ -102,6 +307,18 @@ declare const templates: {
|
|
|
102
307
|
readonly passwordReset: EmailTemplate;
|
|
103
308
|
readonly invitation: EmailTemplate;
|
|
104
309
|
};
|
|
310
|
+
/**
|
|
311
|
+
* Collection of render functions for each email template.
|
|
312
|
+
*
|
|
313
|
+
* These functions take template data and return complete rendered emails
|
|
314
|
+
* with subject, HTML body, and plain text body.
|
|
315
|
+
*
|
|
316
|
+
* @example
|
|
317
|
+
* ```typescript
|
|
318
|
+
* const email = renderFunctions.otp({ code: "123456" });
|
|
319
|
+
* await emailService.send({ to: "user@example.com", ...email });
|
|
320
|
+
* ```
|
|
321
|
+
*/
|
|
105
322
|
declare const renderFunctions: {
|
|
106
323
|
readonly otp: typeof renderOTPEmail;
|
|
107
324
|
readonly magicLink: typeof renderMagicLinkEmail;
|
|
@@ -1 +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":[]}
|
|
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 * Renders a template string by replacing placeholders with data values.\n *\n * Uses a simple mustache-like syntax where `{{key}}` is replaced with the value\n * of `data.key`. Supports nested keys like `{{user.name}}`.\n *\n * @param template - The template string containing placeholders\n * @param data - The data object containing values to substitute\n * @returns The rendered string with placeholders replaced by values\n *\n * @example\n * ```typescript\n * const result = renderTemplate(\"Hello {{name}}!\", { name: \"World\" });\n * // result: \"Hello World!\"\n * ```\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 * Wraps email content in a responsive HTML template with consistent styling.\n *\n * Provides a professional email layout with header branding, content area,\n * and footer. Styles are inlined for maximum email client compatibility.\n *\n * @param content - The HTML content to wrap\n * @param options - Optional branding configuration\n * @param options.brandName - The brand name to display in header (defaults to \"Pars\")\n * @param options.brandColor - The primary brand color for styling (defaults to \"#0070f3\")\n * @param options.footerText - Custom footer text (defaults to copyright notice)\n * @returns Complete HTML email document\n *\n * @example\n * ```typescript\n * const html = wrapEmailHtml(\"<h1>Hello!</h1>\", {\n * brandName: \"My App\",\n * brandColor: \"#ff0000\",\n * });\n * ```\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\n/**\n * Data required for rendering OTP (One-Time Password) email templates.\n */\nexport interface OTPTemplateData extends TemplateData {\n /** The verification code to display */\n code: string;\n /** Expiration time in minutes (defaults to 10) */\n expiresInMinutes?: number;\n /** Brand name for email header */\n brandName?: string;\n /** Brand color for styling */\n brandColor?: string;\n}\n\n/**\n * Pre-built email template for OTP verification codes.\n *\n * Displays a large, easily copyable verification code with expiration notice.\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\n/**\n * Renders an OTP verification email with the provided data.\n *\n * @param data - The template data including verification code and branding options\n * @returns Rendered email with subject, HTML body, and plain text body\n *\n * @example\n * ```typescript\n * const email = renderOTPEmail({\n * code: \"123456\",\n * expiresInMinutes: 15,\n * brandName: \"My App\",\n * });\n * await emailService.send({\n * to: \"user@example.com\",\n * ...email,\n * });\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\n/**\n * Data required for rendering magic link email templates.\n */\nexport interface MagicLinkTemplateData extends TemplateData {\n /** The magic link URL for sign-in */\n url: string;\n /** Expiration time in minutes (defaults to 15) */\n expiresInMinutes?: number;\n /** Brand name for email header and subject */\n brandName?: string;\n /** Brand color for styling */\n brandColor?: string;\n}\n\n/**\n * Pre-built email template for passwordless magic link sign-in.\n *\n * Provides a prominent sign-in button with fallback URL text.\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\n/**\n * Renders a magic link sign-in email with the provided data.\n *\n * @param data - The template data including magic link URL and branding options\n * @returns Rendered email with subject, HTML body, and plain text body\n *\n * @example\n * ```typescript\n * const email = renderMagicLinkEmail({\n * url: \"https://example.com/auth/verify?token=abc123\",\n * brandName: \"My App\",\n * });\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\n/**\n * Data required for rendering email verification templates.\n */\nexport interface VerificationTemplateData extends TemplateData {\n /** The verification URL */\n url: string;\n /** User's name for personalized greeting (optional) */\n name?: string;\n /** Expiration time in hours (defaults to 24) */\n expiresInHours?: number;\n /** Brand name for email header */\n brandName?: string;\n /** Brand color for styling */\n brandColor?: string;\n}\n\n/**\n * Pre-built email template for email address verification.\n *\n * Used when users need to confirm their email address after registration.\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\n/**\n * Renders an email verification email with the provided data.\n *\n * @param data - The template data including verification URL and optional user name\n * @returns Rendered email with subject, HTML body, and plain text body\n *\n * @example\n * ```typescript\n * const email = renderVerificationEmail({\n * url: \"https://example.com/verify?token=abc123\",\n * name: \"John\",\n * });\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\n/**\n * Data required for rendering welcome email templates.\n */\nexport interface WelcomeTemplateData extends TemplateData {\n /** User's name for personalized greeting (optional) */\n name?: string;\n /** URL to the user's dashboard or login page (optional) */\n loginUrl?: string;\n /** Brand name for email header */\n brandName?: string;\n /** Brand color for styling */\n brandColor?: string;\n}\n\n/**\n * Pre-built email template for welcoming new users.\n *\n * Sent after successful registration to greet users and provide next steps.\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\n/**\n * Renders a welcome email with the provided data.\n *\n * @param data - The template data including optional user name and dashboard URL\n * @returns Rendered email with subject, HTML body, and plain text body\n *\n * @example\n * ```typescript\n * const email = renderWelcomeEmail({\n * name: \"John\",\n * loginUrl: \"https://example.com/dashboard\",\n * brandName: \"My App\",\n * });\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\n/**\n * Data required for rendering password reset email templates.\n */\nexport interface PasswordResetTemplateData extends TemplateData {\n /** The password reset URL */\n url: string;\n /** Expiration time in minutes (defaults to 60) */\n expiresInMinutes?: number;\n /** Brand name for email header */\n brandName?: string;\n /** Brand color for styling */\n brandColor?: string;\n}\n\n/**\n * Pre-built email template for password reset requests.\n *\n * Includes security notice that the link can be ignored if not requested.\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\n/**\n * Renders a password reset email with the provided data.\n *\n * @param data - The template data including reset URL and branding options\n * @returns Rendered email with subject, HTML body, and plain text body\n *\n * @example\n * ```typescript\n * const email = renderPasswordResetEmail({\n * url: \"https://example.com/reset?token=abc123\",\n * expiresInMinutes: 30,\n * });\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\n/**\n * Data required for rendering invitation email templates.\n */\nexport interface InvitationTemplateData extends TemplateData {\n /** The invitation acceptance URL */\n url: string;\n /** Name of the person who sent the invitation (optional) */\n inviterName?: string;\n /** Name of the organization being invited to (defaults to \"the team\") */\n organizationName?: string;\n /** Role the user is being invited to (optional) */\n role?: string;\n /** Expiration time in days (defaults to 7) */\n expiresInDays?: number;\n /** Brand name for email header */\n brandName?: string;\n /** Brand color for styling */\n brandColor?: string;\n}\n\n/**\n * Pre-built email template for team/organization invitations.\n *\n * Supports inviter name, organization name, and role customization.\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\n/**\n * Renders an invitation email with the provided data.\n *\n * @param data - The template data including invitation URL and organization details\n * @returns Rendered email with subject, HTML body, and plain text body\n *\n * @example\n * ```typescript\n * const email = renderInvitationEmail({\n * url: \"https://example.com/invite?token=abc123\",\n * inviterName: \"Jane\",\n * organizationName: \"Acme Corp\",\n * role: \"developer\",\n * });\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\n/**\n * Collection of all pre-built email templates.\n *\n * Use these templates with the corresponding render functions to generate\n * complete email content with proper styling.\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\n/**\n * Collection of render functions for each email template.\n *\n * These functions take template data and return complete rendered emails\n * with subject, HTML body, and plain text body.\n *\n * @example\n * ```typescript\n * const email = renderFunctions.otp({ code: \"123456\" });\n * await emailService.send({ to: \"user@example.com\", ...email });\n * ```\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
CHANGED
|
@@ -162,11 +162,31 @@ interface EmailTemplate {
|
|
|
162
162
|
text?: string | undefined;
|
|
163
163
|
}
|
|
164
164
|
/**
|
|
165
|
-
*
|
|
165
|
+
* Custom error class for email-related errors.
|
|
166
|
+
*
|
|
167
|
+
* Provides structured error information with error codes for programmatic handling.
|
|
168
|
+
*
|
|
169
|
+
* @example
|
|
170
|
+
* ```typescript
|
|
171
|
+
* try {
|
|
172
|
+
* await provider.send(options);
|
|
173
|
+
* } catch (err) {
|
|
174
|
+
* if (err instanceof EmailError) {
|
|
175
|
+
* console.log(`Error code: ${err.code}`);
|
|
176
|
+
* }
|
|
177
|
+
* }
|
|
178
|
+
* ```
|
|
166
179
|
*/
|
|
167
180
|
declare class EmailError extends Error {
|
|
168
181
|
readonly code: string;
|
|
169
182
|
readonly cause?: unknown | undefined;
|
|
183
|
+
/**
|
|
184
|
+
* Creates a new EmailError instance.
|
|
185
|
+
*
|
|
186
|
+
* @param message - Human-readable error description
|
|
187
|
+
* @param code - Error code from EmailErrorCodes for programmatic handling
|
|
188
|
+
* @param cause - The underlying error that caused this error, if any
|
|
189
|
+
*/
|
|
170
190
|
constructor(message: string, code: string, cause?: unknown | undefined);
|
|
171
191
|
}
|
|
172
192
|
/**
|
package/dist/types.js
CHANGED
|
@@ -14,6 +14,13 @@ import {
|
|
|
14
14
|
emailConfig
|
|
15
15
|
} from "@parsrun/types";
|
|
16
16
|
var EmailError = class extends Error {
|
|
17
|
+
/**
|
|
18
|
+
* Creates a new EmailError instance.
|
|
19
|
+
*
|
|
20
|
+
* @param message - Human-readable error description
|
|
21
|
+
* @param code - Error code from EmailErrorCodes for programmatic handling
|
|
22
|
+
* @param cause - The underlying error that caused this error, if any
|
|
23
|
+
*/
|
|
17
24
|
constructor(message, code, cause) {
|
|
18
25
|
super(message);
|
|
19
26
|
this.code = code;
|
package/dist/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/types.ts"],"sourcesContent":["/**\n * @parsrun/email - Type Definitions\n * Email types and interfaces\n */\n\n// Re-export types from @parsrun/types for convenience\nexport {\n type,\n emailAddress,\n emailAttachment,\n sendEmailOptions,\n sendTemplateEmailOptions,\n emailSendResult,\n smtpConfig,\n resendConfig,\n sendgridConfig,\n sesConfig,\n postmarkConfig,\n emailConfig,\n type EmailAddress as ParsEmailAddress,\n type EmailAttachment as ParsEmailAttachment,\n type SendEmailOptions,\n type SendTemplateEmailOptions,\n type EmailSendResult,\n type SmtpConfig,\n type ResendConfig,\n type SendgridConfig,\n type SesConfig,\n type PostmarkConfig,\n type EmailConfig,\n} from \"@parsrun/types\";\n\n/**\n * Email provider type\n */\nexport type EmailProviderType = \"resend\" | \"sendgrid\" | \"postmark\" | \"ses\" | \"console\" | \"mailgun\";\n\n/**\n * Email address with optional name\n */\nexport interface EmailAddress {\n email: string;\n name?: string | undefined;\n}\n\n/**\n * Email attachment\n */\nexport interface EmailAttachment {\n /** File name */\n filename: string;\n /** File content (base64 encoded or Buffer) */\n content: string | Uint8Array;\n /** Content type (MIME type) */\n contentType?: string | undefined;\n /** Content ID for inline attachments */\n contentId?: string | undefined;\n}\n\n/**\n * Email options\n */\nexport interface EmailOptions {\n /** Recipient email address(es) */\n to: string | string[] | EmailAddress | EmailAddress[];\n /** Email subject */\n subject: string;\n /** HTML content */\n html?: string | undefined;\n /** Plain text content */\n text?: string | undefined;\n /** From address (overrides default) */\n from?: string | EmailAddress | undefined;\n /** Reply-to address */\n replyTo?: string | EmailAddress | undefined;\n /** CC recipients */\n cc?: string | string[] | EmailAddress | EmailAddress[] | undefined;\n /** BCC recipients */\n bcc?: string | string[] | EmailAddress | EmailAddress[] | undefined;\n /** Attachments */\n attachments?: EmailAttachment[] | undefined;\n /** Custom headers */\n headers?: Record<string, string> | undefined;\n /** Tags for tracking */\n tags?: Record<string, string> | undefined;\n /** Schedule send time */\n scheduledAt?: Date | undefined;\n}\n\n/**\n * Email send result\n */\nexport interface EmailResult {\n /** Whether send was successful */\n success: boolean;\n /** Message ID from provider */\n messageId?: string | undefined;\n /** Error message if failed */\n error?: string | undefined;\n /** Provider-specific response data */\n data?: unknown;\n}\n\n/**\n * Batch email options\n */\nexport interface BatchEmailOptions {\n /** List of emails to send */\n emails: EmailOptions[];\n /** Whether to stop on first error */\n stopOnError?: boolean | undefined;\n}\n\n/**\n * Batch email result\n */\nexport interface BatchEmailResult {\n /** Total emails attempted */\n total: number;\n /** Successful sends */\n successful: number;\n /** Failed sends */\n failed: number;\n /** Individual results */\n results: EmailResult[];\n}\n\n/**\n * Email provider configuration\n */\nexport interface EmailProviderConfig {\n /** API key for the provider */\n apiKey: string;\n /** Default from email */\n fromEmail: string;\n /** Default from name */\n fromName?: string | undefined;\n /** Provider-specific options */\n options?: Record<string, unknown> | undefined;\n}\n\n/**\n * Email provider interface\n */\nexport interface EmailProvider {\n /** Provider type */\n readonly type: EmailProviderType;\n\n /**\n * Send a single email\n */\n send(options: EmailOptions): Promise<EmailResult>;\n\n /**\n * Send multiple emails\n */\n sendBatch?(options: BatchEmailOptions): Promise<BatchEmailResult>;\n\n /**\n * Verify provider configuration\n */\n verify?(): Promise<boolean>;\n}\n\n/**\n * Email service configuration\n */\nexport interface EmailServiceConfig {\n /** Provider type */\n provider: EmailProviderType;\n /** API key */\n apiKey: string;\n /** Default from email */\n fromEmail: string;\n /** Default from name */\n fromName?: string | undefined;\n /** Enable debug logging */\n debug?: boolean | undefined;\n /** Provider-specific options */\n providerOptions?: Record<string, unknown> | undefined;\n}\n\n/**\n * Template data for email templates\n */\nexport interface TemplateData {\n [key: string]: string | number | boolean | undefined | null | TemplateData | TemplateData[];\n}\n\n/**\n * Email template\n */\nexport interface EmailTemplate {\n /** Template name */\n name: string;\n /** Subject template */\n subject: string;\n /** HTML template */\n html: string;\n /** Plain text template */\n text?: string | undefined;\n}\n\n/**\n *
|
|
1
|
+
{"version":3,"sources":["../src/types.ts"],"sourcesContent":["/**\n * @parsrun/email - Type Definitions\n * Email types and interfaces\n */\n\n// Re-export types from @parsrun/types for convenience\nexport {\n type,\n emailAddress,\n emailAttachment,\n sendEmailOptions,\n sendTemplateEmailOptions,\n emailSendResult,\n smtpConfig,\n resendConfig,\n sendgridConfig,\n sesConfig,\n postmarkConfig,\n emailConfig,\n type EmailAddress as ParsEmailAddress,\n type EmailAttachment as ParsEmailAttachment,\n type SendEmailOptions,\n type SendTemplateEmailOptions,\n type EmailSendResult,\n type SmtpConfig,\n type ResendConfig,\n type SendgridConfig,\n type SesConfig,\n type PostmarkConfig,\n type EmailConfig,\n} from \"@parsrun/types\";\n\n/**\n * Email provider type\n */\nexport type EmailProviderType = \"resend\" | \"sendgrid\" | \"postmark\" | \"ses\" | \"console\" | \"mailgun\";\n\n/**\n * Email address with optional name\n */\nexport interface EmailAddress {\n email: string;\n name?: string | undefined;\n}\n\n/**\n * Email attachment\n */\nexport interface EmailAttachment {\n /** File name */\n filename: string;\n /** File content (base64 encoded or Buffer) */\n content: string | Uint8Array;\n /** Content type (MIME type) */\n contentType?: string | undefined;\n /** Content ID for inline attachments */\n contentId?: string | undefined;\n}\n\n/**\n * Email options\n */\nexport interface EmailOptions {\n /** Recipient email address(es) */\n to: string | string[] | EmailAddress | EmailAddress[];\n /** Email subject */\n subject: string;\n /** HTML content */\n html?: string | undefined;\n /** Plain text content */\n text?: string | undefined;\n /** From address (overrides default) */\n from?: string | EmailAddress | undefined;\n /** Reply-to address */\n replyTo?: string | EmailAddress | undefined;\n /** CC recipients */\n cc?: string | string[] | EmailAddress | EmailAddress[] | undefined;\n /** BCC recipients */\n bcc?: string | string[] | EmailAddress | EmailAddress[] | undefined;\n /** Attachments */\n attachments?: EmailAttachment[] | undefined;\n /** Custom headers */\n headers?: Record<string, string> | undefined;\n /** Tags for tracking */\n tags?: Record<string, string> | undefined;\n /** Schedule send time */\n scheduledAt?: Date | undefined;\n}\n\n/**\n * Email send result\n */\nexport interface EmailResult {\n /** Whether send was successful */\n success: boolean;\n /** Message ID from provider */\n messageId?: string | undefined;\n /** Error message if failed */\n error?: string | undefined;\n /** Provider-specific response data */\n data?: unknown;\n}\n\n/**\n * Batch email options\n */\nexport interface BatchEmailOptions {\n /** List of emails to send */\n emails: EmailOptions[];\n /** Whether to stop on first error */\n stopOnError?: boolean | undefined;\n}\n\n/**\n * Batch email result\n */\nexport interface BatchEmailResult {\n /** Total emails attempted */\n total: number;\n /** Successful sends */\n successful: number;\n /** Failed sends */\n failed: number;\n /** Individual results */\n results: EmailResult[];\n}\n\n/**\n * Email provider configuration\n */\nexport interface EmailProviderConfig {\n /** API key for the provider */\n apiKey: string;\n /** Default from email */\n fromEmail: string;\n /** Default from name */\n fromName?: string | undefined;\n /** Provider-specific options */\n options?: Record<string, unknown> | undefined;\n}\n\n/**\n * Email provider interface\n */\nexport interface EmailProvider {\n /** Provider type */\n readonly type: EmailProviderType;\n\n /**\n * Send a single email\n */\n send(options: EmailOptions): Promise<EmailResult>;\n\n /**\n * Send multiple emails\n */\n sendBatch?(options: BatchEmailOptions): Promise<BatchEmailResult>;\n\n /**\n * Verify provider configuration\n */\n verify?(): Promise<boolean>;\n}\n\n/**\n * Email service configuration\n */\nexport interface EmailServiceConfig {\n /** Provider type */\n provider: EmailProviderType;\n /** API key */\n apiKey: string;\n /** Default from email */\n fromEmail: string;\n /** Default from name */\n fromName?: string | undefined;\n /** Enable debug logging */\n debug?: boolean | undefined;\n /** Provider-specific options */\n providerOptions?: Record<string, unknown> | undefined;\n}\n\n/**\n * Template data for email templates\n */\nexport interface TemplateData {\n [key: string]: string | number | boolean | undefined | null | TemplateData | TemplateData[];\n}\n\n/**\n * Email template\n */\nexport interface EmailTemplate {\n /** Template name */\n name: string;\n /** Subject template */\n subject: string;\n /** HTML template */\n html: string;\n /** Plain text template */\n text?: string | undefined;\n}\n\n/**\n * Custom error class for email-related errors.\n *\n * Provides structured error information with error codes for programmatic handling.\n *\n * @example\n * ```typescript\n * try {\n * await provider.send(options);\n * } catch (err) {\n * if (err instanceof EmailError) {\n * console.log(`Error code: ${err.code}`);\n * }\n * }\n * ```\n */\nexport class EmailError extends Error {\n /**\n * Creates a new EmailError instance.\n *\n * @param message - Human-readable error description\n * @param code - Error code from EmailErrorCodes for programmatic handling\n * @param cause - The underlying error that caused this error, if any\n */\n constructor(\n message: string,\n public readonly code: string,\n public readonly cause?: unknown\n ) {\n super(message);\n this.name = \"EmailError\";\n }\n}\n\n/**\n * Common email error codes\n */\nexport const EmailErrorCodes = {\n INVALID_CONFIG: \"INVALID_CONFIG\",\n INVALID_RECIPIENT: \"INVALID_RECIPIENT\",\n INVALID_CONTENT: \"INVALID_CONTENT\",\n SEND_FAILED: \"SEND_FAILED\",\n RATE_LIMITED: \"RATE_LIMITED\",\n PROVIDER_ERROR: \"PROVIDER_ERROR\",\n TEMPLATE_ERROR: \"TEMPLATE_ERROR\",\n ATTACHMENT_ERROR: \"ATTACHMENT_ERROR\",\n} as const;\n"],"mappings":";AAMA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAYK;AA6LA,IAAM,aAAN,cAAyB,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQpC,YACE,SACgB,MACA,OAChB;AACA,UAAM,OAAO;AAHG;AACA;AAGhB,SAAK,OAAO;AAAA,EACd;AACF;AAKO,IAAM,kBAAkB;AAAA,EAC7B,gBAAgB;AAAA,EAChB,mBAAmB;AAAA,EACnB,iBAAiB;AAAA,EACjB,aAAa;AAAA,EACb,cAAc;AAAA,EACd,gBAAgB;AAAA,EAChB,gBAAgB;AAAA,EAChB,kBAAkB;AACpB;","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@parsrun/email",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.31",
|
|
4
4
|
"description": "Edge-compatible email sending for Pars - Resend, SendGrid, Postmark, AWS SES",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -61,8 +61,8 @@
|
|
|
61
61
|
"directory": "packages/email"
|
|
62
62
|
},
|
|
63
63
|
"dependencies": {
|
|
64
|
-
"@parsrun/core": "0.1.
|
|
65
|
-
"@parsrun/types": "0.1.
|
|
64
|
+
"@parsrun/core": "0.1.31",
|
|
65
|
+
"@parsrun/types": "0.1.31"
|
|
66
66
|
},
|
|
67
67
|
"devDependencies": {
|
|
68
68
|
"tsup": "^8.0.0",
|