@parsrun/email 0.1.30 → 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.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/types.ts","../src/providers/resend.ts","../src/providers/sendgrid.ts","../src/providers/postmark.ts","../src/providers/console.ts","../src/templates/index.ts","../src/index.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","/**\n * @parsrun/email - Resend Provider\n * Edge-compatible Resend email provider\n */\n\nimport type {\n BatchEmailOptions,\n BatchEmailResult,\n EmailAddress,\n EmailOptions,\n EmailProvider,\n EmailProviderConfig,\n EmailResult,\n} from \"../types.js\";\nimport { EmailError, EmailErrorCodes } from \"../types.js\";\n\n/**\n * Resend Email Provider\n * Uses fetch API for edge compatibility\n *\n * @example\n * ```typescript\n * const resend = new ResendProvider({\n * apiKey: process.env.RESEND_API_KEY,\n * fromEmail: 'hello@example.com',\n * fromName: 'My App',\n * });\n *\n * await resend.send({\n * to: 'user@example.com',\n * subject: 'Hello',\n * html: '<p>Hello World!</p>',\n * });\n * ```\n */\nexport class ResendProvider implements EmailProvider {\n readonly type = \"resend\" as const;\n\n private apiKey: string;\n private fromEmail: string;\n private fromName: string | undefined;\n private baseUrl = \"https://api.resend.com\";\n\n constructor(config: EmailProviderConfig) {\n this.apiKey = config.apiKey;\n this.fromEmail = config.fromEmail;\n this.fromName = config.fromName;\n }\n\n private formatAddress(address: string | EmailAddress): string {\n if (typeof address === \"string\") {\n return address;\n }\n if (address.name) {\n return `${address.name} <${address.email}>`;\n }\n return address.email;\n }\n\n private formatAddresses(\n addresses: string | string[] | EmailAddress | EmailAddress[]\n ): string[] {\n if (Array.isArray(addresses)) {\n return addresses.map((a) => this.formatAddress(a));\n }\n return [this.formatAddress(addresses)];\n }\n\n async send(options: EmailOptions): Promise<EmailResult> {\n const from = options.from\n ? this.formatAddress(options.from)\n : this.fromName\n ? `${this.fromName} <${this.fromEmail}>`\n : this.fromEmail;\n\n const payload: Record<string, unknown> = {\n from,\n to: this.formatAddresses(options.to),\n subject: options.subject,\n };\n\n if (options.html) payload[\"html\"] = options.html;\n if (options.text) payload[\"text\"] = options.text;\n if (options.replyTo) payload[\"reply_to\"] = this.formatAddress(options.replyTo);\n if (options.cc) payload[\"cc\"] = this.formatAddresses(options.cc);\n if (options.bcc) payload[\"bcc\"] = this.formatAddresses(options.bcc);\n if (options.headers) payload[\"headers\"] = options.headers;\n if (options.tags) payload[\"tags\"] = Object.entries(options.tags).map(([name, value]) => ({ name, value }));\n if (options.scheduledAt) payload[\"scheduled_at\"] = options.scheduledAt.toISOString();\n\n if (options.attachments && options.attachments.length > 0) {\n payload[\"attachments\"] = options.attachments.map((att) => ({\n filename: att.filename,\n content: typeof att.content === \"string\"\n ? att.content\n : this.uint8ArrayToBase64(att.content),\n content_type: att.contentType,\n }));\n }\n\n try {\n const response = await fetch(`${this.baseUrl}/emails`, {\n method: \"POST\",\n headers: {\n \"Authorization\": `Bearer ${this.apiKey}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(payload),\n });\n\n const data = await response.json() as { id?: string; message?: string; statusCode?: number };\n\n if (!response.ok) {\n return {\n success: false,\n error: data.message || `HTTP ${response.status}`,\n data,\n };\n }\n\n return {\n success: true,\n messageId: data.id,\n data,\n };\n } catch (err) {\n throw new EmailError(\n `Resend send failed: ${err instanceof Error ? err.message : \"Unknown error\"}`,\n EmailErrorCodes.SEND_FAILED,\n err\n );\n }\n }\n\n async sendBatch(options: BatchEmailOptions): Promise<BatchEmailResult> {\n const results: EmailResult[] = [];\n let successful = 0;\n let failed = 0;\n\n for (const email of options.emails) {\n try {\n const result = await this.send(email);\n results.push(result);\n\n if (result.success) {\n successful++;\n } else {\n failed++;\n if (options.stopOnError) break;\n }\n } catch (err) {\n failed++;\n results.push({\n success: false,\n error: err instanceof Error ? err.message : \"Unknown error\",\n });\n\n if (options.stopOnError) break;\n }\n }\n\n return {\n total: options.emails.length,\n successful,\n failed,\n results,\n };\n }\n\n async verify(): Promise<boolean> {\n try {\n const response = await fetch(`${this.baseUrl}/domains`, {\n headers: {\n \"Authorization\": `Bearer ${this.apiKey}`,\n },\n });\n\n return response.ok;\n } catch {\n return false;\n }\n }\n\n private uint8ArrayToBase64(data: Uint8Array): string {\n // Edge-compatible base64 encoding\n let binary = \"\";\n for (let i = 0; i < data.length; i++) {\n const byte = data[i];\n if (byte !== undefined) {\n binary += String.fromCharCode(byte);\n }\n }\n return btoa(binary);\n }\n}\n\n/**\n * Create a Resend provider\n */\nexport function createResendProvider(config: EmailProviderConfig): ResendProvider {\n return new ResendProvider(config);\n}\n","/**\n * @parsrun/email - SendGrid Provider\n * Edge-compatible SendGrid email provider\n */\n\nimport type {\n BatchEmailOptions,\n BatchEmailResult,\n EmailAddress,\n EmailOptions,\n EmailProvider,\n EmailProviderConfig,\n EmailResult,\n} from \"../types.js\";\nimport { EmailError, EmailErrorCodes } from \"../types.js\";\n\n/**\n * SendGrid Email Provider\n * Uses fetch API for edge compatibility\n *\n * @example\n * ```typescript\n * const sendgrid = new SendGridProvider({\n * apiKey: process.env.SENDGRID_API_KEY,\n * fromEmail: 'hello@example.com',\n * fromName: 'My App',\n * });\n *\n * await sendgrid.send({\n * to: 'user@example.com',\n * subject: 'Hello',\n * html: '<p>Hello World!</p>',\n * });\n * ```\n */\nexport class SendGridProvider implements EmailProvider {\n readonly type = \"sendgrid\" as const;\n\n private apiKey: string;\n private fromEmail: string;\n private fromName: string | undefined;\n private baseUrl = \"https://api.sendgrid.com/v3\";\n\n constructor(config: EmailProviderConfig) {\n this.apiKey = config.apiKey;\n this.fromEmail = config.fromEmail;\n this.fromName = config.fromName;\n }\n\n private formatAddress(address: string | EmailAddress): { email: string; name?: string | undefined } {\n if (typeof address === \"string\") {\n return { email: address };\n }\n return address.name ? { email: address.email, name: address.name } : { email: address.email };\n }\n\n private formatAddresses(\n addresses: string | string[] | EmailAddress | EmailAddress[]\n ): Array<{ email: string; name?: string | undefined }> {\n if (Array.isArray(addresses)) {\n return addresses.map((a) => this.formatAddress(a));\n }\n return [this.formatAddress(addresses)];\n }\n\n async send(options: EmailOptions): Promise<EmailResult> {\n const from = options.from\n ? this.formatAddress(options.from)\n : this.fromName\n ? { email: this.fromEmail, name: this.fromName }\n : { email: this.fromEmail };\n\n const personalization: {\n to: Array<{ email: string; name?: string | undefined }>;\n cc?: Array<{ email: string; name?: string | undefined }> | undefined;\n bcc?: Array<{ email: string; name?: string | undefined }> | undefined;\n headers?: Record<string, string> | undefined;\n } = {\n to: this.formatAddresses(options.to),\n };\n\n if (options.cc) {\n personalization.cc = this.formatAddresses(options.cc);\n }\n if (options.bcc) {\n personalization.bcc = this.formatAddresses(options.bcc);\n }\n if (options.headers) {\n personalization.headers = options.headers;\n }\n\n const payload: Record<string, unknown> = {\n personalizations: [personalization],\n from,\n subject: options.subject,\n content: [],\n };\n\n const content: Array<{ type: string; value: string }> = [];\n if (options.text) {\n content.push({ type: \"text/plain\", value: options.text });\n }\n if (options.html) {\n content.push({ type: \"text/html\", value: options.html });\n }\n payload[\"content\"] = content;\n\n if (options.replyTo) {\n payload[\"reply_to\"] = this.formatAddress(options.replyTo);\n }\n\n if (options.attachments && options.attachments.length > 0) {\n payload[\"attachments\"] = options.attachments.map((att) => ({\n filename: att.filename,\n content: typeof att.content === \"string\"\n ? att.content\n : this.uint8ArrayToBase64(att.content),\n type: att.contentType,\n content_id: att.contentId,\n disposition: att.contentId ? \"inline\" : \"attachment\",\n }));\n }\n\n if (options.scheduledAt) {\n payload[\"send_at\"] = Math.floor(options.scheduledAt.getTime() / 1000);\n }\n\n try {\n const response = await fetch(`${this.baseUrl}/mail/send`, {\n method: \"POST\",\n headers: {\n \"Authorization\": `Bearer ${this.apiKey}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(payload),\n });\n\n // SendGrid returns 202 for success with no body\n if (response.status === 202) {\n const messageId = response.headers.get(\"x-message-id\");\n return {\n success: true,\n messageId: messageId ?? undefined,\n };\n }\n\n const data = await response.json().catch(() => ({})) as { errors?: Array<{ message: string }> };\n const errorMessage = data.errors?.[0]?.message || `HTTP ${response.status}`;\n\n return {\n success: false,\n error: errorMessage,\n data,\n };\n } catch (err) {\n throw new EmailError(\n `SendGrid send failed: ${err instanceof Error ? err.message : \"Unknown error\"}`,\n EmailErrorCodes.SEND_FAILED,\n err\n );\n }\n }\n\n async sendBatch(options: BatchEmailOptions): Promise<BatchEmailResult> {\n const results: EmailResult[] = [];\n let successful = 0;\n let failed = 0;\n\n for (const email of options.emails) {\n try {\n const result = await this.send(email);\n results.push(result);\n\n if (result.success) {\n successful++;\n } else {\n failed++;\n if (options.stopOnError) break;\n }\n } catch (err) {\n failed++;\n results.push({\n success: false,\n error: err instanceof Error ? err.message : \"Unknown error\",\n });\n\n if (options.stopOnError) break;\n }\n }\n\n return {\n total: options.emails.length,\n successful,\n failed,\n results,\n };\n }\n\n async verify(): Promise<boolean> {\n try {\n const response = await fetch(`${this.baseUrl}/scopes`, {\n headers: {\n \"Authorization\": `Bearer ${this.apiKey}`,\n },\n });\n\n return response.ok;\n } catch {\n return false;\n }\n }\n\n private uint8ArrayToBase64(data: Uint8Array): string {\n let binary = \"\";\n for (let i = 0; i < data.length; i++) {\n const byte = data[i];\n if (byte !== undefined) {\n binary += String.fromCharCode(byte);\n }\n }\n return btoa(binary);\n }\n}\n\n/**\n * Create a SendGrid provider\n */\nexport function createSendGridProvider(config: EmailProviderConfig): SendGridProvider {\n return new SendGridProvider(config);\n}\n","/**\n * @parsrun/email - Postmark Provider\n * Edge-compatible Postmark email provider\n */\n\nimport type {\n BatchEmailOptions,\n BatchEmailResult,\n EmailAddress,\n EmailOptions,\n EmailProvider,\n EmailProviderConfig,\n EmailResult,\n} from \"../types.js\";\nimport { EmailError, EmailErrorCodes } from \"../types.js\";\n\n/**\n * Postmark Email Provider\n * Uses fetch API for edge compatibility\n *\n * @example\n * ```typescript\n * const postmark = new PostmarkProvider({\n * apiKey: process.env.POSTMARK_API_KEY,\n * fromEmail: 'hello@example.com',\n * fromName: 'My App',\n * });\n *\n * await postmark.send({\n * to: 'user@example.com',\n * subject: 'Hello',\n * html: '<p>Hello World!</p>',\n * });\n * ```\n */\nexport class PostmarkProvider implements EmailProvider {\n readonly type = \"postmark\" as const;\n\n private apiKey: string;\n private fromEmail: string;\n private fromName: string | undefined;\n private baseUrl = \"https://api.postmarkapp.com\";\n\n constructor(config: EmailProviderConfig) {\n this.apiKey = config.apiKey;\n this.fromEmail = config.fromEmail;\n this.fromName = config.fromName;\n }\n\n private formatAddress(address: string | EmailAddress): string {\n if (typeof address === \"string\") {\n return address;\n }\n if (address.name) {\n return `${address.name} <${address.email}>`;\n }\n return address.email;\n }\n\n private formatAddresses(\n addresses: string | string[] | EmailAddress | EmailAddress[]\n ): string {\n if (Array.isArray(addresses)) {\n return addresses.map((a) => this.formatAddress(a)).join(\",\");\n }\n return this.formatAddress(addresses);\n }\n\n async send(options: EmailOptions): Promise<EmailResult> {\n const from = options.from\n ? this.formatAddress(options.from)\n : this.fromName\n ? `${this.fromName} <${this.fromEmail}>`\n : this.fromEmail;\n\n const payload: Record<string, unknown> = {\n From: from,\n To: this.formatAddresses(options.to),\n Subject: options.subject,\n };\n\n if (options.html) payload[\"HtmlBody\"] = options.html;\n if (options.text) payload[\"TextBody\"] = options.text;\n if (options.replyTo) payload[\"ReplyTo\"] = this.formatAddress(options.replyTo);\n if (options.cc) payload[\"Cc\"] = this.formatAddresses(options.cc);\n if (options.bcc) payload[\"Bcc\"] = this.formatAddresses(options.bcc);\n\n if (options.headers) {\n payload[\"Headers\"] = Object.entries(options.headers).map(([Name, Value]) => ({ Name, Value }));\n }\n\n if (options.tags) {\n // Postmark uses Tag field (single tag) and Metadata for custom data\n const tagEntries = Object.entries(options.tags);\n if (tagEntries.length > 0 && tagEntries[0]) {\n payload[\"Tag\"] = tagEntries[0][1];\n }\n payload[\"Metadata\"] = options.tags;\n }\n\n if (options.attachments && options.attachments.length > 0) {\n payload[\"Attachments\"] = options.attachments.map((att) => ({\n Name: att.filename,\n Content: typeof att.content === \"string\"\n ? att.content\n : this.uint8ArrayToBase64(att.content),\n ContentType: att.contentType || \"application/octet-stream\",\n ContentID: att.contentId,\n }));\n }\n\n try {\n const response = await fetch(`${this.baseUrl}/email`, {\n method: \"POST\",\n headers: {\n \"X-Postmark-Server-Token\": this.apiKey,\n \"Content-Type\": \"application/json\",\n \"Accept\": \"application/json\",\n },\n body: JSON.stringify(payload),\n });\n\n const data = await response.json() as {\n MessageID?: string;\n ErrorCode?: number;\n Message?: string;\n };\n\n if (!response.ok || data.ErrorCode) {\n return {\n success: false,\n error: data.Message || `HTTP ${response.status}`,\n data,\n };\n }\n\n return {\n success: true,\n messageId: data.MessageID,\n data,\n };\n } catch (err) {\n throw new EmailError(\n `Postmark send failed: ${err instanceof Error ? err.message : \"Unknown error\"}`,\n EmailErrorCodes.SEND_FAILED,\n err\n );\n }\n }\n\n async sendBatch(options: BatchEmailOptions): Promise<BatchEmailResult> {\n // Postmark supports batch sending up to 500 emails\n const batchPayload = options.emails.map((email) => {\n const from = email.from\n ? this.formatAddress(email.from)\n : this.fromName\n ? `${this.fromName} <${this.fromEmail}>`\n : this.fromEmail;\n\n const item: Record<string, unknown> = {\n From: from,\n To: this.formatAddresses(email.to),\n Subject: email.subject,\n };\n\n if (email.html) item[\"HtmlBody\"] = email.html;\n if (email.text) item[\"TextBody\"] = email.text;\n if (email.replyTo) item[\"ReplyTo\"] = this.formatAddress(email.replyTo);\n if (email.cc) item[\"Cc\"] = this.formatAddresses(email.cc);\n if (email.bcc) item[\"Bcc\"] = this.formatAddresses(email.bcc);\n\n return item;\n });\n\n try {\n const response = await fetch(`${this.baseUrl}/email/batch`, {\n method: \"POST\",\n headers: {\n \"X-Postmark-Server-Token\": this.apiKey,\n \"Content-Type\": \"application/json\",\n \"Accept\": \"application/json\",\n },\n body: JSON.stringify(batchPayload),\n });\n\n const data = await response.json() as Array<{\n MessageID?: string;\n ErrorCode?: number;\n Message?: string;\n }>;\n\n const results: EmailResult[] = data.map((item) => ({\n success: !item.ErrorCode,\n messageId: item.MessageID,\n error: item.ErrorCode ? item.Message : undefined,\n data: item,\n }));\n\n const successful = results.filter((r) => r.success).length;\n const failed = results.filter((r) => !r.success).length;\n\n return {\n total: options.emails.length,\n successful,\n failed,\n results,\n };\n } catch (err) {\n throw new EmailError(\n `Postmark batch send failed: ${err instanceof Error ? err.message : \"Unknown error\"}`,\n EmailErrorCodes.SEND_FAILED,\n err\n );\n }\n }\n\n async verify(): Promise<boolean> {\n try {\n const response = await fetch(`${this.baseUrl}/server`, {\n headers: {\n \"X-Postmark-Server-Token\": this.apiKey,\n \"Accept\": \"application/json\",\n },\n });\n\n return response.ok;\n } catch {\n return false;\n }\n }\n\n private uint8ArrayToBase64(data: Uint8Array): string {\n let binary = \"\";\n for (let i = 0; i < data.length; i++) {\n const byte = data[i];\n if (byte !== undefined) {\n binary += String.fromCharCode(byte);\n }\n }\n return btoa(binary);\n }\n}\n\n/**\n * Create a Postmark provider\n */\nexport function createPostmarkProvider(config: EmailProviderConfig): PostmarkProvider {\n return new PostmarkProvider(config);\n}\n","/**\n * @parsrun/email - Console Provider\n * Development/testing email provider that logs to console\n */\n\nimport type {\n BatchEmailOptions,\n BatchEmailResult,\n EmailAddress,\n EmailOptions,\n EmailProvider,\n EmailProviderConfig,\n EmailResult,\n} from \"../types.js\";\n\n/**\n * Console Email Provider\n * Logs emails to console instead of sending them.\n * Useful for development and testing.\n *\n * @example\n * ```typescript\n * const console = new ConsoleProvider({\n * apiKey: 'not-needed',\n * fromEmail: 'test@example.com',\n * fromName: 'Test App',\n * });\n *\n * await console.send({\n * to: 'user@example.com',\n * subject: 'Hello',\n * html: '<p>Hello World!</p>',\n * });\n * // Logs email details to console\n * ```\n */\nexport class ConsoleProvider implements EmailProvider {\n readonly type = \"console\" as const;\n\n private fromEmail: string;\n private fromName: string | undefined;\n private messageCounter = 0;\n\n constructor(config: EmailProviderConfig) {\n this.fromEmail = config.fromEmail;\n this.fromName = config.fromName;\n }\n\n private formatAddress(address: string | EmailAddress): string {\n if (typeof address === \"string\") {\n return address;\n }\n if (address.name) {\n return `${address.name} <${address.email}>`;\n }\n return address.email;\n }\n\n private formatAddresses(\n addresses: string | string[] | EmailAddress | EmailAddress[]\n ): string {\n if (Array.isArray(addresses)) {\n return addresses.map((a) => this.formatAddress(a)).join(\", \");\n }\n return this.formatAddress(addresses);\n }\n\n async send(options: EmailOptions): Promise<EmailResult> {\n this.messageCounter++;\n const messageId = `console-${Date.now()}-${this.messageCounter}`;\n\n const from = options.from\n ? this.formatAddress(options.from)\n : this.fromName\n ? `${this.fromName} <${this.fromEmail}>`\n : this.fromEmail;\n\n const separator = \"─\".repeat(60);\n\n console.log(`\\n${separator}`);\n console.log(\"šŸ“§ EMAIL (Console Provider)\");\n console.log(separator);\n console.log(`Message ID: ${messageId}`);\n console.log(`From: ${from}`);\n console.log(`To: ${this.formatAddresses(options.to)}`);\n if (options.cc) {\n console.log(`CC: ${this.formatAddresses(options.cc)}`);\n }\n if (options.bcc) {\n console.log(`BCC: ${this.formatAddresses(options.bcc)}`);\n }\n if (options.replyTo) {\n console.log(`Reply-To: ${this.formatAddress(options.replyTo)}`);\n }\n console.log(`Subject: ${options.subject}`);\n\n if (options.headers) {\n console.log(`Headers: ${JSON.stringify(options.headers)}`);\n }\n if (options.tags) {\n console.log(`Tags: ${JSON.stringify(options.tags)}`);\n }\n if (options.scheduledAt) {\n console.log(`Scheduled: ${options.scheduledAt.toISOString()}`);\n }\n if (options.attachments && options.attachments.length > 0) {\n console.log(`Attachments:`);\n for (const att of options.attachments) {\n const size = typeof att.content === \"string\"\n ? att.content.length\n : att.content.length;\n console.log(` - ${att.filename} (${att.contentType || \"unknown\"}, ${size} bytes)`);\n }\n }\n\n console.log(separator);\n if (options.text) {\n console.log(\"TEXT CONTENT:\");\n console.log(options.text);\n }\n if (options.html) {\n console.log(\"HTML CONTENT:\");\n console.log(options.html);\n }\n console.log(`${separator}\\n`);\n\n return {\n success: true,\n messageId,\n };\n }\n\n async sendBatch(options: BatchEmailOptions): Promise<BatchEmailResult> {\n const results: EmailResult[] = [];\n let successful = 0;\n let failed = 0;\n\n console.log(`\\nšŸ“¬ BATCH EMAIL (${options.emails.length} emails)`);\n\n for (const email of options.emails) {\n try {\n const result = await this.send(email);\n results.push(result);\n\n if (result.success) {\n successful++;\n } else {\n failed++;\n if (options.stopOnError) break;\n }\n } catch (err) {\n failed++;\n results.push({\n success: false,\n error: err instanceof Error ? err.message : \"Unknown error\",\n });\n\n if (options.stopOnError) break;\n }\n }\n\n console.log(`šŸ“¬ BATCH COMPLETE: ${successful} sent, ${failed} failed\\n`);\n\n return {\n total: options.emails.length,\n successful,\n failed,\n results,\n };\n }\n\n async verify(): Promise<boolean> {\n console.log(\"šŸ“§ Console email provider verified (always returns true)\");\n return true;\n }\n}\n\n/**\n * Create a Console provider\n */\nexport function createConsoleProvider(config: EmailProviderConfig): ConsoleProvider {\n return new ConsoleProvider(config);\n}\n","/**\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","/**\n * @module\n * Edge-compatible email sending for Pars.\n *\n * Supports multiple providers:\n * - Resend (recommended)\n * - SendGrid\n * - Postmark\n * - Console (development)\n *\n * @example\n * ```typescript\n * import { createEmailService } from '@parsrun/email';\n *\n * const email = createEmailService({\n * provider: 'resend',\n * apiKey: process.env.RESEND_API_KEY,\n * fromEmail: 'hello@example.com',\n * fromName: 'My App',\n * });\n *\n * await email.send({\n * to: 'user@example.com',\n * subject: 'Hello',\n * html: '<p>Hello World!</p>',\n * });\n * ```\n */\n\n// Re-export types\nexport * from \"./types.js\";\n\n// Re-export providers\nexport { ResendProvider, createResendProvider } from \"./providers/resend.js\";\nexport { SendGridProvider, createSendGridProvider } from \"./providers/sendgrid.js\";\nexport { PostmarkProvider, createPostmarkProvider } from \"./providers/postmark.js\";\nexport { ConsoleProvider, createConsoleProvider } from \"./providers/console.js\";\n\n// Re-export templates\nexport * from \"./templates/index.js\";\n\nimport type {\n BatchEmailOptions,\n BatchEmailResult,\n EmailOptions,\n EmailProvider,\n EmailProviderConfig,\n EmailProviderType,\n EmailResult,\n EmailServiceConfig,\n} from \"./types.js\";\nimport { EmailError, EmailErrorCodes } from \"./types.js\";\nimport { ResendProvider } from \"./providers/resend.js\";\nimport { SendGridProvider } from \"./providers/sendgrid.js\";\nimport { PostmarkProvider } from \"./providers/postmark.js\";\nimport { ConsoleProvider } from \"./providers/console.js\";\n\n/**\n * Email Service\n * High-level email service with provider abstraction\n */\nexport class EmailService {\n private provider: EmailProvider;\n private debug: boolean;\n\n constructor(config: EmailServiceConfig) {\n this.debug = config.debug ?? false;\n this.provider = this.createProvider(config);\n }\n\n private createProvider(config: EmailServiceConfig): EmailProvider {\n const providerConfig: EmailProviderConfig = {\n apiKey: config.apiKey,\n fromEmail: config.fromEmail,\n fromName: config.fromName,\n options: config.providerOptions,\n };\n\n switch (config.provider) {\n case \"resend\":\n return new ResendProvider(providerConfig);\n case \"sendgrid\":\n return new SendGridProvider(providerConfig);\n case \"postmark\":\n return new PostmarkProvider(providerConfig);\n case \"console\":\n return new ConsoleProvider(providerConfig);\n default:\n throw new EmailError(\n `Unknown email provider: ${config.provider}`,\n EmailErrorCodes.INVALID_CONFIG\n );\n }\n }\n\n /**\n * Get the provider type\n */\n get providerType(): EmailProviderType {\n return this.provider.type;\n }\n\n /**\n * Send a single email\n */\n async send(options: EmailOptions): Promise<EmailResult> {\n if (this.debug) {\n console.log(\"[Email] Sending email:\", {\n to: options.to,\n subject: options.subject,\n provider: this.provider.type,\n });\n }\n\n const result = await this.provider.send(options);\n\n if (this.debug) {\n console.log(\"[Email] Result:\", result);\n }\n\n return result;\n }\n\n /**\n * Send multiple emails\n */\n async sendBatch(options: BatchEmailOptions): Promise<BatchEmailResult> {\n if (this.debug) {\n console.log(\"[Email] Sending batch:\", {\n count: options.emails.length,\n provider: this.provider.type,\n });\n }\n\n // Use provider's native batch if available\n if (this.provider.sendBatch) {\n const result = await this.provider.sendBatch(options);\n\n if (this.debug) {\n console.log(\"[Email] Batch result:\", {\n total: result.total,\n successful: result.successful,\n failed: result.failed,\n });\n }\n\n return result;\n }\n\n // Fallback to sequential sending\n const results: EmailResult[] = [];\n let successful = 0;\n let failed = 0;\n\n for (const email of options.emails) {\n try {\n const result = await this.send(email);\n results.push(result);\n\n if (result.success) {\n successful++;\n } else {\n failed++;\n if (options.stopOnError) break;\n }\n } catch (err) {\n failed++;\n results.push({\n success: false,\n error: err instanceof Error ? err.message : \"Unknown error\",\n });\n\n if (options.stopOnError) break;\n }\n }\n\n return {\n total: options.emails.length,\n successful,\n failed,\n results,\n };\n }\n\n /**\n * Verify provider configuration\n */\n async verify(): Promise<boolean> {\n if (this.provider.verify) {\n return this.provider.verify();\n }\n return true;\n }\n}\n\n/**\n * Create an email service\n *\n * @example\n * ```typescript\n * // With Resend\n * const email = createEmailService({\n * provider: 'resend',\n * apiKey: process.env.RESEND_API_KEY,\n * fromEmail: 'hello@example.com',\n * });\n *\n * // With SendGrid\n * const email = createEmailService({\n * provider: 'sendgrid',\n * apiKey: process.env.SENDGRID_API_KEY,\n * fromEmail: 'hello@example.com',\n * });\n *\n * // For development\n * const email = createEmailService({\n * provider: 'console',\n * apiKey: 'not-needed',\n * fromEmail: 'test@example.com',\n * });\n * ```\n */\nexport function createEmailService(config: EmailServiceConfig): EmailService {\n return new EmailService(config);\n}\n\n/**\n * Create an email provider directly\n */\nexport function createEmailProvider(\n type: EmailProviderType,\n config: EmailProviderConfig\n): EmailProvider {\n switch (type) {\n case \"resend\":\n return new ResendProvider(config);\n case \"sendgrid\":\n return new SendGridProvider(config);\n case \"postmark\":\n return new PostmarkProvider(config);\n case \"console\":\n return new ConsoleProvider(config);\n default:\n throw new EmailError(\n `Unknown email provider: ${type}`,\n EmailErrorCodes.INVALID_CONFIG\n );\n }\n}\n\n// Default export\nexport default {\n EmailService,\n createEmailService,\n createEmailProvider,\n};\n"],"mappings":"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","names":["type"]}
1
+ {"version":3,"sources":["../src/types.ts","../src/providers/resend.ts","../src/providers/sendgrid.ts","../src/providers/postmark.ts","../src/providers/console.ts","../src/templates/index.ts","../src/index.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","/**\n * @parsrun/email - Resend Provider\n * Edge-compatible Resend email provider\n */\n\nimport type {\n BatchEmailOptions,\n BatchEmailResult,\n EmailAddress,\n EmailOptions,\n EmailProvider,\n EmailProviderConfig,\n EmailResult,\n} from \"../types.js\";\nimport { EmailError, EmailErrorCodes } from \"../types.js\";\n\n/**\n * Resend Email Provider\n * Uses fetch API for edge compatibility\n *\n * @example\n * ```typescript\n * const resend = new ResendProvider({\n * apiKey: process.env.RESEND_API_KEY,\n * fromEmail: 'hello@example.com',\n * fromName: 'My App',\n * });\n *\n * await resend.send({\n * to: 'user@example.com',\n * subject: 'Hello',\n * html: '<p>Hello World!</p>',\n * });\n * ```\n */\nexport class ResendProvider implements EmailProvider {\n /** Provider type identifier */\n readonly type = \"resend\" as const;\n\n private apiKey: string;\n private fromEmail: string;\n private fromName: string | undefined;\n private baseUrl = \"https://api.resend.com\";\n\n /**\n * Creates a new ResendProvider instance.\n *\n * @param config - The provider configuration including API key and sender info\n */\n constructor(config: EmailProviderConfig) {\n this.apiKey = config.apiKey;\n this.fromEmail = config.fromEmail;\n this.fromName = config.fromName;\n }\n\n private formatAddress(address: string | EmailAddress): string {\n if (typeof address === \"string\") {\n return address;\n }\n if (address.name) {\n return `${address.name} <${address.email}>`;\n }\n return address.email;\n }\n\n private formatAddresses(\n addresses: string | string[] | EmailAddress | EmailAddress[]\n ): string[] {\n if (Array.isArray(addresses)) {\n return addresses.map((a) => this.formatAddress(a));\n }\n return [this.formatAddress(addresses)];\n }\n\n /**\n * Sends an email via the Resend API.\n *\n * @param options - The email options including recipient, subject, and content\n * @returns A promise that resolves to the send result with message ID if successful\n * @throws {EmailError} If the Resend API request fails\n */\n async send(options: EmailOptions): Promise<EmailResult> {\n const from = options.from\n ? this.formatAddress(options.from)\n : this.fromName\n ? `${this.fromName} <${this.fromEmail}>`\n : this.fromEmail;\n\n const payload: Record<string, unknown> = {\n from,\n to: this.formatAddresses(options.to),\n subject: options.subject,\n };\n\n if (options.html) payload[\"html\"] = options.html;\n if (options.text) payload[\"text\"] = options.text;\n if (options.replyTo) payload[\"reply_to\"] = this.formatAddress(options.replyTo);\n if (options.cc) payload[\"cc\"] = this.formatAddresses(options.cc);\n if (options.bcc) payload[\"bcc\"] = this.formatAddresses(options.bcc);\n if (options.headers) payload[\"headers\"] = options.headers;\n if (options.tags) payload[\"tags\"] = Object.entries(options.tags).map(([name, value]) => ({ name, value }));\n if (options.scheduledAt) payload[\"scheduled_at\"] = options.scheduledAt.toISOString();\n\n if (options.attachments && options.attachments.length > 0) {\n payload[\"attachments\"] = options.attachments.map((att) => ({\n filename: att.filename,\n content: typeof att.content === \"string\"\n ? att.content\n : this.uint8ArrayToBase64(att.content),\n content_type: att.contentType,\n }));\n }\n\n try {\n const response = await fetch(`${this.baseUrl}/emails`, {\n method: \"POST\",\n headers: {\n \"Authorization\": `Bearer ${this.apiKey}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(payload),\n });\n\n const data = await response.json() as { id?: string; message?: string; statusCode?: number };\n\n if (!response.ok) {\n return {\n success: false,\n error: data.message || `HTTP ${response.status}`,\n data,\n };\n }\n\n return {\n success: true,\n messageId: data.id,\n data,\n };\n } catch (err) {\n throw new EmailError(\n `Resend send failed: ${err instanceof Error ? err.message : \"Unknown error\"}`,\n EmailErrorCodes.SEND_FAILED,\n err\n );\n }\n }\n\n /**\n * Sends multiple emails via the Resend API sequentially.\n *\n * @param options - The batch email options containing emails and error handling config\n * @returns A promise that resolves to the batch result with success/failure counts\n */\n async sendBatch(options: BatchEmailOptions): Promise<BatchEmailResult> {\n const results: EmailResult[] = [];\n let successful = 0;\n let failed = 0;\n\n for (const email of options.emails) {\n try {\n const result = await this.send(email);\n results.push(result);\n\n if (result.success) {\n successful++;\n } else {\n failed++;\n if (options.stopOnError) break;\n }\n } catch (err) {\n failed++;\n results.push({\n success: false,\n error: err instanceof Error ? err.message : \"Unknown error\",\n });\n\n if (options.stopOnError) break;\n }\n }\n\n return {\n total: options.emails.length,\n successful,\n failed,\n results,\n };\n }\n\n /**\n * Verifies the Resend API key by checking configured domains.\n *\n * @returns A promise that resolves to true if the API key is valid, false otherwise\n */\n async verify(): Promise<boolean> {\n try {\n const response = await fetch(`${this.baseUrl}/domains`, {\n headers: {\n \"Authorization\": `Bearer ${this.apiKey}`,\n },\n });\n\n return response.ok;\n } catch {\n return false;\n }\n }\n\n /**\n * Converts a Uint8Array to a base64-encoded string.\n *\n * Edge-compatible base64 encoding that works in all JavaScript runtimes.\n *\n * @param data - The binary data to encode\n * @returns The base64-encoded string\n */\n private uint8ArrayToBase64(data: Uint8Array): string {\n let binary = \"\";\n for (let i = 0; i < data.length; i++) {\n const byte = data[i];\n if (byte !== undefined) {\n binary += String.fromCharCode(byte);\n }\n }\n return btoa(binary);\n }\n}\n\n/**\n * Creates a Resend provider instance.\n *\n * @param config - The provider configuration including API key and sender info\n * @returns A new ResendProvider instance\n */\nexport function createResendProvider(config: EmailProviderConfig): ResendProvider {\n return new ResendProvider(config);\n}\n","/**\n * @parsrun/email - SendGrid Provider\n * Edge-compatible SendGrid email provider\n */\n\nimport type {\n BatchEmailOptions,\n BatchEmailResult,\n EmailAddress,\n EmailOptions,\n EmailProvider,\n EmailProviderConfig,\n EmailResult,\n} from \"../types.js\";\nimport { EmailError, EmailErrorCodes } from \"../types.js\";\n\n/**\n * SendGrid Email Provider\n * Uses fetch API for edge compatibility\n *\n * @example\n * ```typescript\n * const sendgrid = new SendGridProvider({\n * apiKey: process.env.SENDGRID_API_KEY,\n * fromEmail: 'hello@example.com',\n * fromName: 'My App',\n * });\n *\n * await sendgrid.send({\n * to: 'user@example.com',\n * subject: 'Hello',\n * html: '<p>Hello World!</p>',\n * });\n * ```\n */\nexport class SendGridProvider implements EmailProvider {\n /** Provider type identifier */\n readonly type = \"sendgrid\" as const;\n\n private apiKey: string;\n private fromEmail: string;\n private fromName: string | undefined;\n private baseUrl = \"https://api.sendgrid.com/v3\";\n\n /**\n * Creates a new SendGridProvider instance.\n *\n * @param config - The provider configuration including API key and sender info\n */\n constructor(config: EmailProviderConfig) {\n this.apiKey = config.apiKey;\n this.fromEmail = config.fromEmail;\n this.fromName = config.fromName;\n }\n\n private formatAddress(address: string | EmailAddress): { email: string; name?: string | undefined } {\n if (typeof address === \"string\") {\n return { email: address };\n }\n return address.name ? { email: address.email, name: address.name } : { email: address.email };\n }\n\n private formatAddresses(\n addresses: string | string[] | EmailAddress | EmailAddress[]\n ): Array<{ email: string; name?: string | undefined }> {\n if (Array.isArray(addresses)) {\n return addresses.map((a) => this.formatAddress(a));\n }\n return [this.formatAddress(addresses)];\n }\n\n /**\n * Sends an email via the SendGrid API.\n *\n * @param options - The email options including recipient, subject, and content\n * @returns A promise that resolves to the send result with message ID if successful\n * @throws {EmailError} If the SendGrid API request fails\n */\n async send(options: EmailOptions): Promise<EmailResult> {\n const from = options.from\n ? this.formatAddress(options.from)\n : this.fromName\n ? { email: this.fromEmail, name: this.fromName }\n : { email: this.fromEmail };\n\n const personalization: {\n to: Array<{ email: string; name?: string | undefined }>;\n cc?: Array<{ email: string; name?: string | undefined }> | undefined;\n bcc?: Array<{ email: string; name?: string | undefined }> | undefined;\n headers?: Record<string, string> | undefined;\n } = {\n to: this.formatAddresses(options.to),\n };\n\n if (options.cc) {\n personalization.cc = this.formatAddresses(options.cc);\n }\n if (options.bcc) {\n personalization.bcc = this.formatAddresses(options.bcc);\n }\n if (options.headers) {\n personalization.headers = options.headers;\n }\n\n const payload: Record<string, unknown> = {\n personalizations: [personalization],\n from,\n subject: options.subject,\n content: [],\n };\n\n const content: Array<{ type: string; value: string }> = [];\n if (options.text) {\n content.push({ type: \"text/plain\", value: options.text });\n }\n if (options.html) {\n content.push({ type: \"text/html\", value: options.html });\n }\n payload[\"content\"] = content;\n\n if (options.replyTo) {\n payload[\"reply_to\"] = this.formatAddress(options.replyTo);\n }\n\n if (options.attachments && options.attachments.length > 0) {\n payload[\"attachments\"] = options.attachments.map((att) => ({\n filename: att.filename,\n content: typeof att.content === \"string\"\n ? att.content\n : this.uint8ArrayToBase64(att.content),\n type: att.contentType,\n content_id: att.contentId,\n disposition: att.contentId ? \"inline\" : \"attachment\",\n }));\n }\n\n if (options.scheduledAt) {\n payload[\"send_at\"] = Math.floor(options.scheduledAt.getTime() / 1000);\n }\n\n try {\n const response = await fetch(`${this.baseUrl}/mail/send`, {\n method: \"POST\",\n headers: {\n \"Authorization\": `Bearer ${this.apiKey}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(payload),\n });\n\n // SendGrid returns 202 for success with no body\n if (response.status === 202) {\n const messageId = response.headers.get(\"x-message-id\");\n return {\n success: true,\n messageId: messageId ?? undefined,\n };\n }\n\n const data = await response.json().catch(() => ({})) as { errors?: Array<{ message: string }> };\n const errorMessage = data.errors?.[0]?.message || `HTTP ${response.status}`;\n\n return {\n success: false,\n error: errorMessage,\n data,\n };\n } catch (err) {\n throw new EmailError(\n `SendGrid send failed: ${err instanceof Error ? err.message : \"Unknown error\"}`,\n EmailErrorCodes.SEND_FAILED,\n err\n );\n }\n }\n\n /**\n * Sends multiple emails via the SendGrid API sequentially.\n *\n * Note: SendGrid does not support true batch sending via the standard API,\n * so emails are sent one at a time.\n *\n * @param options - The batch email options containing emails and error handling config\n * @returns A promise that resolves to the batch result with success/failure counts\n */\n async sendBatch(options: BatchEmailOptions): Promise<BatchEmailResult> {\n const results: EmailResult[] = [];\n let successful = 0;\n let failed = 0;\n\n for (const email of options.emails) {\n try {\n const result = await this.send(email);\n results.push(result);\n\n if (result.success) {\n successful++;\n } else {\n failed++;\n if (options.stopOnError) break;\n }\n } catch (err) {\n failed++;\n results.push({\n success: false,\n error: err instanceof Error ? err.message : \"Unknown error\",\n });\n\n if (options.stopOnError) break;\n }\n }\n\n return {\n total: options.emails.length,\n successful,\n failed,\n results,\n };\n }\n\n /**\n * Verifies the SendGrid API key by checking API scopes.\n *\n * @returns A promise that resolves to true if the API key is valid, false otherwise\n */\n async verify(): Promise<boolean> {\n try {\n const response = await fetch(`${this.baseUrl}/scopes`, {\n headers: {\n \"Authorization\": `Bearer ${this.apiKey}`,\n },\n });\n\n return response.ok;\n } catch {\n return false;\n }\n }\n\n /**\n * Converts a Uint8Array to a base64-encoded string.\n *\n * @param data - The binary data to encode\n * @returns The base64-encoded string\n */\n private uint8ArrayToBase64(data: Uint8Array): string {\n let binary = \"\";\n for (let i = 0; i < data.length; i++) {\n const byte = data[i];\n if (byte !== undefined) {\n binary += String.fromCharCode(byte);\n }\n }\n return btoa(binary);\n }\n}\n\n/**\n * Creates a SendGrid provider instance.\n *\n * @param config - The provider configuration including API key and sender info\n * @returns A new SendGridProvider instance\n */\nexport function createSendGridProvider(config: EmailProviderConfig): SendGridProvider {\n return new SendGridProvider(config);\n}\n","/**\n * @parsrun/email - Postmark Provider\n * Edge-compatible Postmark email provider\n */\n\nimport type {\n BatchEmailOptions,\n BatchEmailResult,\n EmailAddress,\n EmailOptions,\n EmailProvider,\n EmailProviderConfig,\n EmailResult,\n} from \"../types.js\";\nimport { EmailError, EmailErrorCodes } from \"../types.js\";\n\n/**\n * Postmark Email Provider\n * Uses fetch API for edge compatibility\n *\n * @example\n * ```typescript\n * const postmark = new PostmarkProvider({\n * apiKey: process.env.POSTMARK_API_KEY,\n * fromEmail: 'hello@example.com',\n * fromName: 'My App',\n * });\n *\n * await postmark.send({\n * to: 'user@example.com',\n * subject: 'Hello',\n * html: '<p>Hello World!</p>',\n * });\n * ```\n */\nexport class PostmarkProvider implements EmailProvider {\n /** Provider type identifier */\n readonly type = \"postmark\" as const;\n\n private apiKey: string;\n private fromEmail: string;\n private fromName: string | undefined;\n private baseUrl = \"https://api.postmarkapp.com\";\n\n /**\n * Creates a new PostmarkProvider instance.\n *\n * @param config - The provider configuration including API key (server token) and sender info\n */\n constructor(config: EmailProviderConfig) {\n this.apiKey = config.apiKey;\n this.fromEmail = config.fromEmail;\n this.fromName = config.fromName;\n }\n\n private formatAddress(address: string | EmailAddress): string {\n if (typeof address === \"string\") {\n return address;\n }\n if (address.name) {\n return `${address.name} <${address.email}>`;\n }\n return address.email;\n }\n\n private formatAddresses(\n addresses: string | string[] | EmailAddress | EmailAddress[]\n ): string {\n if (Array.isArray(addresses)) {\n return addresses.map((a) => this.formatAddress(a)).join(\",\");\n }\n return this.formatAddress(addresses);\n }\n\n /**\n * Sends an email via the Postmark API.\n *\n * @param options - The email options including recipient, subject, and content\n * @returns A promise that resolves to the send result with message ID if successful\n * @throws {EmailError} If the Postmark API request fails\n */\n async send(options: EmailOptions): Promise<EmailResult> {\n const from = options.from\n ? this.formatAddress(options.from)\n : this.fromName\n ? `${this.fromName} <${this.fromEmail}>`\n : this.fromEmail;\n\n const payload: Record<string, unknown> = {\n From: from,\n To: this.formatAddresses(options.to),\n Subject: options.subject,\n };\n\n if (options.html) payload[\"HtmlBody\"] = options.html;\n if (options.text) payload[\"TextBody\"] = options.text;\n if (options.replyTo) payload[\"ReplyTo\"] = this.formatAddress(options.replyTo);\n if (options.cc) payload[\"Cc\"] = this.formatAddresses(options.cc);\n if (options.bcc) payload[\"Bcc\"] = this.formatAddresses(options.bcc);\n\n if (options.headers) {\n payload[\"Headers\"] = Object.entries(options.headers).map(([Name, Value]) => ({ Name, Value }));\n }\n\n if (options.tags) {\n // Postmark uses Tag field (single tag) and Metadata for custom data\n const tagEntries = Object.entries(options.tags);\n if (tagEntries.length > 0 && tagEntries[0]) {\n payload[\"Tag\"] = tagEntries[0][1];\n }\n payload[\"Metadata\"] = options.tags;\n }\n\n if (options.attachments && options.attachments.length > 0) {\n payload[\"Attachments\"] = options.attachments.map((att) => ({\n Name: att.filename,\n Content: typeof att.content === \"string\"\n ? att.content\n : this.uint8ArrayToBase64(att.content),\n ContentType: att.contentType || \"application/octet-stream\",\n ContentID: att.contentId,\n }));\n }\n\n try {\n const response = await fetch(`${this.baseUrl}/email`, {\n method: \"POST\",\n headers: {\n \"X-Postmark-Server-Token\": this.apiKey,\n \"Content-Type\": \"application/json\",\n \"Accept\": \"application/json\",\n },\n body: JSON.stringify(payload),\n });\n\n const data = await response.json() as {\n MessageID?: string;\n ErrorCode?: number;\n Message?: string;\n };\n\n if (!response.ok || data.ErrorCode) {\n return {\n success: false,\n error: data.Message || `HTTP ${response.status}`,\n data,\n };\n }\n\n return {\n success: true,\n messageId: data.MessageID,\n data,\n };\n } catch (err) {\n throw new EmailError(\n `Postmark send failed: ${err instanceof Error ? err.message : \"Unknown error\"}`,\n EmailErrorCodes.SEND_FAILED,\n err\n );\n }\n }\n\n /**\n * Sends multiple emails via Postmark's batch API.\n *\n * Postmark supports native batch sending of up to 500 emails in a single request.\n *\n * @param options - The batch email options containing emails and error handling config\n * @returns A promise that resolves to the batch result with success/failure counts\n * @throws {EmailError} If the Postmark batch API request fails\n */\n async sendBatch(options: BatchEmailOptions): Promise<BatchEmailResult> {\n // Postmark supports batch sending up to 500 emails\n const batchPayload = options.emails.map((email) => {\n const from = email.from\n ? this.formatAddress(email.from)\n : this.fromName\n ? `${this.fromName} <${this.fromEmail}>`\n : this.fromEmail;\n\n const item: Record<string, unknown> = {\n From: from,\n To: this.formatAddresses(email.to),\n Subject: email.subject,\n };\n\n if (email.html) item[\"HtmlBody\"] = email.html;\n if (email.text) item[\"TextBody\"] = email.text;\n if (email.replyTo) item[\"ReplyTo\"] = this.formatAddress(email.replyTo);\n if (email.cc) item[\"Cc\"] = this.formatAddresses(email.cc);\n if (email.bcc) item[\"Bcc\"] = this.formatAddresses(email.bcc);\n\n return item;\n });\n\n try {\n const response = await fetch(`${this.baseUrl}/email/batch`, {\n method: \"POST\",\n headers: {\n \"X-Postmark-Server-Token\": this.apiKey,\n \"Content-Type\": \"application/json\",\n \"Accept\": \"application/json\",\n },\n body: JSON.stringify(batchPayload),\n });\n\n const data = await response.json() as Array<{\n MessageID?: string;\n ErrorCode?: number;\n Message?: string;\n }>;\n\n const results: EmailResult[] = data.map((item) => ({\n success: !item.ErrorCode,\n messageId: item.MessageID,\n error: item.ErrorCode ? item.Message : undefined,\n data: item,\n }));\n\n const successful = results.filter((r) => r.success).length;\n const failed = results.filter((r) => !r.success).length;\n\n return {\n total: options.emails.length,\n successful,\n failed,\n results,\n };\n } catch (err) {\n throw new EmailError(\n `Postmark batch send failed: ${err instanceof Error ? err.message : \"Unknown error\"}`,\n EmailErrorCodes.SEND_FAILED,\n err\n );\n }\n }\n\n /**\n * Verifies the Postmark server token by checking server info.\n *\n * @returns A promise that resolves to true if the server token is valid, false otherwise\n */\n async verify(): Promise<boolean> {\n try {\n const response = await fetch(`${this.baseUrl}/server`, {\n headers: {\n \"X-Postmark-Server-Token\": this.apiKey,\n \"Accept\": \"application/json\",\n },\n });\n\n return response.ok;\n } catch {\n return false;\n }\n }\n\n /**\n * Converts a Uint8Array to a base64-encoded string.\n *\n * @param data - The binary data to encode\n * @returns The base64-encoded string\n */\n private uint8ArrayToBase64(data: Uint8Array): string {\n let binary = \"\";\n for (let i = 0; i < data.length; i++) {\n const byte = data[i];\n if (byte !== undefined) {\n binary += String.fromCharCode(byte);\n }\n }\n return btoa(binary);\n }\n}\n\n/**\n * Creates a Postmark provider instance.\n *\n * @param config - The provider configuration including server token and sender info\n * @returns A new PostmarkProvider instance\n */\nexport function createPostmarkProvider(config: EmailProviderConfig): PostmarkProvider {\n return new PostmarkProvider(config);\n}\n","/**\n * @parsrun/email - Console Provider\n * Development/testing email provider that logs to console\n */\n\nimport type {\n BatchEmailOptions,\n BatchEmailResult,\n EmailAddress,\n EmailOptions,\n EmailProvider,\n EmailProviderConfig,\n EmailResult,\n} from \"../types.js\";\n\n/**\n * Console Email Provider\n * Logs emails to console instead of sending them.\n * Useful for development and testing.\n *\n * @example\n * ```typescript\n * const console = new ConsoleProvider({\n * apiKey: 'not-needed',\n * fromEmail: 'test@example.com',\n * fromName: 'Test App',\n * });\n *\n * await console.send({\n * to: 'user@example.com',\n * subject: 'Hello',\n * html: '<p>Hello World!</p>',\n * });\n * // Logs email details to console\n * ```\n */\nexport class ConsoleProvider implements EmailProvider {\n /** Provider type identifier */\n readonly type = \"console\" as const;\n\n private fromEmail: string;\n private fromName: string | undefined;\n private messageCounter = 0;\n\n /**\n * Creates a new ConsoleProvider instance.\n *\n * @param config - The provider configuration (apiKey is not required for console provider)\n */\n constructor(config: EmailProviderConfig) {\n this.fromEmail = config.fromEmail;\n this.fromName = config.fromName;\n }\n\n private formatAddress(address: string | EmailAddress): string {\n if (typeof address === \"string\") {\n return address;\n }\n if (address.name) {\n return `${address.name} <${address.email}>`;\n }\n return address.email;\n }\n\n private formatAddresses(\n addresses: string | string[] | EmailAddress | EmailAddress[]\n ): string {\n if (Array.isArray(addresses)) {\n return addresses.map((a) => this.formatAddress(a)).join(\", \");\n }\n return this.formatAddress(addresses);\n }\n\n /**\n * Logs an email to the console instead of sending it.\n *\n * @param options - The email options to log\n * @returns A promise that resolves to a successful result with a generated message ID\n */\n async send(options: EmailOptions): Promise<EmailResult> {\n this.messageCounter++;\n const messageId = `console-${Date.now()}-${this.messageCounter}`;\n\n const from = options.from\n ? this.formatAddress(options.from)\n : this.fromName\n ? `${this.fromName} <${this.fromEmail}>`\n : this.fromEmail;\n\n const separator = \"─\".repeat(60);\n\n console.log(`\\n${separator}`);\n console.log(\"šŸ“§ EMAIL (Console Provider)\");\n console.log(separator);\n console.log(`Message ID: ${messageId}`);\n console.log(`From: ${from}`);\n console.log(`To: ${this.formatAddresses(options.to)}`);\n if (options.cc) {\n console.log(`CC: ${this.formatAddresses(options.cc)}`);\n }\n if (options.bcc) {\n console.log(`BCC: ${this.formatAddresses(options.bcc)}`);\n }\n if (options.replyTo) {\n console.log(`Reply-To: ${this.formatAddress(options.replyTo)}`);\n }\n console.log(`Subject: ${options.subject}`);\n\n if (options.headers) {\n console.log(`Headers: ${JSON.stringify(options.headers)}`);\n }\n if (options.tags) {\n console.log(`Tags: ${JSON.stringify(options.tags)}`);\n }\n if (options.scheduledAt) {\n console.log(`Scheduled: ${options.scheduledAt.toISOString()}`);\n }\n if (options.attachments && options.attachments.length > 0) {\n console.log(`Attachments:`);\n for (const att of options.attachments) {\n const size = typeof att.content === \"string\"\n ? att.content.length\n : att.content.length;\n console.log(` - ${att.filename} (${att.contentType || \"unknown\"}, ${size} bytes)`);\n }\n }\n\n console.log(separator);\n if (options.text) {\n console.log(\"TEXT CONTENT:\");\n console.log(options.text);\n }\n if (options.html) {\n console.log(\"HTML CONTENT:\");\n console.log(options.html);\n }\n console.log(`${separator}\\n`);\n\n return {\n success: true,\n messageId,\n };\n }\n\n /**\n * Logs multiple emails to the console.\n *\n * @param options - The batch email options containing emails to log\n * @returns A promise that resolves to the batch result with success/failure counts\n */\n async sendBatch(options: BatchEmailOptions): Promise<BatchEmailResult> {\n const results: EmailResult[] = [];\n let successful = 0;\n let failed = 0;\n\n console.log(`\\nšŸ“¬ BATCH EMAIL (${options.emails.length} emails)`);\n\n for (const email of options.emails) {\n try {\n const result = await this.send(email);\n results.push(result);\n\n if (result.success) {\n successful++;\n } else {\n failed++;\n if (options.stopOnError) break;\n }\n } catch (err) {\n failed++;\n results.push({\n success: false,\n error: err instanceof Error ? err.message : \"Unknown error\",\n });\n\n if (options.stopOnError) break;\n }\n }\n\n console.log(`šŸ“¬ BATCH COMPLETE: ${successful} sent, ${failed} failed\\n`);\n\n return {\n total: options.emails.length,\n successful,\n failed,\n results,\n };\n }\n\n /**\n * Verifies the provider configuration.\n *\n * For the console provider, this always returns true and logs a message.\n *\n * @returns A promise that always resolves to true\n */\n async verify(): Promise<boolean> {\n console.log(\"šŸ“§ Console email provider verified (always returns true)\");\n return true;\n }\n}\n\n/**\n * Creates a Console provider instance.\n *\n * @param config - The provider configuration\n * @returns A new ConsoleProvider instance\n */\nexport function createConsoleProvider(config: EmailProviderConfig): ConsoleProvider {\n return new ConsoleProvider(config);\n}\n","/**\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","/**\n * @module\n * Edge-compatible email sending for Pars.\n *\n * Supports multiple providers:\n * - Resend (recommended)\n * - SendGrid\n * - Postmark\n * - Console (development)\n *\n * @example\n * ```typescript\n * import { createEmailService } from '@parsrun/email';\n *\n * const email = createEmailService({\n * provider: 'resend',\n * apiKey: process.env.RESEND_API_KEY,\n * fromEmail: 'hello@example.com',\n * fromName: 'My App',\n * });\n *\n * await email.send({\n * to: 'user@example.com',\n * subject: 'Hello',\n * html: '<p>Hello World!</p>',\n * });\n * ```\n */\n\n// Re-export types\nexport * from \"./types.js\";\n\n// Re-export providers\nexport { ResendProvider, createResendProvider } from \"./providers/resend.js\";\nexport { SendGridProvider, createSendGridProvider } from \"./providers/sendgrid.js\";\nexport { PostmarkProvider, createPostmarkProvider } from \"./providers/postmark.js\";\nexport { ConsoleProvider, createConsoleProvider } from \"./providers/console.js\";\n\n// Re-export templates\nexport * from \"./templates/index.js\";\n\nimport type {\n BatchEmailOptions,\n BatchEmailResult,\n EmailOptions,\n EmailProvider,\n EmailProviderConfig,\n EmailProviderType,\n EmailResult,\n EmailServiceConfig,\n} from \"./types.js\";\nimport { EmailError, EmailErrorCodes } from \"./types.js\";\nimport { ResendProvider } from \"./providers/resend.js\";\nimport { SendGridProvider } from \"./providers/sendgrid.js\";\nimport { PostmarkProvider } from \"./providers/postmark.js\";\nimport { ConsoleProvider } from \"./providers/console.js\";\n\n/**\n * Email Service\n *\n * High-level email service that provides a unified interface for sending emails\n * through various providers (Resend, SendGrid, Postmark, or Console for development).\n *\n * @example\n * ```typescript\n * const service = new EmailService({\n * provider: 'resend',\n * apiKey: 'your-api-key',\n * fromEmail: 'hello@example.com',\n * fromName: 'My App',\n * });\n *\n * await service.send({\n * to: 'user@example.com',\n * subject: 'Hello',\n * html: '<p>Welcome!</p>',\n * });\n * ```\n */\nexport class EmailService {\n private provider: EmailProvider;\n private debug: boolean;\n\n /**\n * Creates a new EmailService instance.\n *\n * @param config - The email service configuration including provider type, API key, and default sender info\n */\n constructor(config: EmailServiceConfig) {\n this.debug = config.debug ?? false;\n this.provider = this.createProvider(config);\n }\n\n private createProvider(config: EmailServiceConfig): EmailProvider {\n const providerConfig: EmailProviderConfig = {\n apiKey: config.apiKey,\n fromEmail: config.fromEmail,\n fromName: config.fromName,\n options: config.providerOptions,\n };\n\n switch (config.provider) {\n case \"resend\":\n return new ResendProvider(providerConfig);\n case \"sendgrid\":\n return new SendGridProvider(providerConfig);\n case \"postmark\":\n return new PostmarkProvider(providerConfig);\n case \"console\":\n return new ConsoleProvider(providerConfig);\n default:\n throw new EmailError(\n `Unknown email provider: ${config.provider}`,\n EmailErrorCodes.INVALID_CONFIG\n );\n }\n }\n\n /**\n * Gets the type of email provider being used.\n *\n * @returns The provider type identifier (e.g., 'resend', 'sendgrid', 'postmark', 'console')\n */\n get providerType(): EmailProviderType {\n return this.provider.type;\n }\n\n /**\n * Sends a single email through the configured provider.\n *\n * @param options - The email options including recipient, subject, and content\n * @returns A promise that resolves to the result of the send operation\n * @throws {EmailError} If the email send fails due to provider error\n */\n async send(options: EmailOptions): Promise<EmailResult> {\n if (this.debug) {\n console.log(\"[Email] Sending email:\", {\n to: options.to,\n subject: options.subject,\n provider: this.provider.type,\n });\n }\n\n const result = await this.provider.send(options);\n\n if (this.debug) {\n console.log(\"[Email] Result:\", result);\n }\n\n return result;\n }\n\n /**\n * Sends multiple emails in a batch.\n *\n * If the provider supports native batch sending, it will be used. Otherwise,\n * emails are sent sequentially.\n *\n * @param options - The batch email options including array of emails and error handling config\n * @returns A promise that resolves to the batch result with success/failure counts\n */\n async sendBatch(options: BatchEmailOptions): Promise<BatchEmailResult> {\n if (this.debug) {\n console.log(\"[Email] Sending batch:\", {\n count: options.emails.length,\n provider: this.provider.type,\n });\n }\n\n // Use provider's native batch if available\n if (this.provider.sendBatch) {\n const result = await this.provider.sendBatch(options);\n\n if (this.debug) {\n console.log(\"[Email] Batch result:\", {\n total: result.total,\n successful: result.successful,\n failed: result.failed,\n });\n }\n\n return result;\n }\n\n // Fallback to sequential sending\n const results: EmailResult[] = [];\n let successful = 0;\n let failed = 0;\n\n for (const email of options.emails) {\n try {\n const result = await this.send(email);\n results.push(result);\n\n if (result.success) {\n successful++;\n } else {\n failed++;\n if (options.stopOnError) break;\n }\n } catch (err) {\n failed++;\n results.push({\n success: false,\n error: err instanceof Error ? err.message : \"Unknown error\",\n });\n\n if (options.stopOnError) break;\n }\n }\n\n return {\n total: options.emails.length,\n successful,\n failed,\n results,\n };\n }\n\n /**\n * Verifies the provider configuration by testing the API connection.\n *\n * @returns A promise that resolves to true if the configuration is valid, false otherwise\n */\n async verify(): Promise<boolean> {\n if (this.provider.verify) {\n return this.provider.verify();\n }\n return true;\n }\n}\n\n/**\n * Create an email service\n *\n * @example\n * ```typescript\n * // With Resend\n * const email = createEmailService({\n * provider: 'resend',\n * apiKey: process.env.RESEND_API_KEY,\n * fromEmail: 'hello@example.com',\n * });\n *\n * // With SendGrid\n * const email = createEmailService({\n * provider: 'sendgrid',\n * apiKey: process.env.SENDGRID_API_KEY,\n * fromEmail: 'hello@example.com',\n * });\n *\n * // For development\n * const email = createEmailService({\n * provider: 'console',\n * apiKey: 'not-needed',\n * fromEmail: 'test@example.com',\n * });\n * ```\n */\nexport function createEmailService(config: EmailServiceConfig): EmailService {\n return new EmailService(config);\n}\n\n/**\n * Creates an email provider instance directly.\n *\n * Use this when you need direct access to a provider without the EmailService wrapper.\n *\n * @param type - The type of email provider to create\n * @param config - The provider configuration including API key and sender info\n * @returns A new email provider instance\n * @throws {EmailError} If an unknown provider type is specified\n *\n * @example\n * ```typescript\n * const provider = createEmailProvider('resend', {\n * apiKey: 'your-api-key',\n * fromEmail: 'hello@example.com',\n * });\n *\n * await provider.send({\n * to: 'user@example.com',\n * subject: 'Hello',\n * html: '<p>Hi!</p>',\n * });\n * ```\n */\nexport function createEmailProvider(\n type: EmailProviderType,\n config: EmailProviderConfig\n): EmailProvider {\n switch (type) {\n case \"resend\":\n return new ResendProvider(config);\n case \"sendgrid\":\n return new SendGridProvider(config);\n case \"postmark\":\n return new PostmarkProvider(config);\n case \"console\":\n return new ConsoleProvider(config);\n default:\n throw new EmailError(\n `Unknown email provider: ${type}`,\n EmailErrorCodes.INVALID_CONFIG\n );\n }\n}\n\n// Default export\nexport default {\n EmailService,\n createEmailService,\n createEmailProvider,\n};\n"],"mappings":"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","names":["type"]}
@@ -28,19 +28,47 @@ import '@parsrun/types';
28
28
  * ```
29
29
  */
30
30
  declare class ConsoleProvider implements EmailProvider {
31
+ /** Provider type identifier */
31
32
  readonly type: "console";
32
33
  private fromEmail;
33
34
  private fromName;
34
35
  private messageCounter;
36
+ /**
37
+ * Creates a new ConsoleProvider instance.
38
+ *
39
+ * @param config - The provider configuration (apiKey is not required for console provider)
40
+ */
35
41
  constructor(config: EmailProviderConfig);
36
42
  private formatAddress;
37
43
  private formatAddresses;
44
+ /**
45
+ * Logs an email to the console instead of sending it.
46
+ *
47
+ * @param options - The email options to log
48
+ * @returns A promise that resolves to a successful result with a generated message ID
49
+ */
38
50
  send(options: EmailOptions): Promise<EmailResult>;
51
+ /**
52
+ * Logs multiple emails to the console.
53
+ *
54
+ * @param options - The batch email options containing emails to log
55
+ * @returns A promise that resolves to the batch result with success/failure counts
56
+ */
39
57
  sendBatch(options: BatchEmailOptions): Promise<BatchEmailResult>;
58
+ /**
59
+ * Verifies the provider configuration.
60
+ *
61
+ * For the console provider, this always returns true and logs a message.
62
+ *
63
+ * @returns A promise that always resolves to true
64
+ */
40
65
  verify(): Promise<boolean>;
41
66
  }
42
67
  /**
43
- * Create a Console provider
68
+ * Creates a Console provider instance.
69
+ *
70
+ * @param config - The provider configuration
71
+ * @returns A new ConsoleProvider instance
44
72
  */
45
73
  declare function createConsoleProvider(config: EmailProviderConfig): ConsoleProvider;
46
74
 
@@ -1,9 +1,15 @@
1
1
  // src/providers/console.ts
2
2
  var ConsoleProvider = class {
3
+ /** Provider type identifier */
3
4
  type = "console";
4
5
  fromEmail;
5
6
  fromName;
6
7
  messageCounter = 0;
8
+ /**
9
+ * Creates a new ConsoleProvider instance.
10
+ *
11
+ * @param config - The provider configuration (apiKey is not required for console provider)
12
+ */
7
13
  constructor(config) {
8
14
  this.fromEmail = config.fromEmail;
9
15
  this.fromName = config.fromName;
@@ -23,6 +29,12 @@ var ConsoleProvider = class {
23
29
  }
24
30
  return this.formatAddress(addresses);
25
31
  }
32
+ /**
33
+ * Logs an email to the console instead of sending it.
34
+ *
35
+ * @param options - The email options to log
36
+ * @returns A promise that resolves to a successful result with a generated message ID
37
+ */
26
38
  async send(options) {
27
39
  this.messageCounter++;
28
40
  const messageId = `console-${Date.now()}-${this.messageCounter}`;
@@ -77,6 +89,12 @@ ${separator}`);
77
89
  messageId
