feat(structure): Use unified Email class
This commit is contained in:
@ -3,6 +3,7 @@ import * as paths from '../../paths.js';
|
||||
import { logger } from '../../logger.js';
|
||||
import { SecurityLogger, SecurityLogLevel, SecurityEventType } from '../../security/index.js';
|
||||
import { LRUCache } from 'lru-cache';
|
||||
import type { Email } from './classes.email.js';
|
||||
|
||||
/**
|
||||
* Bounce types for categorizing the reasons for bounces
|
||||
@ -380,7 +381,7 @@ export class BounceManager {
|
||||
* @param bounceEmail The email containing bounce information
|
||||
* @returns Processed bounce record or null if not a bounce
|
||||
*/
|
||||
public async processBounceEmail(bounceEmail: plugins.smartmail.Smartmail<any>): Promise<BounceRecord | null> {
|
||||
public async processBounceEmail(bounceEmail: Email): Promise<BounceRecord | null> {
|
||||
try {
|
||||
// Check if this is a bounce notification
|
||||
const subject = bounceEmail.getSubject();
|
||||
@ -435,7 +436,7 @@ export class BounceManager {
|
||||
if (!recipient) {
|
||||
logger.log('warn', 'Could not extract recipient from bounce notification', {
|
||||
subject,
|
||||
sender: bounceEmail.options.from
|
||||
sender: bounceEmail.from
|
||||
});
|
||||
return null;
|
||||
}
|
||||
@ -449,7 +450,7 @@ export class BounceManager {
|
||||
// Create bounce data
|
||||
const bounceData: Partial<BounceRecord> = {
|
||||
recipient,
|
||||
sender: bounceEmail.options.from,
|
||||
sender: bounceEmail.from,
|
||||
domain: recipient.split('@')[1],
|
||||
subject: bounceEmail.getSubject(),
|
||||
diagnosticCode,
|
||||
|
@ -640,6 +640,34 @@ export class Email {
|
||||
return this.from;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the subject (Smartmail compatibility method)
|
||||
* @returns The email subject
|
||||
*/
|
||||
public getSubject(): string {
|
||||
return this.subject;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the body content (Smartmail compatibility method)
|
||||
* @param isHtml Whether to return HTML content if available
|
||||
* @returns The email body (HTML if requested and available, otherwise plain text)
|
||||
*/
|
||||
public getBody(isHtml: boolean = false): string {
|
||||
if (isHtml && this.html) {
|
||||
return this.html;
|
||||
}
|
||||
return this.text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the from address (Smartmail compatibility method)
|
||||
* @returns The sender email address
|
||||
*/
|
||||
public getFrom(): string {
|
||||
return this.from;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the message ID
|
||||
* @returns The message ID
|
||||
|
@ -1,12 +1,11 @@
|
||||
import * as plugins from '../../plugins.js';
|
||||
import { EmailService } from '../services/classes.emailservice.js';
|
||||
import { logger } from '../../logger.js';
|
||||
import { Email, type IEmailOptions } from './classes.email.js';
|
||||
|
||||
export class RuleManager {
|
||||
public emailRef: EmailService;
|
||||
public smartruleInstance = new plugins.smartrule.SmartRule<
|
||||
plugins.smartmail.Smartmail<any>
|
||||
>();
|
||||
public smartruleInstance = new plugins.smartrule.SmartRule<Email>();
|
||||
|
||||
constructor(emailRefArg: EmailService) {
|
||||
this.emailRef = emailRefArg;
|
||||
@ -50,19 +49,9 @@ export class RuleManager {
|
||||
|
||||
// Set up event listener on UnifiedEmailServer if available
|
||||
if (this.emailRef.unifiedEmailServer) {
|
||||
this.emailRef.unifiedEmailServer.on('emailProcessed', (email, mode, rule) => {
|
||||
this.emailRef.unifiedEmailServer.on('emailProcessed', (email: Email, mode, rule) => {
|
||||
// Process email through rule system
|
||||
// Convert Email to Smartmail format
|
||||
// Convert Email object to Smartmail format
|
||||
const smartmail = new plugins.smartmail.Smartmail({
|
||||
// Use standard fields
|
||||
from: email.from,
|
||||
subject: email.subject || '',
|
||||
body: email.text || email.html || ''
|
||||
});
|
||||
|
||||
// Process with rules
|
||||
this.smartruleInstance.makeDecision(smartmail);
|
||||
this.smartruleInstance.makeDecision(email);
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -78,36 +67,44 @@ export class RuleManager {
|
||||
// Parse the email content into proper format
|
||||
const parsedContent = await plugins.mailparser.simpleParser(emailContent);
|
||||
|
||||
// Create a Smartmail object with the parsed content
|
||||
const fetchedSmartmail = new plugins.smartmail.Smartmail({
|
||||
// Use standardized fields that are always available
|
||||
body: parsedContent.text || parsedContent.html || '',
|
||||
// Create an Email object with the parsed content
|
||||
const fromAddress = Array.isArray(parsedContent.from)
|
||||
? parsedContent.from[0]?.text || 'unknown@example.com'
|
||||
: parsedContent.from?.text || 'unknown@example.com';
|
||||
|
||||
const toAddress = Array.isArray(parsedContent.to)
|
||||
? parsedContent.to[0]?.text || 'unknown@example.com'
|
||||
: parsedContent.to?.text || 'unknown@example.com';
|
||||
|
||||
const fetchedEmail = new Email({
|
||||
from: fromAddress,
|
||||
to: toAddress,
|
||||
subject: parsedContent.subject || '',
|
||||
// Use a default from address if not present
|
||||
from: parsedContent.from?.text || 'unknown@example.com'
|
||||
text: parsedContent.text || '',
|
||||
html: parsedContent.html || undefined
|
||||
});
|
||||
|
||||
console.log('=======================');
|
||||
console.log('Received a mail:');
|
||||
console.log(`From: ${fetchedSmartmail.options?.from || 'unknown'}`);
|
||||
console.log(`Subject: ${fetchedSmartmail.options?.subject || 'no subject'}`);
|
||||
console.log(`From: ${fetchedEmail.from}`);
|
||||
console.log(`Subject: ${fetchedEmail.subject}`);
|
||||
console.log('^^^^^^^^^^^^^^^^^^^^^^^');
|
||||
|
||||
logger.log(
|
||||
'info',
|
||||
`email from ${fetchedSmartmail.options?.from || 'unknown'} with subject '${fetchedSmartmail.options?.subject || 'no subject'}'`,
|
||||
`email from ${fetchedEmail.from} with subject '${fetchedEmail.subject}'`,
|
||||
{
|
||||
eventType: 'receivedEmail',
|
||||
provider: 'unified',
|
||||
email: {
|
||||
from: fetchedSmartmail.options?.from || 'unknown',
|
||||
subject: fetchedSmartmail.options?.subject || 'no subject',
|
||||
from: fetchedEmail.from,
|
||||
subject: fetchedEmail.subject,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
// Process with rules
|
||||
this.smartruleInstance.makeDecision(fetchedSmartmail);
|
||||
this.smartruleInstance.makeDecision(fetchedEmail);
|
||||
} catch (error) {
|
||||
logger.log('error', `Failed to process incoming email: ${error.message}`, {
|
||||
eventType: 'emailError',
|
||||
@ -135,9 +132,9 @@ export class RuleManager {
|
||||
for (const forward of forwards) {
|
||||
this.smartruleInstance.createRule(
|
||||
10,
|
||||
async (smartmailArg) => {
|
||||
async (emailArg: Email) => {
|
||||
const matched = forward.originalToAddress.reduce<boolean>((prevValue, currentValue) => {
|
||||
return smartmailArg.options.creationObjectRef.To.includes(currentValue) || prevValue;
|
||||
return emailArg.to.some(to => to.includes(currentValue)) || prevValue;
|
||||
}, false);
|
||||
if (matched) {
|
||||
console.log('Forward rule matched');
|
||||
@ -147,48 +144,46 @@ export class RuleManager {
|
||||
return 'continue';
|
||||
}
|
||||
},
|
||||
async (smartmailArg: plugins.smartmail.Smartmail<any>) => {
|
||||
async (emailArg: Email) => {
|
||||
forward.forwardedToAddress.map(async (toArg) => {
|
||||
const forwardedSmartMail = new plugins.smartmail.Smartmail({
|
||||
body:
|
||||
const forwardedEmail = new Email({
|
||||
from: 'forwarder@mail.lossless.one',
|
||||
to: toArg,
|
||||
subject: `Forwarded mail for '${emailArg.to.join(', ')}'`,
|
||||
html:
|
||||
`
|
||||
<div style="background: #CCC; padding: 10px; border-radius: 3px;">
|
||||
<div><b>Original Sender:</b></div>
|
||||
<div>${smartmailArg.options.creationObjectRef.From}</div>
|
||||
<div>${emailArg.from}</div>
|
||||
<div><b>Original Recipient:</b></div>
|
||||
<div>${smartmailArg.options.creationObjectRef.To}</div>
|
||||
<div>${emailArg.to.join(', ')}</div>
|
||||
<div><b>Forwarded to:</b></div>
|
||||
<div>${forward.forwardedToAddress.reduce<string>((pVal, cVal) => {
|
||||
return `${pVal ? pVal + ', ' : ''}${cVal}`;
|
||||
}, null)}</div>
|
||||
<div><b>Subject:</b></div>
|
||||
<div>${smartmailArg.getSubject()}</div>
|
||||
<div>${emailArg.getSubject()}</div>
|
||||
<div><b>The original body can be found below.</b></div>
|
||||
</div>
|
||||
` + smartmailArg.getBody(),
|
||||
from: 'forwarder@mail.lossless.one',
|
||||
subject: `Forwarded mail for '${smartmailArg.options.creationObjectRef.To}'`,
|
||||
` + emailArg.getBody(true),
|
||||
text: `Forwarded mail from ${emailArg.from} to ${emailArg.to.join(', ')}\n\n${emailArg.getBody()}`,
|
||||
attachments: emailArg.attachments
|
||||
});
|
||||
for (const attachment of smartmailArg.attachments) {
|
||||
forwardedSmartMail.addAttachment(attachment);
|
||||
}
|
||||
|
||||
// Use the EmailService's sendEmail method to send with the appropriate provider
|
||||
await this.emailRef.sendEmail(forwardedSmartMail, toArg);
|
||||
await this.emailRef.sendEmail(forwardedEmail);
|
||||
|
||||
console.log(`forwarded mail to ${toArg}`);
|
||||
logger.log(
|
||||
'info',
|
||||
`email from ${
|
||||
smartmailArg.options.creationObjectRef.From
|
||||
} to ${toArg} with subject '${smartmailArg.getSubject()}'`,
|
||||
`email from ${emailArg.from} to ${toArg} with subject '${emailArg.getSubject()}'`,
|
||||
{
|
||||
eventType: 'forwardedEmail',
|
||||
email: {
|
||||
from: smartmailArg.options.creationObjectRef.From,
|
||||
to: smartmailArg.options.creationObjectRef.To,
|
||||
from: emailArg.from,
|
||||
to: emailArg.to.join(', '),
|
||||
forwardedTo: toArg,
|
||||
subject: smartmailArg.options.creationObjectRef.Subject,
|
||||
subject: emailArg.subject,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
@ -1,6 +1,7 @@
|
||||
import * as plugins from '../../plugins.js';
|
||||
import * as paths from '../../paths.js';
|
||||
import { logger } from '../../logger.js';
|
||||
import { Email, type IEmailOptions, type IAttachment } from './classes.email.js';
|
||||
|
||||
/**
|
||||
* Email template type definition
|
||||
@ -40,7 +41,7 @@ export enum TemplateCategory {
|
||||
}
|
||||
|
||||
/**
|
||||
* Enhanced template manager using smartmail's capabilities
|
||||
* Enhanced template manager using Email class for template rendering
|
||||
*/
|
||||
export class TemplateManager {
|
||||
private templates: Map<string, IEmailTemplate> = new Map();
|
||||
@ -191,80 +192,74 @@ export class TemplateManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Smartmail instance from a template
|
||||
* Create an Email instance from a template
|
||||
* @param templateId The template ID
|
||||
* @param context The template context data
|
||||
* @returns A configured Smartmail instance
|
||||
* @returns A configured Email instance
|
||||
*/
|
||||
public async createSmartmail<T = any>(
|
||||
public async createEmail<T = any>(
|
||||
templateId: string,
|
||||
context?: ITemplateContext
|
||||
): Promise<plugins.smartmail.Smartmail<T>> {
|
||||
): Promise<Email> {
|
||||
const template = this.getTemplate(templateId);
|
||||
|
||||
if (!template) {
|
||||
throw new Error(`Template with ID '${templateId}' not found`);
|
||||
}
|
||||
|
||||
// Create Smartmail instance with template content
|
||||
const smartmail = new plugins.smartmail.Smartmail<T>({
|
||||
from: template.from || this.defaultConfig.from,
|
||||
subject: template.subject,
|
||||
body: template.bodyHtml || template.bodyText || '',
|
||||
creationObjectRef: context as T
|
||||
});
|
||||
// Build attachments array for Email
|
||||
const attachments: IAttachment[] = [];
|
||||
|
||||
// Add any template attachments
|
||||
if (template.attachments && template.attachments.length > 0) {
|
||||
for (const attachment of template.attachments) {
|
||||
// Load attachment file
|
||||
try {
|
||||
const attachmentPath = plugins.path.isAbsolute(attachment.path)
|
||||
? attachment.path
|
||||
: plugins.path.join(paths.MtaAttachmentsDir, attachment.path);
|
||||
|
||||
// Use appropriate SmartFile method - either read from file or create with empty buffer
|
||||
// For a file path, use the fromFilePath static method
|
||||
const file = await plugins.smartfile.SmartFile.fromFilePath(attachmentPath);
|
||||
// Read the file
|
||||
const fileBuffer = await plugins.fs.promises.readFile(attachmentPath);
|
||||
|
||||
// Set content type if specified
|
||||
if (attachment.contentType) {
|
||||
(file as any).contentType = attachment.contentType;
|
||||
}
|
||||
|
||||
smartmail.addAttachment(file);
|
||||
attachments.push({
|
||||
filename: attachment.name,
|
||||
content: fileBuffer,
|
||||
contentType: attachment.contentType || 'application/octet-stream'
|
||||
});
|
||||
} catch (error) {
|
||||
logger.log('error', `Failed to add attachment '${attachment.name}': ${error.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Apply template variables if context provided
|
||||
if (context) {
|
||||
// Use applyVariables from smartmail v2.1.0+
|
||||
smartmail.applyVariables(context);
|
||||
}
|
||||
// Create Email instance with template content
|
||||
const emailOptions: IEmailOptions = {
|
||||
from: template.from || this.defaultConfig.from,
|
||||
subject: template.subject,
|
||||
text: template.bodyText || '',
|
||||
html: template.bodyHtml,
|
||||
to: '', // Will be set when sending
|
||||
attachments,
|
||||
variables: context || {}
|
||||
};
|
||||
|
||||
return smartmail;
|
||||
return new Email(emailOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and completely process a Smartmail instance from a template
|
||||
* Create and completely process an Email instance from a template
|
||||
* @param templateId The template ID
|
||||
* @param context The template context data
|
||||
* @returns A complete, processed Smartmail instance ready to send
|
||||
* @returns A complete, processed Email instance ready to send
|
||||
*/
|
||||
public async prepareEmail<T = any>(
|
||||
templateId: string,
|
||||
context: ITemplateContext = {}
|
||||
): Promise<plugins.smartmail.Smartmail<T>> {
|
||||
const smartmail = await this.createSmartmail<T>(templateId, context);
|
||||
): Promise<Email> {
|
||||
const email = await this.createEmail<T>(templateId, context);
|
||||
|
||||
// Pre-compile all mustache templates (subject, body)
|
||||
smartmail.getSubject();
|
||||
smartmail.getBody();
|
||||
// Email class processes variables when needed, no pre-compilation required
|
||||
|
||||
return smartmail;
|
||||
return email;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -277,8 +272,8 @@ export class TemplateManager {
|
||||
templateId: string,
|
||||
context: ITemplateContext = {}
|
||||
): Promise<string> {
|
||||
const smartmail = await this.prepareEmail(templateId, context);
|
||||
return smartmail.toMimeFormat();
|
||||
const email = await this.prepareEmail(templateId, context);
|
||||
return email.toRFC822String(context);
|
||||
}
|
||||
|
||||
|
||||
|
Reference in New Issue
Block a user