@parsrun/email 0.1.30 → 0.1.32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/types.ts","../../src/providers/postmark.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 - 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"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/types.ts","../../src/providers/postmark.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 - 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"],"mappings":"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","names":[]}
@@ -26,21 +26,56 @@ import '@parsrun/types';
26
26
  * ```
27
27
  */
28
28
  declare class ResendProvider implements EmailProvider {
29
+ /** Provider type identifier */
29
30
  readonly type: "resend";
30
31
  private apiKey;
31
32
  private fromEmail;
32
33
  private fromName;
33
34
  private baseUrl;
35
+ /**
36
+ * Creates a new ResendProvider instance.
37
+ *
38
+ * @param config - The provider configuration including API key and sender info
39
+ */
34
40
  constructor(config: EmailProviderConfig);
35
41
  private formatAddress;
36
42
  private formatAddresses;
43
+ /**
44
+ * Sends an email via the Resend API.
45
+ *
46
+ * @param options - The email options including recipient, subject, and content
47
+ * @returns A promise that resolves to the send result with message ID if successful
48
+ * @throws {EmailError} If the Resend API request fails
49
+ */
37
50
  send(options: EmailOptions): Promise<EmailResult>;
51
+ /**
52
+ * Sends multiple emails via the Resend API sequentially.
53
+ *
54
+ * @param options - The batch email options containing emails and error handling config
55
+ * @returns A promise that resolves to the batch result with success/failure counts
56
+ */
38
57
  sendBatch(options: BatchEmailOptions): Promise<BatchEmailResult>;
58
+ /**
59
+ * Verifies the Resend API key by checking configured domains.
60
+ *
61
+ * @returns A promise that resolves to true if the API key is valid, false otherwise
62
+ */
39
63
  verify(): Promise<boolean>;
64
+ /**
65
+ * Converts a Uint8Array to a base64-encoded string.
66
+ *
67
+ * Edge-compatible base64 encoding that works in all JavaScript runtimes.
68
+ *
69
+ * @param data - The binary data to encode
70
+ * @returns The base64-encoded string
71
+ */
40
72
  private uint8ArrayToBase64;
41
73
  }
42
74
  /**
43
- * Create a Resend provider
75
+ * Creates a Resend provider instance.
76
+ *
77
+ * @param config - The provider configuration including API key and sender info
78
+ * @returns A new ResendProvider instance
44
79
  */
45
80
  declare function createResendProvider(config: EmailProviderConfig): ResendProvider;
46
81
 
@@ -14,6 +14,13 @@ import {
14
14
  emailConfig
15
15
  } from "@parsrun/types";
16
16
  var EmailError = class extends Error {
17
+ /**
18
+ * Creates a new EmailError instance.
19
+ *
20
+ * @param message - Human-readable error description
21
+ * @param code - Error code from EmailErrorCodes for programmatic handling
22
+ * @param cause - The underlying error that caused this error, if any
23
+ */
17
24
  constructor(message, code, cause) {
18
25
  super(message);
19
26
  this.code = code;
@@ -34,11 +41,17 @@ var EmailErrorCodes = {
34
41
 
35
42
  // src/providers/resend.ts
36
43
  var ResendProvider = class {
44
+ /** Provider type identifier */
37
45
  type = "resend";
38
46
  apiKey;
39
47
  fromEmail;
40
48
  fromName;
41
49
  baseUrl = "https://api.resend.com";
50
+ /**
51
+ * Creates a new ResendProvider instance.
52
+ *
53
+ * @param config - The provider configuration including API key and sender info
54
+ */
42
55
  constructor(config) {
43
56
  this.apiKey = config.apiKey;
44
57
  this.fromEmail = config.fromEmail;
@@ -59,6 +72,13 @@ var ResendProvider = class {
59
72
  }
60
73
  return [this.formatAddress(addresses)];
61
74
  }
75
+ /**
76
+ * Sends an email via the Resend API.
77
+ *
78
+ * @param options - The email options including recipient, subject, and content
79
+ * @returns A promise that resolves to the send result with message ID if successful
80
+ * @throws {EmailError} If the Resend API request fails
81
+ */
62
82
  async send(options) {
63
83
  const from = options.from ? this.formatAddress(options.from) : this.fromName ? `${this.fromName} <${this.fromEmail}>` : this.fromEmail;
64
84
  const payload = {
@@ -111,6 +131,12 @@ var ResendProvider = class {
111
131
  );
112
132
  }
113
133
  }
134
+ /**
135
+ * Sends multiple emails via the Resend API sequentially.
136
+ *
137
+ * @param options - The batch email options containing emails and error handling config
138
+ * @returns A promise that resolves to the batch result with success/failure counts
139
+ */
114
140
  async sendBatch(options) {
115
141
  const results = [];
116
142
  let successful = 0;
@@ -141,6 +167,11 @@ var ResendProvider = class {
141
167
  results
142
168
  };
143
169
  }
170
+ /**
171
+ * Verifies the Resend API key by checking configured domains.
172
+ *
173
+ * @returns A promise that resolves to true if the API key is valid, false otherwise
174
+ */
144
175
  async verify() {
145
176
  try {
146
177
  const response = await fetch(`${this.baseUrl}/domains`, {
@@ -153,6 +184,14 @@ var ResendProvider = class {
153
184
  return false;
154
185
  }
155
186
  }
187
+ /**
188
+ * Converts a Uint8Array to a base64-encoded string.
189
+ *
190
+ * Edge-compatible base64 encoding that works in all JavaScript runtimes.
191
+ *
192
+ * @param data - The binary data to encode
193
+ * @returns The base64-encoded string
194
+ */
156
195
  uint8ArrayToBase64(data) {
157
196
  let binary = "";
158
197
  for (let i = 0; i < data.length; i++) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/types.ts","../../src/providers/resend.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"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/types.ts","../../src/providers/resend.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"],"mappings":"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","names":[]}
@@ -26,21 +26,57 @@ import '@parsrun/types';
26
26
  * ```
