| 
									
										
										
											
												feat: Add comprehensive SMTP test suite for Deno
- Implemented SMTP client utilities in `test/helpers/smtp.client.ts` for creating test clients, sending emails, and testing connections.
- Developed SMTP protocol test utilities in `test/helpers/utils.ts` for managing TCP connections, sending commands, and handling responses.
- Created a detailed README in `test/readme.md` outlining the test framework, infrastructure, organization, and running instructions.
- Ported CMD-01: EHLO Command tests in `test/suite/smtpserver_commands/test.cmd-01.ehlo-command.test.ts` with multiple scenarios including valid and invalid hostnames.
- Ported CMD-02: MAIL FROM Command tests in `test/suite/smtpserver_commands/test.cmd-02.mail-from.test.ts` covering valid address acceptance, invalid address rejection, SIZE parameter support, and command sequence enforcement.
											
										 
											2025-10-25 15:05:11 +00:00
										 |  |  | /** | 
					
						
							|  |  |  |  * SMTP Test Utilities for Deno | 
					
						
							|  |  |  |  * Provides helper functions for testing SMTP protocol implementation | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import { net } from '../../ts/plugins.ts'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * Test result interface | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | export interface ITestResult { | 
					
						
							|  |  |  |   success: boolean; | 
					
						
							|  |  |  |   duration: number; | 
					
						
							|  |  |  |   message?: string; | 
					
						
							|  |  |  |   error?: string; | 
					
						
							|  |  |  |   details?: any; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * Test configuration interface | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | export interface ITestConfig { | 
					
						
							|  |  |  |   host: string; | 
					
						
							|  |  |  |   port: number; | 
					
						
							|  |  |  |   timeout: number; | 
					
						
							|  |  |  |   fromAddress?: string; | 
					
						
							|  |  |  |   toAddress?: string; | 
					
						
							|  |  |  |   [key: string]: any; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * Connect to SMTP server | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | export async function connectToSmtp( | 
					
						
							|  |  |  |   host: string, | 
					
						
							|  |  |  |   port: number, | 
					
						
							|  |  |  |   timeout: number = 5000 | 
					
						
							|  |  |  | ): Promise<Deno.TcpConn> { | 
					
						
							|  |  |  |   const controller = new AbortController(); | 
					
						
							|  |  |  |   const timeoutId = setTimeout(() => controller.abort(), timeout); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   try { | 
					
						
							|  |  |  |     const conn = await Deno.connect({ | 
					
						
							|  |  |  |       hostname: host, | 
					
						
							|  |  |  |       port, | 
					
						
							|  |  |  |       transport: 'tcp', | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |     clearTimeout(timeoutId); | 
					
						
							|  |  |  |     return conn; | 
					
						
							|  |  |  |   } catch (error) { | 
					
						
							|  |  |  |     clearTimeout(timeoutId); | 
					
						
							|  |  |  |     if (error instanceof Error && error.name === 'AbortError') { | 
					
						
							|  |  |  |       throw new Error(`Connection timeout after ${timeout}ms`); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     throw error; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * Read data from TCP connection with timeout | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | async function readWithTimeout( | 
					
						
							|  |  |  |   conn: Deno.TcpConn, | 
					
						
							|  |  |  |   timeout: number | 
					
						
							|  |  |  | ): Promise<string> { | 
					
						
							|  |  |  |   const buffer = new Uint8Array(4096); | 
					
						
							|  |  |  |   const controller = new AbortController(); | 
					
						
							|  |  |  |   const timeoutId = setTimeout(() => controller.abort(), timeout); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   try { | 
					
						
							|  |  |  |     const n = await conn.read(buffer); | 
					
						
							|  |  |  |     clearTimeout(timeoutId); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (n === null) { | 
					
						
							|  |  |  |       throw new Error('Connection closed'); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const decoder = new TextDecoder(); | 
					
						
							|  |  |  |     return decoder.decode(buffer.subarray(0, n)); | 
					
						
							|  |  |  |   } catch (error) { | 
					
						
							|  |  |  |     clearTimeout(timeoutId); | 
					
						
							|  |  |  |     if (error instanceof Error && error.name === 'AbortError') { | 
					
						
							|  |  |  |       throw new Error(`Read timeout after ${timeout}ms`); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     throw error; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							| 
									
										
										
											
												Add comprehensive SMTP command tests for RCPT TO, DATA, QUIT, TLS, and basic email sending
- Implement CMD-03 tests for RCPT TO command, validating recipient addresses, handling multiple recipients, and enforcing command sequence.
- Implement CMD-04 tests for DATA command, ensuring proper email content transmission, handling of dot-stuffing, large messages, and correct command sequence.
- Implement CMD-13 tests for QUIT command, verifying graceful connection termination and idempotency.
- Implement CM-01 tests for TLS connections, including STARTTLS capability and direct TLS connections.
- Implement EP-01 tests for basic email sending, covering complete SMTP transaction flow, MIME attachments, HTML emails, custom headers, and minimal emails.
											
										 
											2025-10-28 10:11:34 +00:00
										 |  |  |  * Read SMTP response without sending a command | 
					
						
							| 
									
										
										
											
												feat: Add comprehensive SMTP test suite for Deno
- Implemented SMTP client utilities in `test/helpers/smtp.client.ts` for creating test clients, sending emails, and testing connections.
- Developed SMTP protocol test utilities in `test/helpers/utils.ts` for managing TCP connections, sending commands, and handling responses.
- Created a detailed README in `test/readme.md` outlining the test framework, infrastructure, organization, and running instructions.
- Ported CMD-01: EHLO Command tests in `test/suite/smtpserver_commands/test.cmd-01.ehlo-command.test.ts` with multiple scenarios including valid and invalid hostnames.
- Ported CMD-02: MAIL FROM Command tests in `test/suite/smtpserver_commands/test.cmd-02.mail-from.test.ts` covering valid address acceptance, invalid address rejection, SIZE parameter support, and command sequence enforcement.
											
										 
											2025-10-25 15:05:11 +00:00
										 |  |  |  */ | 
					
						
							| 
									
										
										
											
												Add comprehensive SMTP command tests for RCPT TO, DATA, QUIT, TLS, and basic email sending
