@parsrun/email 0.1.30 → 0.1.32

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.
@@ -7,93 +7,298 @@ import '@parsrun/types';
7
7
  */
8
8
 
9
9
  /**
10
- * Simple template engine - replaces {{key}} with values
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
- * Base email wrapper with consistent styling
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 ?? `&copy; ${new Date().getFullYear()} ${brandName}. All rights reserved.`}\n </div>\n </div>\n </div>\n</body>\n</html>`;\n}\n\n// ============================================================================\n// OTP Templates\n// ============================================================================\n\nexport interface OTPTemplateData extends TemplateData {\n code: string;\n expiresInMinutes?: number;\n brandName?: string;\n brandColor?: string;\n}\n\nexport const otpTemplate: EmailTemplate = {\n name: \"otp\",\n subject: \"Your verification code: {{code}}\",\n html: `\n<h1>Your verification code</h1>\n<p>Use the following code to verify your identity:</p>\n<div class=\"code-box\">\n <span class=\"code\">{{code}}</span>\n</div>\n<p class=\"text-muted\">This code expires in {{expiresInMinutes}} minutes.</p>\n<p class=\"text-muted\">If you didn't request this code, you can safely ignore this email.</p>\n`,\n text: `Your verification code: {{code}}\n\nThis code expires in {{expiresInMinutes}} minutes.\n\nIf you didn't request this code, you can safely ignore this email.`,\n};\n\nexport function renderOTPEmail(data: OTPTemplateData): { subject: string; html: string; text: string } {\n const templateData = {\n ...data,\n expiresInMinutes: data.expiresInMinutes ?? 10,\n };\n\n return {\n subject: renderTemplate(otpTemplate.subject, templateData),\n html: wrapEmailHtml(renderTemplate(otpTemplate.html, templateData), {\n brandName: data.brandName,\n brandColor: data.brandColor,\n }),\n text: renderTemplate(otpTemplate.text ?? \"\", templateData),\n };\n}\n\n// ============================================================================\n// Magic Link Templates\n// ============================================================================\n\nexport interface MagicLinkTemplateData extends TemplateData {\n url: string;\n expiresInMinutes?: number;\n brandName?: string;\n brandColor?: string;\n}\n\nexport const magicLinkTemplate: EmailTemplate = {\n name: \"magic-link\",\n subject: \"Sign in to {{brandName}}\",\n html: `\n<h1>Sign in to your account</h1>\n<p>Click the button below to securely sign in to your account:</p>\n<div class=\"text-center\">\n <a href=\"{{url}}\" class=\"button\">Sign In</a>\n</div>\n<p class=\"text-muted\">This link expires in {{expiresInMinutes}} minutes.</p>\n<p class=\"text-muted\">If you didn't request this link, you can safely ignore this email.</p>\n<p class=\"text-muted\" style=\"margin-top: 24px; font-size: 12px;\">\n If the button doesn't work, copy and paste this URL into your browser:<br>\n <a href=\"{{url}}\">{{url}}</a>\n</p>\n`,\n text: `Sign in to {{brandName}}\n\nClick this link to sign in:\n{{url}}\n\nThis link expires in {{expiresInMinutes}} minutes.\n\nIf you didn't request this link, you can safely ignore this email.`,\n};\n\nexport function renderMagicLinkEmail(data: MagicLinkTemplateData): { subject: string; html: string; text: string } {\n const templateData = {\n ...data,\n brandName: data.brandName ?? \"Pars\",\n expiresInMinutes: data.expiresInMinutes ?? 15,\n };\n\n return {\n subject: renderTemplate(magicLinkTemplate.subject, templateData),\n html: wrapEmailHtml(renderTemplate(magicLinkTemplate.html, templateData), {\n brandName: templateData.brandName,\n brandColor: data.brandColor,\n }),\n text: renderTemplate(magicLinkTemplate.text ?? \"\", templateData),\n };\n}\n\n// ============================================================================\n// Email Verification Templates\n// ============================================================================\n\nexport interface VerificationTemplateData extends TemplateData {\n url: string;\n name?: string;\n expiresInHours?: number;\n brandName?: string;\n brandColor?: string;\n}\n\nexport const verificationTemplate: EmailTemplate = {\n name: \"verification\",\n subject: \"Verify your email address\",\n html: `\n<h1>Verify your email</h1>\n<p>Hi{{#name}} {{name}}{{/name}},</p>\n<p>Please verify your email address by clicking the button below:</p>\n<div class=\"text-center\">\n <a href=\"{{url}}\" class=\"button\">Verify Email</a>\n</div>\n<p class=\"text-muted\">This link expires in {{expiresInHours}} hours.</p>\n<p class=\"text-muted\" style=\"margin-top: 24px; font-size: 12px;\">\n If the button doesn't work, copy and paste this URL into your browser:<br>\n <a href=\"{{url}}\">{{url}}</a>\n</p>\n`,\n text: `Verify your email address\n\nHi{{#name}} {{name}}{{/name}},\n\nPlease verify your email address by clicking this link:\n{{url}}\n\nThis link expires in {{expiresInHours}} hours.`,\n};\n\nexport function renderVerificationEmail(data: VerificationTemplateData): { subject: string; html: string; text: string } {\n const templateData = {\n ...data,\n expiresInHours: data.expiresInHours ?? 24,\n };\n\n // Handle conditional name\n let html = verificationTemplate.html\n .replace(/\\{\\{#name\\}\\}(.*?)\\{\\{\\/name\\}\\}/gs, data.name ? \"$1\" : \"\");\n let text = (verificationTemplate.text ?? \"\")\n .replace(/\\{\\{#name\\}\\}(.*?)\\{\\{\\/name\\}\\}/gs, data.name ? \"$1\" : \"\");\n\n html = renderTemplate(html, templateData);\n text = renderTemplate(text, templateData);\n\n return {\n subject: renderTemplate(verificationTemplate.subject, templateData),\n html: wrapEmailHtml(html, {\n brandName: data.brandName,\n brandColor: data.brandColor,\n }),\n text,\n };\n}\n\n// ============================================================================\n// Welcome Templates\n// ============================================================================\n\nexport interface WelcomeTemplateData extends TemplateData {\n name?: string;\n loginUrl?: string;\n brandName?: string;\n brandColor?: string;\n}\n\nexport const welcomeTemplate: EmailTemplate = {\n name: \"welcome\",\n subject: \"Welcome to {{brandName}}!\",\n html: `\n<h1>Welcome to {{brandName}}!</h1>\n<p>Hi{{#name}} {{name}}{{/name}},</p>\n<p>Thank you for joining us. We're excited to have you on board!</p>\n<p>Your account is now ready to use.</p>\n{{#loginUrl}}\n<div class=\"text-center\">\n <a href=\"{{loginUrl}}\" class=\"button\">Go to Dashboard</a>\n</div>\n{{/loginUrl}}\n<p>If you have any questions, feel free to reach out to our support team.</p>\n<p>Best regards,<br>The {{brandName}} Team</p>\n`,\n text: `Welcome to {{brandName}}!\n\nHi{{#name}} {{name}}{{/name}},\n\nThank you for joining us. We're excited to have you on board!\n\nYour account is now ready to use.\n\n{{#loginUrl}}Go to your dashboard: {{loginUrl}}{{/loginUrl}}\n\nIf you have any questions, feel free to reach out to our support team.\n\nBest regards,\nThe {{brandName}} Team`,\n};\n\nexport function renderWelcomeEmail(data: WelcomeTemplateData): { subject: string; html: string; text: string } {\n const brandName = data.brandName ?? \"Pars\";\n const templateData = { ...data, brandName };\n\n // Handle conditionals\n let html = welcomeTemplate.html\n .replace(/\\{\\{#name\\}\\}(.*?)\\{\\{\\/name\\}\\}/gs, data.name ? \"$1\" : \"\")\n .replace(/\\{\\{#loginUrl\\}\\}([\\s\\S]*?)\\{\\{\\/loginUrl\\}\\}/g, data.loginUrl ? \"$1\" : \"\");\n\n let text = (welcomeTemplate.text ?? \"\")\n .replace(/\\{\\{#name\\}\\}(.*?)\\{\\{\\/name\\}\\}/gs, data.name ? \"$1\" : \"\")\n .replace(/\\{\\{#loginUrl\\}\\}([\\s\\S]*?)\\{\\{\\/loginUrl\\}\\}/g, data.loginUrl ? \"$1\" : \"\");\n\n html = renderTemplate(html, templateData);\n text = renderTemplate(text, templateData);\n\n return {\n subject: renderTemplate(welcomeTemplate.subject, templateData),\n html: wrapEmailHtml(html, {\n brandName,\n brandColor: data.brandColor,\n }),\n text,\n };\n}\n\n// ============================================================================\n// Password Reset Templates\n// ============================================================================\n\nexport interface PasswordResetTemplateData extends TemplateData {\n url: string;\n expiresInMinutes?: number;\n brandName?: string;\n brandColor?: string;\n}\n\nexport const passwordResetTemplate: EmailTemplate = {\n name: \"password-reset\",\n subject: \"Reset your password\",\n html: `\n<h1>Reset your password</h1>\n<p>We received a request to reset your password. Click the button below to choose a new password:</p>\n<div class=\"text-center\">\n <a href=\"{{url}}\" class=\"button\">Reset Password</a>\n</div>\n<p class=\"text-muted\">This link expires in {{expiresInMinutes}} minutes.</p>\n<p class=\"text-muted\">If you didn't request a password reset, you can safely ignore this email. Your password will remain unchanged.</p>\n<p class=\"text-muted\" style=\"margin-top: 24px; font-size: 12px;\">\n If the button doesn't work, copy and paste this URL into your browser:<br>\n <a href=\"{{url}}\">{{url}}</a>\n</p>\n`,\n text: `Reset your password\n\nWe received a request to reset your password. Click this link to choose a new password:\n{{url}}\n\nThis link expires in {{expiresInMinutes}} minutes.\n\nIf you didn't request a password reset, you can safely ignore this email. Your password will remain unchanged.`,\n};\n\nexport function renderPasswordResetEmail(data: PasswordResetTemplateData): { subject: string; html: string; text: string } {\n const templateData = {\n ...data,\n expiresInMinutes: data.expiresInMinutes ?? 60,\n };\n\n return {\n subject: renderTemplate(passwordResetTemplate.subject, templateData),\n html: wrapEmailHtml(renderTemplate(passwordResetTemplate.html, templateData), {\n brandName: data.brandName,\n brandColor: data.brandColor,\n }),\n text: renderTemplate(passwordResetTemplate.text ?? \"\", templateData),\n };\n}\n\n// ============================================================================\n// Invitation Templates\n// ============================================================================\n\nexport interface InvitationTemplateData extends TemplateData {\n url: string;\n inviterName?: string;\n organizationName?: string;\n role?: string;\n expiresInDays?: number;\n brandName?: string;\n brandColor?: string;\n}\n\nexport const invitationTemplate: EmailTemplate = {\n name: \"invitation\",\n subject: \"{{#inviterName}}{{inviterName}} invited you to join {{/inviterName}}{{organizationName}}\",\n html: `\n<h1>You're invited!</h1>\n{{#inviterName}}\n<p><strong>{{inviterName}}</strong> has invited you to join <strong>{{organizationName}}</strong>{{#role}} as a {{role}}{{/role}}.</p>\n{{/inviterName}}\n{{^inviterName}}\n<p>You've been invited to join <strong>{{organizationName}}</strong>{{#role}} as a {{role}}{{/role}}.</p>\n{{/inviterName}}\n<div class=\"text-center\">\n <a href=\"{{url}}\" class=\"button\">Accept Invitation</a>\n</div>\n<p class=\"text-muted\">This invitation expires in {{expiresInDays}} days.</p>\n<p class=\"text-muted\" style=\"margin-top: 24px; font-size: 12px;\">\n If the button doesn't work, copy and paste this URL into your browser:<br>\n <a href=\"{{url}}\">{{url}}</a>\n</p>\n`,\n text: `You're invited to join {{organizationName}}!\n\n{{#inviterName}}{{inviterName}} has invited you to join {{organizationName}}{{#role}} as a {{role}}{{/role}}.{{/inviterName}}\n{{^inviterName}}You've been invited to join {{organizationName}}{{#role}} as a {{role}}{{/role}}.{{/inviterName}}\n\nAccept the invitation:\n{{url}}\n\nThis invitation expires in {{expiresInDays}} days.`,\n};\n\nexport function renderInvitationEmail(data: InvitationTemplateData): { subject: string; html: string; text: string } {\n const templateData = {\n ...data,\n organizationName: data.organizationName ?? \"the team\",\n expiresInDays: data.expiresInDays ?? 7,\n };\n\n // Handle conditionals (mustache-like syntax)\n let html = invitationTemplate.html\n .replace(/\\{\\{#inviterName\\}\\}([\\s\\S]*?)\\{\\{\\/inviterName\\}\\}/g, data.inviterName ? \"$1\" : \"\")\n .replace(/\\{\\{\\^inviterName\\}\\}([\\s\\S]*?)\\{\\{\\/inviterName\\}\\}/g, data.inviterName ? \"\" : \"$1\")\n .replace(/\\{\\{#role\\}\\}([\\s\\S]*?)\\{\\{\\/role\\}\\}/g, data.role ? \"$1\" : \"\");\n\n let text = (invitationTemplate.text ?? \"\")\n .replace(/\\{\\{#inviterName\\}\\}([\\s\\S]*?)\\{\\{\\/inviterName\\}\\}/g, data.inviterName ? \"$1\" : \"\")\n .replace(/\\{\\{\\^inviterName\\}\\}([\\s\\S]*?)\\{\\{\\/inviterName\\}\\}/g, data.inviterName ? \"\" : \"$1\")\n .replace(/\\{\\{#role\\}\\}([\\s\\S]*?)\\{\\{\\/role\\}\\}/g, data.role ? \"$1\" : \"\");\n\n let subject = invitationTemplate.subject\n .replace(/\\{\\{#inviterName\\}\\}([\\s\\S]*?)\\{\\{\\/inviterName\\}\\}/g, data.inviterName ? \"$1\" : \"You're invited to join \");\n\n html = renderTemplate(html, templateData);\n text = renderTemplate(text, templateData);\n subject = renderTemplate(subject, templateData);\n\n return {\n subject,\n html: wrapEmailHtml(html, {\n brandName: data.brandName,\n brandColor: data.brandColor,\n }),\n text,\n };\n}\n\n// ============================================================================\n// Export all templates\n// ============================================================================\n\nexport const templates = {\n otp: otpTemplate,\n magicLink: magicLinkTemplate,\n verification: verificationTemplate,\n welcome: welcomeTemplate,\n passwordReset: passwordResetTemplate,\n invitation: invitationTemplate,\n} as const;\n\nexport const renderFunctions = {\n otp: renderOTPEmail,\n magicLink: renderMagicLinkEmail,\n verification: renderVerificationEmail,\n welcome: renderWelcomeEmail,\n passwordReset: renderPasswordResetEmail,\n invitation: renderInvitationEmail,\n} as const;\n"],"mappings":"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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 ?? `&copy; ${new Date().getFullYear()} ${brandName}. All rights reserved.`}\n </div>\n </div>\n </div>\n</body>\n</html>`;\n}\n\n// ============================================================================\n// OTP Templates\n// ============================================================================\n\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
- * Email error
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 * Email error\n */\nexport class EmailError extends Error {\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;AAgLA,IAAM,aAAN,cAAyB,MAAM;AAAA,EACpC,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":[]}
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.30",
3
+ "version": "0.1.32",
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.30",
65
- "@parsrun/types": "0.1.30"
64
+ "@parsrun/core": "0.1.32",
65
+ "@parsrun/types": "0.1.32"
66
66
  },
67
67
  "devDependencies": {
68
68
  "tsup": "^8.0.0",