78
90
  };
79
91
  }
92
+ /**
93
+ * Logs multiple emails to the console.
94
+ *
95
+ * @param options - The batch email options containing emails to log
96
+ * @returns A promise that resolves to the batch result with success/failure counts
97
+ */
80
98
  async sendBatch(options) {
81
99
  const results = [];
82
100
  let successful = 0;
@@ -111,6 +129,13 @@ ${separator}`);
111
129
  results
112
130
  };
113
131
  }
132
+ /**
133
+ * Verifies the provider configuration.
134
+ *
135
+ * For the console provider, this always returns true and logs a message.
136
+ *
137
+ * @returns A promise that always resolves to true
138
+ */
114
139
  async verify() {
115
140
  console.log("\u{1F4E7} Console email provider verified (always returns true)");
116
141
  return true;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/providers/console.ts"],"sourcesContent":["/**\n * @parsrun/email - Console Provider\n * Development/testing email provider that logs to console\n */\n\nimport type {\n BatchEmailOptions,\n BatchEmailResult,\n EmailAddress,\n EmailOptions,\n EmailProvider,\n EmailProviderConfig,\n EmailResult,\n} from \"../types.js\";\n\n/**\n * Console Email Provider\n * Logs emails to console instead of sending them.\n * Useful for development and testing.\n *\n * @example\n * ```typescript\n * const console = new ConsoleProvider({\n * apiKey: 'not-needed',\n * fromEmail: 'test@example.com',\n * fromName: 'Test App',\n * });\n *\n * await console.send({\n * to: 'user@example.com',\n * subject: 'Hello',\n * html: '<p>Hello World!</p>',\n * });\n * // Logs email details to console\n * ```\n */\nexport class ConsoleProvider implements EmailProvider {\n readonly type = \"console\" as const;\n\n private fromEmail: string;\n private fromName: string | undefined;\n private messageCounter = 0;\n\n constructor(config: EmailProviderConfig) {\n this.fromEmail = config.fromEmail;\n this.fromName = config.fromName;\n }\n\n private formatAddress(address: string | EmailAddress): string {\n if (typeof address === \"string\") {\n return address;\n }\n if (address.name) {\n return `${address.name} <${address.email}>`;\n }\n return address.email;\n }\n\n private formatAddresses(\n addresses: string | string[] | EmailAddress | EmailAddress[]\n ): string {\n if (Array.isArray(addresses)) {\n return addresses.map((a) => this.formatAddress(a)).join(\", \");\n }\n return this.formatAddress(addresses);\n }\n\n async send(options: EmailOptions): Promise<EmailResult> {\n this.messageCounter++;\n const messageId = `console-${Date.now()}-${this.messageCounter}`;\n\n const from = options.from\n ? this.formatAddress(options.from)\n : this.fromName\n ? `${this.fromName} <${this.fromEmail}>`\n : this.fromEmail;\n\n const separator = \"─\".repeat(60);\n\n console.log(`\\n${separator}`);\n console.log(\"šŸ“§ EMAIL (Console Provider)\");\n console.log(separator);\n console.log(`Message ID: ${messageId}`);\n console.log(`From: ${from}`);\n console.log(`To: ${this.formatAddresses(options.to)}`);\n if (options.cc) {\n console.log(`CC: ${this.formatAddresses(options.cc)}`);\n }\n if (options.bcc) {\n console.log(`BCC: ${this.formatAddresses(options.bcc)}`);\n }\n if (options.replyTo) {\n console.log(`Reply-To: ${this.formatAddress(options.replyTo)}`);\n }\n console.log(`Subject: ${options.subject}`);\n\n if (options.headers) {\n console.log(`Headers: ${JSON.stringify(options.headers)}`);\n }\n if (options.tags) {\n console.log(`Tags: ${JSON.stringify(options.tags)}`);\n }\n if (options.scheduledAt) {\n console.log(`Scheduled: ${options.scheduledAt.toISOString()}`);\n }\n if (options.attachments && options.attachments.length > 0) {\n console.log(`Attachments:`);\n for (const att of options.attachments) {\n const size = typeof att.content === \"string\"\n ? att.content.length\n : att.content.length;\n console.log(` - ${att.filename} (${att.contentType || \"unknown\"}, ${size} bytes)`);\n }\n }\n\n console.log(separator);\n if (options.text) {\n console.log(\"TEXT CONTENT:\");\n console.log(options.text);\n }\n if (options.html) {\n console.log(\"HTML CONTENT:\");\n console.log(options.html);\n }\n console.log(`${separator}\\n`);\n\n return {\n success: true,\n messageId,\n };\n }\n\n async sendBatch(options: BatchEmailOptions): Promise<BatchEmailResult> {\n const results: EmailResult[] = [];\n let successful = 0;\n let failed = 0;\n\n console.log(`\\nšŸ“¬ BATCH EMAIL (${options.emails.length} emails)`);\n\n for (const email of options.emails) {\n try {\n const result = await this.send(email);\n results.push(result);\n\n if (result.success) {\n successful++;\n } else {\n failed++;\n if (options.stopOnError) break;\n }\n } catch (err) {\n failed++;\n results.push({\n success: false,\n error: err instanceof Error ? err.message : \"Unknown error\",\n });\n\n if (options.stopOnError) break;\n }\n }\n\n console.log(`šŸ“¬ BATCH COMPLETE: ${successful} sent, ${failed} failed\\n`);\n\n return {\n total: options.emails.length,\n successful,\n failed,\n results,\n };\n }\n\n async verify(): Promise<boolean> {\n console.log(\"šŸ“§ Console email provider verified (always returns true)\");\n return true;\n }\n}\n\n/**\n * Create a Console provider\n */\nexport function createConsoleProvider(config: EmailProviderConfig): ConsoleProvider {\n return new ConsoleProvider(config);\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/providers/console.ts"],"sourcesContent":["/**\n * @parsrun/email - Console Provider\n * Development/testing email provider that logs to console\n */\n\nimport type {\n BatchEmailOptions,\n BatchEmailResult,\n EmailAddress,\n EmailOptions,\n EmailProvider,\n EmailProviderConfig,\n EmailResult,\n} from \"../types.js\";\n\n/**\n * Console Email Provider\n * Logs emails to console instead of sending them.\n * Useful for development and testing.\n *\n * @example\n * ```typescript\n * const console = new ConsoleProvider({\n * apiKey: 'not-needed',\n * fromEmail: 'test@example.com',\n * fromName: 'Test App',\n * });\n *\n * await console.send({\n * to: 'user@example.com',\n * subject: 'Hello',\n * html: '<p>Hello World!</p>',\n * });\n * // Logs email details to console\n * ```\n */\nexport class ConsoleProvider implements EmailProvider {\n /** Provider type identifier */\n readonly type = \"console\" as const;\n\n private fromEmail: string;\n private fromName: string | undefined;\n private messageCounter = 0;\n\n /**\n * Creates a new ConsoleProvider instance.\n *\n * @param config - The provider configuration (apiKey is not required for console provider)\n */\n constructor(config: EmailProviderConfig) {\n this.fromEmail = config.fromEmail;\n this.fromName = config.fromName;\n }\n\n private formatAddress(address: string | EmailAddress): string {\n if (typeof address === \"string\") {\n return address;\n }\n if (address.name) {\n return `${address.name} <${address.email}>`;\n }\n return address.email;\n }\n\n private formatAddresses(\n addresses: string | string[] | EmailAddress | EmailAddress[]\n ): string {\n if (Array.isArray(addresses)) {\n return addresses.map((a) => this.formatAddress(a)).join(\", \");\n }\n return this.formatAddress(addresses);\n }\n\n /**\n * Logs an email to the console instead of sending it.\n *\n * @param options - The email options to log\n * @returns A promise that resolves to a successful result with a generated message ID\n */\n async send(options: EmailOptions): Promise<EmailResult> {\n this.messageCounter++;\n const messageId = `console-${Date.now()}-${this.messageCounter}`;\n\n const from = options.from\n ? this.formatAddress(options.from)\n : this.fromName\n ? `${this.fromName} <${this.fromEmail}>`\n : this.fromEmail;\n\n const separator = \"─\".repeat(60);\n\n console.log(`\\n${separator}`);\n console.log(\"šŸ“§ EMAIL (Console Provider)\");\n console.log(separator);\n console.log(`Message ID: ${messageId}`);\n console.log(`From: ${from}`);\n console.log(`To: ${this.formatAddresses(options.to)}`);\n if (options.cc) {\n console.log(`CC: ${this.formatAddresses(options.cc)}`);\n }\n if (options.bcc) {\n console.log(`BCC: ${this.formatAddresses(options.bcc)}`);\n }\n if (options.replyTo) {\n console.log(`Reply-To: ${this.formatAddress(options.replyTo)}`);\n }\n console.log(`Subject: ${options.subject}`);\n\n if (options.headers) {\n console.log(`Headers: ${JSON.stringify(options.headers)}`);\n }\n if (options.tags) {\n console.log(`Tags: ${JSON.stringify(options.tags)}`);\n }\n if (options.scheduledAt) {\n console.log(`Scheduled: ${options.scheduledAt.toISOString()}`);\n }\n if (options.attachments && options.attachments.length > 0) {\n console.log(`Attachments:`);\n for (const att of options.attachments) {\n const size = typeof att.content === \"string\"\n ? att.content.length\n : att.content.length;\n console.log(` - ${att.filename} (${att.contentType || \"unknown\"}, ${size} bytes)`);\n }\n }\n\n console.log(separator);\n if (options.text) {\n console.log(\"TEXT CONTENT:\");\n console.log(options.text);\n }\n if (options.html) {\n console.log(\"HTML CONTENT:\");\n console.log(options.html);\n }\n console.log(`${separator}\\n`);\n\n return {\n success: true,\n messageId,\n };\n }\n\n /**\n * Logs multiple emails to the console.\n *\n * @param options - The batch email options containing emails to log\n * @returns A promise that resolves to the batch result with success/failure counts\n */\n async sendBatch(options: BatchEmailOptions): Promise<BatchEmailResult> {\n const results: EmailResult[] = [];\n let successful = 0;\n let failed = 0;\n\n console.log(`\\nšŸ“¬ BATCH EMAIL (${options.emails.length} emails)`);\n\n for (const email of options.emails) {\n try {\n const result = await this.send(email);\n results.push(result);\n\n if (result.success) {\n successful++;\n } else {\n failed++;\n if (options.stopOnError) break;\n }\n } catch (err) {\n failed++;\n results.push({\n success: false,\n error: err instanceof Error ? err.message : \"Unknown error\",\n });\n\n if (options.stopOnError) break;\n }\n }\n\n console.log(`šŸ“¬ BATCH COMPLETE: ${successful} sent, ${failed} failed\\n`);\n\n return {\n total: options.emails.length,\n successful,\n failed,\n results,\n };\n }\n\n /**\n * Verifies the provider configuration.\n *\n * For the console provider, this always returns true and logs a message.\n *\n * @returns A promise that always resolves to true\n */\n async verify(): Promise<boolean> {\n console.log(\"šŸ“§ Console email provider verified (always returns true)\");\n return true;\n }\n}\n\n/**\n * Creates a Console provider instance.\n *\n * @param config - The provider configuration\n * @returns A new ConsoleProvider instance\n */\nexport function createConsoleProvider(config: EmailProviderConfig): ConsoleProvider {\n return new ConsoleProvider(config);\n}\n"],"mappings":"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","names":[]}