import * as plugins from './smartmail.plugins.js'; import { EmailAddressValidator } from './smartmail.classes.emailaddressvalidator.js'; export type EmailAddress = string; export type EmailAddressList = EmailAddress[]; export interface ISmartmailOptions { from: EmailAddress; to?: EmailAddressList; cc?: EmailAddressList; bcc?: EmailAddressList; replyTo?: EmailAddress; subject: string; body: string; htmlBody?: string; creationObjectRef?: T; headers?: Record; priority?: 'high' | 'normal' | 'low'; validateEmails?: boolean; } export interface IMimeAttachment { filename: string; content: Buffer; contentType: string; } /** * A standard representation for emails with advanced features */ export class Smartmail { public options: ISmartmailOptions; public attachments: plugins.smartfile.SmartFile[] = []; private emailValidator: EmailAddressValidator; constructor(optionsArg: ISmartmailOptions) { // Set default options this.options = { validateEmails: false, to: [], cc: [], bcc: [], headers: {}, priority: 'normal', ...optionsArg }; this.emailValidator = new EmailAddressValidator(); } /** * Adds an attachment to the email * @param smartfileArg The file to attach */ public addAttachment(smartfileArg: plugins.smartfile.SmartFile) { this.attachments.push(smartfileArg); } /** * Gets the creation object reference * @returns The creation object reference */ public getCreationObject(): T { return this.options.creationObjectRef; } /** * Gets the processed subject with template variables applied * @param dataArg Data to apply to the template * @returns Processed subject */ public getSubject(dataArg: any = {}): string { const smartmustache = new plugins.smartmustache.SmartMustache(this.options.subject); return smartmustache.applyData(dataArg); } /** * Gets the processed plain text body with template variables applied * @param dataArg Data to apply to the template * @returns Processed body */ public getBody(dataArg: any = {}): string { const smartmustache = new plugins.smartmustache.SmartMustache(this.options.body); return smartmustache.applyData(dataArg); } /** * Gets the processed HTML body with template variables applied * @param dataArg Data to apply to the template * @returns Processed HTML body or null if not set */ public getHtmlBody(dataArg: any = {}): string | null { if (!this.options.htmlBody) { return null; } const smartmustache = new plugins.smartmustache.SmartMustache(this.options.htmlBody); return smartmustache.applyData(dataArg); } /** * Adds a recipient to the email * @param email Email address to add * @param type Type of recipient (to, cc, bcc) */ public addRecipient(email: EmailAddress, type: 'to' | 'cc' | 'bcc' = 'to'): void { if (!this.options[type]) { this.options[type] = []; } this.options[type]!.push(email); } /** * Adds multiple recipients to the email * @param emails Email addresses to add * @param type Type of recipients (to, cc, bcc) */ public addRecipients(emails: EmailAddressList, type: 'to' | 'cc' | 'bcc' = 'to'): void { if (!this.options[type]) { this.options[type] = []; } this.options[type] = [...this.options[type]!, ...emails]; } /** * Sets the reply-to address * @param email Email address for reply-to */ public setReplyTo(email: EmailAddress): void { this.options.replyTo = email; } /** * Sets the priority of the email * @param priority Priority level */ public setPriority(priority: 'high' | 'normal' | 'low'): void { this.options.priority = priority; } /** * Adds a custom header to the email * @param name Header name * @param value Header value */ public addHeader(name: string, value: string): void { if (!this.options.headers) { this.options.headers = {}; } this.options.headers[name] = value; } /** * Validates all email addresses in the email * @returns Promise resolving to validation results */ public async validateAllEmails(): Promise> { const results: Record = {}; const emails: EmailAddress[] = []; // Collect all emails if (this.options.from) emails.push(this.options.from); if (this.options.replyTo) emails.push(this.options.replyTo); if (this.options.to) emails.push(...this.options.to); if (this.options.cc) emails.push(...this.options.cc); if (this.options.bcc) emails.push(...this.options.bcc); // Validate each email for (const email of emails) { const validationResult = await this.emailValidator.validate(email); results[email] = validationResult.valid; } return results; } /** * Converts the email to a MIME format object for sending * @param dataArg Data to apply to templates * @returns MIME format object */ public async toMimeFormat(dataArg: any = {}): Promise { // Validate emails if option is enabled if (this.options.validateEmails) { const validationResults = await this.validateAllEmails(); const invalidEmails = Object.entries(validationResults) .filter(([_, valid]) => !valid) .map(([email]) => email); if (invalidEmails.length > 0) { throw new Error(`Invalid email addresses: ${invalidEmails.join(', ')}`); } } // Build MIME parts const subject = this.getSubject(dataArg); const textBody = this.getBody(dataArg); const htmlBody = this.getHtmlBody(dataArg); // Convert attachments to MIME format const mimeAttachments: IMimeAttachment[] = await Promise.all( this.attachments.map(async (file) => { return { filename: file.path.split('/').pop()!, content: file.contentBuffer, contentType: 'application/octet-stream' }; }) ); // Build email format object const mimeObj: any = { from: this.options.from, subject, text: textBody, attachments: mimeAttachments, headers: { ...this.options.headers } }; // Add optional fields if (this.options.to && this.options.to.length > 0) { mimeObj.to = this.options.to; } if (this.options.cc && this.options.cc.length > 0) { mimeObj.cc = this.options.cc; } if (this.options.bcc && this.options.bcc.length > 0) { mimeObj.bcc = this.options.bcc; } if (this.options.replyTo) { mimeObj.replyTo = this.options.replyTo; } if (htmlBody) { mimeObj.html = htmlBody; } // Add priority headers if specified if (this.options.priority === 'high') { mimeObj.headers['X-Priority'] = '1'; mimeObj.headers['X-MSMail-Priority'] = 'High'; mimeObj.headers['Importance'] = 'High'; } else if (this.options.priority === 'low') { mimeObj.headers['X-Priority'] = '5'; mimeObj.headers['X-MSMail-Priority'] = 'Low'; mimeObj.headers['Importance'] = 'Low'; } return mimeObj; } }