27
27
  */
28
28
  declare class SendGridProvider implements EmailProvider {
29
+ /** Provider type identifier */
29
30
  readonly type: "sendgrid";
30
31
  private apiKey;
31
32
  private fromEmail;
32
33
  private fromName;
33
34
  private baseUrl;
35
+ /**
36
+ * Creates a new SendGridProvider instance.
37
+ *
38
+ * @param config - The provider configuration including API key and sender info
39
+ */
34
40
  constructor(config: EmailProviderConfig);
35
41
  private formatAddress;
36
42
  private formatAddresses;
43
+ /**
44
+ * Sends an email via the SendGrid API.
45
+ *
46
+ * @param options - The email options including recipient, subject, and content
47
+ * @returns A promise that resolves to the send result with message ID if successful
48
+ * @throws {EmailError} If the SendGrid API request fails
49
+ */
37
50
  send(options: EmailOptions): Promise<EmailResult>;
51
+ /**
52
+ * Sends multiple emails via the SendGrid API sequentially.
53
+ *
54
+ * Note: SendGrid does not support true batch sending via the standard API,
55
+ * so emails are sent one at a time.
56
+ *
57
+ * @param options - The batch email options containing emails and error handling config
58
+ * @returns A promise that resolves to the batch result with success/failure counts
59
+ */
38
60
  sendBatch(options: BatchEmailOptions): Promise<BatchEmailResult>;
61
+ /**
62
+ * Verifies the SendGrid API key by checking API scopes.
63
+ *
64
+ * @returns A promise that resolves to true if the API key is valid, false otherwise
65
+ */
39
66
  verify(): Promise<boolean>;
67
+ /**
68
+ * Converts a Uint8Array to a base64-encoded string.
69
+ *
70
+ * @param data - The binary data to encode
71
+ * @returns The base64-encoded string
72
+ */
40
73
  private uint8ArrayToBase64;
41
74
  }