- Implement CMD-03 tests for RCPT TO command, validating recipient addresses, handling multiple recipients, and enforcing command sequence.
- Implement CMD-04 tests for DATA command, ensuring proper email content transmission, handling of dot-stuffing, large messages, and correct command sequence.
- Implement CMD-13 tests for QUIT command, verifying graceful connection termination and idempotency.
- Implement CM-01 tests for TLS connections, including STARTTLS capability and direct TLS connections.
- Implement EP-01 tests for basic email sending, covering complete SMTP transaction flow, MIME attachments, HTML emails, custom headers, and minimal emails.
											
										 
											2025-10-28 10:11:34 +00:00
										 |  |  | export async function readSmtpResponse( | 
					
						
							| 
									
										
										
											
												feat: Add comprehensive SMTP test suite for Deno
- Implemented SMTP client utilities in `test/helpers/smtp.client.ts` for creating test clients, sending emails, and testing connections.
- Developed SMTP protocol test utilities in `test/helpers/utils.ts` for managing TCP connections, sending commands, and handling responses.
- Created a detailed README in `test/readme.md` outlining the test framework, infrastructure, organization, and running instructions.
- Ported CMD-01: EHLO Command tests in `test/suite/smtpserver_commands/test.cmd-01.ehlo-command.test.ts` with multiple scenarios including valid and invalid hostnames.
- Ported CMD-02: MAIL FROM Command tests in `test/suite/smtpserver_commands/test.cmd-02.mail-from.test.ts` covering valid address acceptance, invalid address rejection, SIZE parameter support, and command sequence enforcement.
											
										 
											2025-10-25 15:05:11 +00:00
										 |  |  |   conn: Deno.TcpConn, | 
					
						
							|  |  |  |   expectedCode?: string, | 
					
						
							|  |  |  |   timeout: number = 5000 | 
					
						
							|  |  |  | ): Promise<string> { | 
					
						
							|  |  |  |   let buffer = ''; | 
					
						
							|  |  |  |   const startTime = Date.now(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   while (Date.now() - startTime < timeout) { | 
					
						
							|  |  |  |     const chunk = await readWithTimeout(conn, timeout - (Date.now() - startTime)); | 
					
						
							|  |  |  |     buffer += chunk; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Check if we have a complete response (ends with \r\n)
 | 
					
						
							|  |  |  |     if (buffer.includes('\r\n')) { | 
					
						
							|  |  |  |       if (expectedCode && !buffer.startsWith(expectedCode)) { | 
					
						
							|  |  |  |         throw new Error(`Expected ${expectedCode}, got: ${buffer.trim()}`); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       return buffer; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
											
												Add comprehensive SMTP command tests for RCPT TO, DATA, QUIT, TLS, and basic email sending
- Implement CMD-03 tests for RCPT TO command, validating recipient addresses, handling multiple recipients, and enforcing command sequence.
- Implement CMD-04 tests for DATA command, ensuring proper email content transmission, handling of dot-stuffing, large messages, and correct command sequence.
- Implement CMD-13 tests for QUIT command, verifying graceful connection termination and idempotency.
- Implement CM-01 tests for TLS connections, including STARTTLS capability and direct TLS connections.
- Implement EP-01 tests for basic email sending, covering complete SMTP transaction flow, MIME attachments, HTML emails, custom headers, and minimal emails.
											
										 
											2025-10-28 10:11:34 +00:00
										 |  |  |   throw new Error(`Response timeout after ${timeout}ms`); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * Send SMTP command and wait for response | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | export async function sendSmtpCommand( | 
					
						
							|  |  |  |   conn: Deno.TcpConn, | 
					
						
							|  |  |  |   command: string, | 
					
						
							|  |  |  |   expectedCode?: string, | 
					
						
							|  |  |  |   timeout: number = 5000 | 
					
						
							|  |  |  | ): Promise<string> { | 
					
						
							|  |  |  |   // Send command
 | 
					
						
							|  |  |  |   const encoder = new TextEncoder(); | 
					
						
							|  |  |  |   await conn.write(encoder.encode(command + '\r\n')); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // Read response using the dedicated function
 | 
					
						
							|  |  |  |   return await readSmtpResponse(conn, expectedCode, timeout); | 
					
						
							| 
									
										
										
											
												feat: Add comprehensive SMTP test suite for Deno
- Implemented SMTP client utilities in `test/helpers/smtp.client.ts` for creating test clients, sending emails, and testing connections.
- Developed SMTP protocol test utilities in `test/helpers/utils.ts` for managing TCP connections, sending commands, and handling responses.
- Created a detailed README in `test/readme.md` outlining the test framework, infrastructure, organization, and running instructions.
- Ported CMD-01: EHLO Command tests in `test/suite/smtpserver_commands/test.cmd-01.ehlo-command.test.ts` with multiple scenarios including valid and invalid hostnames.
- Ported CMD-02: MAIL FROM Command tests in `test/suite/smtpserver_commands/test.cmd-02.mail-from.test.ts` covering valid address acceptance, invalid address rejection, SIZE parameter support, and command sequence enforcement.
											
										 
											2025-10-25 15:05:11 +00:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * Wait for SMTP greeting (220 code) | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | export async function waitForGreeting( | 
					
						
							|  |  |  |   conn: Deno.TcpConn, | 
					
						
							|  |  |  |   timeout: number = 5000 | 
					
						
							|  |  |  | ): Promise<string> { | 
					
						
							|  |  |  |   let buffer = ''; | 
					
						
							|  |  |  |   const startTime = Date.now(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   while (Date.now() - startTime < timeout) { | 
					
						
							|  |  |  |     const chunk = await readWithTimeout(conn, timeout - (Date.now() - startTime)); | 
					
						
							|  |  |  |     buffer += chunk; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (buffer.includes('220')) { | 
					
						
							|  |  |  |       return buffer; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   throw new Error(`Greeting timeout after ${timeout}ms`); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * Perform SMTP handshake and return capabilities | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | export async function performSmtpHandshake( | 
					
						
							|  |  |  |   conn: Deno.TcpConn, | 
					
						
							|  |  |  |   hostname: string = 'test.example.com' | 
					
						
							|  |  |  | ): Promise<string[]> { | 
					
						
							|  |  |  |   const capabilities: string[] = []; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // Wait for greeting
 | 
					
						
							|  |  |  |   await waitForGreeting(conn); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // Send EHLO
 | 
					
						
							|  |  |  |   const ehloResponse = await sendSmtpCommand(conn, `EHLO ${hostname}`, '250'); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // Parse capabilities
 | 
					
						
							|  |  |  |   const lines = ehloResponse.split('\r\n'); | 
					
						
							|  |  |  |   for (const line of lines) { | 
					
						
							|  |  |  |     if (line.startsWith('250-') || line.startsWith('250 ')) { | 
					
						
							|  |  |  |       const capability = line.substring(4).trim(); | 
					
						
							|  |  |  |       if (capability) { | 
					
						
							|  |  |  |         capabilities.push(capability); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   return capabilities; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * Create multiple concurrent connections | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | export async function createConcurrentConnections( | 
					
						
							|  |  |  |   host: string, | 
					
						
							|  |  |  |   port: number, | 
					
						
							|  |  |  |   count: number, | 
					
						
							|  |  |  |   timeout: number = 5000 | 
					
						
							|  |  |  | ): Promise<Deno.TcpConn[]> { | 
					
						
							|  |  |  |   const connectionPromises = []; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   for (let i = 0; i < count; i++) { | 
					
						
							|  |  |  |     connectionPromises.push(connectToSmtp(host, port, timeout)); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   return Promise.all(connectionPromises); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * Close SMTP connection gracefully | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | export async function closeSmtpConnection(conn: Deno.TcpConn): Promise<void> { | 
					
						
							|  |  |  |   try { | 
					
						
							|  |  |  |     await sendSmtpCommand(conn, 'QUIT', '221'); | 
					
						
							|  |  |  |   } catch { | 
					
						
							|  |  |  |     // Ignore errors during QUIT
 | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   try { | 
					
						
							|  |  |  |     conn.close(); | 
					
						
							|  |  |  |   } catch { | 
					
						
							|  |  |  |     // Ignore close errors
 | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * Generate random email content | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | export function generateRandomEmail(size: number = 1024): string { | 
					
						
							|  |  |  |   const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 \r\n'; | 
					
						
							|  |  |  |   let content = ''; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   for (let i = 0; i < size; i++) { | 
					
						
							|  |  |  |     content += chars.charAt(Math.floor(Math.random() * chars.length)); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   return content; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * Create MIME message | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | export function createMimeMessage(options: { | 
					
						
							|  |  |  |   from: string; | 
					
						
							|  |  |  |   to: string; | 
					
						
							|  |  |  |   subject: string; | 
					
						
							|  |  |  |   text?: string; | 
					
						
							|  |  |  |   html?: string; | 
					
						
							|  |  |  |   attachments?: Array<{ filename: string; content: string; contentType: string }>; | 
					
						
							|  |  |  | }): string { | 
					
						
							|  |  |  |   const boundary = `----=_Part_${Date.now()}_${Math.random().toString(36).substring(2)}`; | 
					
						
							|  |  |  |   const date = new Date().toUTCString(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   let message = ''; | 
					
						
							|  |  |  |   message += `From: ${options.from}\r\n`; | 
					
						
							|  |  |  |   message += `To: ${options.to}\r\n`; | 
					
						
							|  |  |  |   message += `Subject: ${options.subject}\r\n`; | 
					
						
							|  |  |  |   message += `Date: ${date}\r\n`; | 
					
						
							|  |  |  |   message += `MIME-Version: 1.0\r\n`; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if (options.attachments && options.attachments.length > 0) { | 
					
						
							|  |  |  |     message += `Content-Type: multipart/mixed; boundary="${boundary}"\r\n`; | 
					
						
							|  |  |  |     message += '\r\n'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Text part
 | 
					
						
							|  |  |  |     if (options.text) { | 
					
						
							|  |  |  |       message += `--${boundary}\r\n`; | 
					
						
							|  |  |  |       message += 'Content-Type: text/plain; charset=utf-8\r\n'; | 
					
						
							|  |  |  |       message += 'Content-Transfer-Encoding: 8bit\r\n'; | 
					
						
							|  |  |  |       message += '\r\n'; | 
					
						
							|  |  |  |       message += options.text + '\r\n'; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // HTML part
 | 
					
						
							|  |  |  |     if (options.html) { | 
					
						
							|  |  |  |       message += `--${boundary}\r\n`; | 
					
						
							|  |  |  |       message += 'Content-Type: text/html; charset=utf-8\r\n'; | 
					
						
							|  |  |  |       message += 'Content-Transfer-Encoding: 8bit\r\n'; | 
					
						
							|  |  |  |       message += '\r\n'; | 
					
						
							|  |  |  |       message += options.html + '\r\n'; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Attachments
 | 
					
						
							|  |  |  |     const encoder = new TextEncoder(); | 
					
						
							|  |  |  |     for (const attachment of options.attachments) { | 
					
						
							|  |  |  |       message += `--${boundary}\r\n`; | 
					
						
							|  |  |  |       message += `Content-Type: ${attachment.contentType}\r\n`; | 
					
						
							|  |  |  |       message += `Content-Disposition: attachment; filename="${attachment.filename}"\r\n`; | 
					
						
							|  |  |  |       message += 'Content-Transfer-Encoding: base64\r\n'; | 
					
						
							|  |  |  |       message += '\r\n'; | 
					
						
							|  |  |  |       // Convert to base64
 | 
					
						
							|  |  |  |       const bytes = encoder.encode(attachment.content); | 
					
						
							|  |  |  |       const base64 = btoa(String.fromCharCode(...bytes)); | 
					
						
							|  |  |  |       message += base64 + '\r\n'; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     message += `--${boundary}--\r\n`; | 
					
						
							|  |  |  |   } else if (options.html && options.text) { | 
					
						
							|  |  |  |     const altBoundary = `----=_Alt_${Date.now()}_${Math.random().toString(36).substring(2)}`; | 
					
						
							|  |  |  |     message += `Content-Type: multipart/alternative; boundary="${altBoundary}"\r\n`; | 
					
						
							|  |  |  |     message += '\r\n'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Text part
 | 
					
						
							|  |  |  |     message += `--${altBoundary}\r\n`; | 
					
						
							|  |  |  |     message += 'Content-Type: text/plain; charset=utf-8\r\n'; | 
					
						
							|  |  |  |     message += 'Content-Transfer-Encoding: 8bit\r\n'; | 
					
						
							|  |  |  |     message += '\r\n'; | 
					
						
							|  |  |  |     message += options.text + '\r\n'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // HTML part
 | 
					
						
							|  |  |  |     message += `--${altBoundary}\r\n`; | 
					
						
							|  |  |  |     message += 'Content-Type: text/html; charset=utf-8\r\n'; | 
					
						
							|  |  |  |     message += 'Content-Transfer-Encoding: 8bit\r\n'; | 
					
						
							|  |  |  |     message += '\r\n'; | 
					
						
							|  |  |  |     message += options.html + '\r\n'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     message += `--${altBoundary}--\r\n`; | 
					
						
							|  |  |  |   } else if (options.html) { | 
					
						
							|  |  |  |     message += 'Content-Type: text/html; charset=utf-8\r\n'; | 
					
						
							|  |  |  |     message += 'Content-Transfer-Encoding: 8bit\r\n'; | 
					
						
							|  |  |  |     message += '\r\n'; | 
					
						
							|  |  |  |     message += options.html; | 
					
						
							|  |  |  |   } else { | 
					
						
							|  |  |  |     message += 'Content-Type: text/plain; charset=utf-8\r\n'; | 
					
						
							|  |  |  |     message += 'Content-Transfer-Encoding: 8bit\r\n'; | 
					
						
							|  |  |  |     message += '\r\n'; | 
					
						
							|  |  |  |     message += options.text || ''; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   return message; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * Measure operation time | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | export async function measureTime<T>( | 
					
						
							|  |  |  |   operation: () => Promise<T> | 
					
						
							|  |  |  | ): Promise<{ result: T; duration: number }> { | 
					
						
							|  |  |  |   const startTime = Date.now(); | 
					
						
							|  |  |  |   const result = await operation(); | 
					
						
							|  |  |  |   const duration = Date.now() - startTime; | 
					
						
							|  |  |  |   return { result, duration }; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * Retry operation with exponential backoff | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | export async function retryOperation<T>( | 
					
						
							|  |  |  |   operation: () => Promise<T>, | 
					
						
							|  |  |  |   maxRetries: number = 3, | 
					
						
							|  |  |  |   initialDelay: number = 1000 | 
					
						
							|  |  |  | ): Promise<T> { | 
					
						
							|  |  |  |   let lastError: Error; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   for (let i = 0; i < maxRetries; i++) { | 
					
						
							|  |  |  |     try { | 
					
						
							|  |  |  |       return await operation(); | 
					
						
							|  |  |  |     } catch (error) { | 
					
						
							|  |  |  |       lastError = error as Error; | 
					
						
							|  |  |  |       if (i < maxRetries - 1) { | 
					
						
							|  |  |  |         const delay = initialDelay * Math.pow(2, i); | 
					
						
							|  |  |  |         await new Promise((resolve) => setTimeout(resolve, delay)); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   throw lastError!; | 
					
						
							|  |  |  | } |