42
75
  /**
43
- * Create a SendGrid provider
76
+ * Creates a SendGrid provider instance.
77
+ *
78
+ * @param config - The provider configuration including API key and sender info
79
+ * @returns A new SendGridProvider instance
44
80
  */
45
81
  declare function createSendGridProvider(config: EmailProviderConfig): SendGridProvider;
46
82
 
@@ -14,6 +14,13 @@ import {
14
14
  emailConfig
15
15
  } from "@parsrun/types";
16
16
  var EmailError = class extends Error {
17
+ /**
18
+ * Creates a new EmailError instance.
19
+ *
20
+ * @param message - Human-readable error description
21
+ * @param code - Error code from EmailErrorCodes for programmatic handling
22
+ * @param cause - The underlying error that caused this error, if any
23
+ */
17
24
  constructor(message, code, cause) {
18
25
  super(message);
19
26
  this.code = code;
@@ -34,11 +41,17 @@ var EmailErrorCodes = {
34
41
 
35
42
  // src/providers/sendgrid.ts
36
43
  var SendGridProvider = class {
44
+ /** Provider type identifier */
37
45
  type = "sendgrid";
38
46
  apiKey;
39
47
  fromEmail;
40
48
  fromName;
41
49
  baseUrl = "https://api.sendgrid.com/v3";
50
+ /**
51
+ * Creates a new SendGridProvider instance.
52
+ *
53
+ * @param config - The provider configuration including API key and sender info
54
+ */
42
55
  constructor(config) {
43
56
  this.apiKey = config.apiKey;
44
57
  this.fromEmail = config.fromEmail;
@@ -56,6 +69,13 @@ var SendGridProvider = class {
56
69
  }
57
70
  return [this.formatAddress(addresses)];
58
71
  }
72
+ /**
73
+ * Sends an email via the SendGrid API.
74
+ *
75
+ * @param options - The email options including recipient, subject, and content
76
+ * @returns A promise that resolves to the send result with message ID if successful
77
+ * @throws {EmailError} If the SendGrid API request fails
78
+ */
59
79
  async send(options) {
60
80
  const from = options.from ? this.formatAddress(options.from) : this.fromName ? { email: this.fromEmail, name: this.fromName } : { email: this.fromEmail };
61
81
  const personalization = {
@@ -130,6 +150,15 @@ var SendGridProvider = class {
130
150
  );
131
151
  }
132
152
  }
153
+ /**
154
+ * Sends multiple emails via the SendGrid API sequentially.
155
+ *
156
+ * Note: SendGrid does not support true batch sending via the standard API,
157
+ * so emails are sent one at a time.
158
+ *
159
+ * @param options - The batch email options containing emails and error handling config
160
+ * @returns A promise that resolves to the batch result with success/failure counts
161
+ */
133
162
  async sendBatch(options) {
134
163
  const results = [];
135
164
  let successful = 0;
@@ -160,6 +189,11 @@ var SendGridProvider = class {
160
189
  results
161
190
  };
162
191
  }
192
+ /**
193
+ * Verifies the SendGrid API key by checking API scopes.
194
+ *
195
+ * @returns A promise that resolves to true if the API key is valid, false otherwise
196
+ */
163
197
  async verify() {
164
198
  try {
165
199
  const response = await fetch(`${this.baseUrl}/scopes`, {
@@ -172,6 +206,12 @@ var SendGridProvider = class {
172
206
  return false;
173
207
  }
174
208
  }
209
+ /**
210
+ * Converts a Uint8Array to a base64-encoded string.
211
+ *
212
+ * @param data - The binary data to encode
213
+ * @returns The base64-encoded string
214
+ */
175
215
  uint8ArrayToBase64(data) {
176
216
  let binary = "";
177
217
  for (let i = 0; i < data.length; i++) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/types.ts","../../src/providers/sendgrid.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 - 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"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/types.ts","../../src/providers/sendgrid.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 - 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"],"mappings":"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","names":[]}