BREAKING CHANGE(platformservice): Remove deprecated AIBridge module and update email service to use the MTA connector; update dependency versions and adjust build scripts in package.json.

This commit is contained in:
2025-03-15 16:04:03 +00:00
parent 90d3e75963
commit a4d79c2d01
18 changed files with 472 additions and 517 deletions

View File

@ -3,6 +3,6 @@
*/
export const commitinfo = {
name: '@serve.zone/platformservice',
version: '1.1.2',
version: '2.0.0',
description: 'A multifaceted platform service handling mail, SMS, letter delivery, and AI services.'
}

View File

@ -1,50 +0,0 @@
import * as plugins from './aibridge.plugins.js';
import * as paths from './aibridge.paths.js';
import { AiBridgeDb } from './aibridge.classes.aibridgedb.js';
import { OpenAiBridge } from './aibridge.classes.openaibridge.js';
export class AiBridge {
public projectinfo: plugins.projectinfo.ProjectInfo;
public serverInstance: plugins.loleServiceserver.ServiceServer;
public serviceQenv = new plugins.qenv.Qenv('./', './.nogit');
public aibridgeDb: AiBridgeDb;
public openAiBridge: OpenAiBridge;
public typedrouter = new plugins.typedrequest.TypedRouter();
public async start() {
this.aibridgeDb = new AiBridgeDb(this);
await this.aibridgeDb.start();
this.projectinfo = new plugins.projectinfo.ProjectInfo(paths.packageDir);
this.openAiBridge = new OpenAiBridge(this);
await this.openAiBridge.start();
// server
this.serverInstance = new plugins.loleServiceserver.ServiceServer({
serviceDomain: 'aibridge.lossless.one',
serviceName: 'aibridge',
serviceVersion: this.projectinfo.npm.version,
addCustomRoutes: async (serverArg) => {
// any custom route configs go here
},
});
// lets implemenet the actual typedrequest functions
this.typedrouter.addTypedHandler<plugins.lointAiBridge.requests.IReq_Chat>(new plugins.typedrequest.TypedHandler('chat', async reqArg => {
const resultChat = await this.openAiBridge.chat(reqArg.chat.systemMessage, reqArg.chat.messages[reqArg.chat.messages.length - 1].content, reqArg.chat.messages);
return {
chat: reqArg.chat,
latestMessage: resultChat.message.content,
}
}))
await this.serverInstance.start();
this.serverInstance.typedServer.typedrouter.addTypedRouter(this.typedrouter);
}
public async stop() {
await this.serverInstance.stop();
await this.aibridgeDb.stop();
}
}

View File

@ -1,25 +0,0 @@
import * as plugins from './aibridge.plugins.js';
import { AiBridge } from './aibridge.classes.aibridge.js';
export class AiBridgeDb {
public smartdataDb: plugins.smartdata.SmartdataDb;
public aibridgeRef: AiBridge;
constructor(aibridgeRefArg: AiBridge) {
this.aibridgeRef = aibridgeRefArg;
}
public async start() {
this.smartdataDb = new plugins.smartdata.SmartdataDb({
mongoDbUser: await this.aibridgeRef.serviceQenv.getEnvVarOnDemand('MONGO_DB_USER'),
mongoDbName: await this.aibridgeRef.serviceQenv.getEnvVarOnDemand('MONGO_DB_NAME'),
mongoDbPass: await this.aibridgeRef.serviceQenv.getEnvVarOnDemand('MONGO_DB_PASS'),
mongoDbUrl: await this.aibridgeRef.serviceQenv.getEnvVarOnDemand('MONGO_DB_URL'),
});
await this.smartdataDb.init();
}
public async stop() {
await this.smartdataDb.close();
}
}

View File

@ -1,58 +0,0 @@
import { AiBridge } from './aibridge.classes.aibridge.js';
import * as plugins from './aibridge.plugins.js';
import * as paths from './aibridge.paths.js';
export class OpenAiBridge {
public aiBridgeRef: AiBridge;
public openAiApiClient: plugins.openai.default;
constructor(aiBridgeRefArg: AiBridge) {
this.aiBridgeRef = aiBridgeRefArg;
}
public async start() {
const openAiToken = await this.aiBridgeRef.serviceQenv.getEnvVarOnDemand('OPENAI_TOKEN');
this.openAiApiClient = new plugins.openai.default({
apiKey: openAiToken,
dangerouslyAllowBrowser: true,
});
}
public async stop() {}
public async chat(
systemMessage: string,
userMessage: string,
messageHistory: {
role: 'assistant' | 'user';
content: string;
}[]
) {
const result = await this.openAiApiClient.chat.completions.create({
model: 'gpt-4-turbo-preview',
messages: [
{ role: 'system', content: systemMessage },
...messageHistory,
{ role: 'user', content: userMessage },
],
});
return {
message: result.choices[0].message,
};
}
public async audio(messageArg: string) {
const done = plugins.smartpromise.defer();
const result = await this.openAiApiClient.audio.speech.create({
model: 'tts-1-hd',
input: messageArg,
voice: 'nova',
response_format: 'mp3',
speed: 1,
});
const stream = result.body.pipe(plugins.smartfile.fsStream.createWriteStream(plugins.path.join(paths.nogitDir, 'output.mp3')));
stream.on('finish', () => {
done.resolve();
});
return done.promise;
}
}

View File

@ -1,16 +0,0 @@
import * as plugins from './aibridge.plugins.js';
export const packageDir = plugins.path.join(
plugins.smartpath.get.dirnameFromImportMetaUrl(import.meta.url),
'../'
);
export const assetsDir = plugins.path.join(
packageDir,
'./assets/'
);
export const nogitDir = plugins.path.join(
packageDir,
'./.nogit/'
);

View File

@ -1,32 +0,0 @@
// node native
import * as path from 'path';
export { path };
// @losslessone_private scope
import * as loleServiceserver from '@losslessone_private/lole-serviceserver';
import * as lointAiBridge from '@losslessone_private/loint-aibridge';
export { loleServiceserver, lointAiBridge };
// apiglobal scope
import * as typedrequest from '@api.global/typedrequest';
export {
typedrequest,
}
// pushrocks scope
import * as projectinfo from '@push.rocks/projectinfo';
import * as qenv from '@push.rocks/qenv';
import * as smartdata from '@push.rocks/smartdata';
import * as smartfile from '@push.rocks/smartfile';
import * as smartpath from '@push.rocks/smartpath';
import * as smartpromise from '@push.rocks/smartpromise';
export { projectinfo, qenv, smartdata, smartfile, smartpath, smartpromise };
// thirdparty scope
import * as antrophic from '@anthropic-ai/sdk';
import * as openai from 'openai';
export { antrophic as anthropic, openai };

View File

@ -1,17 +0,0 @@
import { AiBridge } from './aibridge.classes.aibridge.js';
export {
AiBridge,
}
let aibridgeInstance: AiBridge;
export const runCli = async () => {
aibridgeInstance = new AiBridge();
await aibridgeInstance.start();
};
export const stop = async () => {
if (aibridgeInstance) {
await aibridgeInstance.stop();
}
};

View File

@ -4,12 +4,21 @@ import { logger } from '../logger.js';
export class ApiManager {
public emailRef: EmailService;
public typedRouter = new plugins.typedrequest.TypedRouter();
constructor(emailRefArg: EmailService) {
this.emailRef = emailRefArg;
this.emailRef.typedrouter.addTypedRouter(this.typedRouter);
// Register API endpoints
this.registerApiEndpoints();
}
/**
* Register API endpoints for email functionality
*/
private registerApiEndpoints() {
// Register the SendEmail endpoint
this.typedRouter.addTypedHandler<plugins.servezoneInterfaces.platformservice.mta.IRequest_SendEmail>(
new plugins.typedrequest.TypedHandler('sendEmail', async (requestData) => {
const mailToSend = new plugins.smartmail.Smartmail({
@ -30,10 +39,12 @@ export class ApiManager {
}
}
await this.emailRef.mailgunConnector.sendEmail(mailToSend, requestData.to, {});
// Send email through the service which will route to the appropriate connector
const emailId = await this.emailRef.sendEmail(mailToSend, requestData.to, {});
logger.log(
'info',
`send an email to ${requestData.to} with subject '${mailToSend.getSubject()}'`,
`sent an email to ${requestData.to} with subject '${mailToSend.getSubject()}'`,
{
eventType: 'sentEmail',
email: {
@ -42,10 +53,35 @@ export class ApiManager {
},
}
);
return {
responseId: 'abc', // TODO: generate proper response id
responseId: emailId,
};
})
);
// Add endpoint to check email status
this.typedRouter.addTypedHandler<{ emailId: string }>(
new plugins.typedrequest.TypedHandler('checkEmailStatus', async (requestData) => {
// If MTA is enabled, use it to check status
if (this.emailRef.mtaConnector) {
const status = await this.emailRef.mtaConnector.checkEmailStatus(requestData.emailId);
return status;
}
// For Mailgun, we don't have a status check implementation currently
return {
status: 'unknown',
details: { message: 'Status tracking not available for current provider' }
};
})
);
// Add statistics endpoint
this.typedRouter.addTypedHandler<void>(
new plugins.typedrequest.TypedHandler('getEmailStats', async () => {
return this.emailRef.getStats();
})
);
}
}
}

View File

@ -1,30 +0,0 @@
import * as plugins from './email.plugins.js';
import { EmailService } from './email.classes.emailservice.js';
export class MailgunConnector {
public emailRef: EmailService;
public mailgunAccount: plugins.mailgun.MailgunAccount;
constructor(emailRefArg: EmailService) {
this.emailRef = emailRefArg;
this.mailgunAccount = new plugins.mailgun.MailgunAccount({
apiToken: this.emailRef.qenv.getEnvVarOnDemand('MAILGUN_API_TOKEN'),
region: 'eu',
});
this.mailgunAccount.addSmtpCredentials(
this.emailRef.qenv.getEnvVarOnDemand('MAILGUN_SMTP_CREDENTIALS')
);
}
public async sendEmail(
smartMailArg: plugins.smartmail.Smartmail<any>,
toArg: string,
dataArg: any = {}
) {
this.mailgunAccount.sendSmartMail(smartMailArg, toArg, dataArg);
}
public async receiveEmail(messageUrl: string) {
return await this.mailgunAccount.retrieveSmartMailFromMessageUrl(messageUrl);
}
}

View File

@ -0,0 +1,169 @@
import * as plugins from '../plugins.js';
import { EmailService } from './email.classes.emailservice.js';
import { logger } from '../logger.js';
// Import MTA classes
import {
MtaService,
Email as MtaEmail,
type IEmailOptions,
DeliveryStatus,
type IAttachment
} from '../mta/index.js';
export class MtaConnector {
public emailRef: EmailService;
private mtaService: MtaService;
constructor(emailRefArg: EmailService, mtaService?: MtaService) {
this.emailRef = emailRefArg;
this.mtaService = mtaService || this.emailRef.mtaService;
}
/**
* Send an email using the MTA service
* @param smartmail The email to send
* @param toAddresses Recipients (comma-separated or array)
* @param options Additional options
*/
public async sendEmail(
smartmail: plugins.smartmail.Smartmail<>,
toAddresses: string | string[],
options: any = {}
): Promise<string> {
try {
// Process recipients
const toArray = Array.isArray(toAddresses)
? toAddresses
: toAddresses.split(',').map(addr => addr.trim());
// Map SmartMail attachments to MTA attachments
const attachments: IAttachment[] = smartmail.attachments.map(attachment => {
return {
filename: attachment.parsedPath.base,
content: Buffer.from(attachment.contentBuffer),
contentType: (attachment as any)?.getContentType?.() || 'application/octet-stream' // TODO: revisit after smartfile has been updated
};
});
// Create MTA Email
const mtaEmail = new MtaEmail({
from: smartmail.options.from,
to: toArray,
subject: smartmail.getSubject(),
text: smartmail.getBody(false), // Plain text version
html: smartmail.getBody(true), // HTML version
attachments
});
// Send using MTA
const emailId = await this.mtaService.send(mtaEmail);
logger.log('info', `Email sent via MTA to ${toAddresses}`, {
eventType: 'sentEmail',
provider: 'mta',
emailId,
to: toAddresses
});
return emailId;
} catch (error) {
logger.log('error', `Failed to send email via MTA: ${error.message}`, {
eventType: 'emailError',
provider: 'mta',
error: error.message
});
throw error;
}
}
/**
* Retrieve and process an incoming email
* For MTA, this would handle an email already received by the SMTP server
* @param emailData The raw email data or identifier
*/
public async receiveEmail(emailData: string): Promise<plugins.smartmail.Smartmail<>> {
try {
// In a real implementation, this would retrieve an email from the MTA storage
// For now, we can use a simplified approach:
// Parse the email (assuming emailData is a raw email or a file path)
const parsedEmail = await plugins.mailparser.simpleParser(emailData);
// Create a Smartmail from the parsed email
const smartmail = new plugins.smartmail.Smartmail({
from: parsedEmail.from?.text || '',
subject: parsedEmail.subject || '',
body: parsedEmail.html || parsedEmail.text || '',
creationObjectRef: {
From: parsedEmail.from?.text || '',
To: parsedEmail.to?.text || '',
Subject: parsedEmail.subject || ''
}
});
// Add attachments if present
if (parsedEmail.attachments && parsedEmail.attachments.length > 0) {
for (const attachment of parsedEmail.attachments) {
smartmail.addAttachment(
await plugins.smartfile.SmartFile.fromBuffer(
attachment.filename || 'attachment',
attachment.content
)
);
}
}
return smartmail;
} catch (error) {
logger.log('error', `Failed to receive email via MTA: ${error.message}`, {
eventType: 'emailError',
provider: 'mta',
error: error.message
});
throw error;
}
}
/**
* Check the status of a sent email
* @param emailId The email ID to check
*/
public async checkEmailStatus(emailId: string): Promise<{
status: string;
details?: any;
}> {
try {
const status = this.mtaService.getEmailStatus(emailId);
if (!status) {
return {
status: 'unknown',
details: { message: 'Email not found' }
};
}
return {
status: status.status,
details: {
attempts: status.attempts,
lastAttempt: status.lastAttempt,
nextAttempt: status.nextAttempt,
error: status.error?.message
}
};
} catch (error) {
logger.log('error', `Failed to check email status: ${error.message}`, {
eventType: 'emailError',
provider: 'mta',
emailId,
error: error.message
});
return {
status: 'error',
details: { message: error.message }
};
}
}
}

View File

@ -1,16 +1,22 @@
import * as plugins from '../plugins.js';
import * as paths from '../paths.js';
import { MailgunConnector } from './email.classes.connector.mailgun.js';
import { MtaConnector } from './email.classes.connector.mta.js';
import { RuleManager } from './email.classes.rulemanager.js';
import { ApiManager } from './email.classes.apimanager.js';
import { logger } from '../logger.js';
import type { SzPlatformService } from '../classes.platformservice.js';
// Import MTA service
import { MtaService, type IMtaConfig } from '../mta/index.js';
export interface IEmailConstructorOptions {
mailgunApiKey: string;
useMta?: boolean;
mtaConfig?: IMtaConfig;
}
/**
* Email service with support for both Mailgun and local MTA
*/
export class EmailService {
public platformServiceRef: SzPlatformService;
@ -18,36 +24,108 @@ export class EmailService {
public typedrouter = new plugins.typedrequest.TypedRouter();
// connectors
public mailgunConnector: MailgunConnector;
public mtaConnector: MtaConnector;
public qenv = new plugins.qenv.Qenv('./', '.nogit/');
// server
public apiManager = new ApiManager(this);
// MTA service
public mtaService: MtaService;
// services
public apiManager: ApiManager;
public ruleManager: RuleManager;
constructor(platformServiceRefArg: SzPlatformService) {
// configuration
private config: IEmailConstructorOptions;
constructor(platformServiceRefArg: SzPlatformService, options: IEmailConstructorOptions = {}) {
this.platformServiceRef = platformServiceRefArg;
this.platformServiceRef.typedrouter.addTypedRouter(this.typedrouter);
this.mailgunConnector = new MailgunConnector(this);
// Set default options
this.config = {
useMta: options.useMta ?? true,
mtaConfig: options.mtaConfig || {}
};
if (this.config.useMta) {
// Initialize MTA service
this.mtaService = new MtaService(platformServiceRefArg, this.config.mtaConfig);
// Initialize MTA connector
this.mtaConnector = new MtaConnector(this);
}
// Initialize API manager and rule manager
this.apiManager = new ApiManager(this);
this.ruleManager = new RuleManager(this);
this.platformServiceRef.typedserver.server.addRoute(
'/mailgun-notify',
new plugins.typedserver.servertools.Handler('POST', async (req, res) => {
console.log('Got a mailgun email notification');
res.status(200);
res.end();
this.ruleManager.handleNotification(req.body);
})
);
// Set up MTA SMTP server webhook if using MTA
if (this.config.useMta) {
// The MTA SMTP server will handle incoming emails directly
// through its SMTP protocol. No additional webhook needed.
}
}
/**
* Start the email service
*/
public async start() {
// Initialize rule manager
await this.ruleManager.init();
// Start MTA service if enabled
if (this.config.useMta && this.mtaService) {
await this.mtaService.start();
logger.log('success', 'Started MTA service');
}
logger.log('success', `Started email service`);
}
/**
* Stop the email service
*/
public async stop() {
// Stop MTA service if it's running
if (this.config.useMta && this.mtaService) {
await this.mtaService.stop();
logger.log('info', 'Stopped MTA service');
}
logger.log('info', 'Stopped email service');
}
}
/**
* Send an email using the configured provider (Mailgun or MTA)
* @param email The email to send
* @param to Recipient(s)
* @param options Additional options
*/
public async sendEmail(
email: plugins.smartmail.Smartmail<>,
to: string | string[],
options: any = {}
): Promise<string> {
// Determine which connector to use
if (this.config.useMta && this.mtaConnector) {
return this.mtaConnector.sendEmail(email, to, options);
} else {
throw new Error('No email provider configured');
}
}
/**
* Get email service statistics
*/
public getStats() {
const stats: any = {
activeProviders: []
};
if (this.config.useMta) {
stats.activeProviders.push('mta');
stats.mta = this.mtaService.getStats();
}
return stats;
}
}

View File

@ -1,51 +1,101 @@
import * as plugins from './email.plugins.js';
import * as plugins from '../plugins.js';
import { EmailService } from './email.classes.emailservice.js';
import { logger } from './email.logging.js';
import { logger } from '../logger.js';
export class RuleManager {
public emailRef: EmailService;
public smartruleInstance = new plugins.smartrule.SmartRule<
plugins.smartmail.Smartmail<plugins.mailgun.IMailgunMessage>
plugins.smartmail.Smartmail<any>
>();
constructor(emailRefArg: EmailService) {
this.emailRef = emailRefArg;
// Register MTA handler for incoming emails if MTA is enabled
if (this.emailRef.mtaService) {
this.setupMtaIncomingHandler();
}
}
public async handleNotification(notification: plugins.mailgun.IMailgunNotification) {
console.log(notification['message-url']);
/**
* Set up handler for incoming emails via MTA's SMTP server
*/
private setupMtaIncomingHandler() {
// The original MtaService doesn't have a direct callback for incoming emails,
// but we can modify this approach based on how you prefer to integrate.
// One option would be to extend the MtaService to add an event emitter.
// For now, we'll use a directory watcher as an example
// This would watch the directory where MTA saves incoming emails
const incomingDir = this.emailRef.mtaService['receivedEmailsDir'] || './received';
// Simple file watcher (in real implementation, use proper file watching)
// This is just conceptual - would need modification to work with your specific setup
this.watchIncomingEmails(incomingDir);
}
// basic checks here
// none for now
const fetchedSmartmail = await this.emailRef.mailgunConnector.receiveEmail(
notification['message-url']
);
console.log('=======================');
console.log('Received a mail:');
console.log(`From: ${fetchedSmartmail.options.creationObjectRef.From}`);
console.log(`To: ${fetchedSmartmail.options.creationObjectRef.To}`);
console.log(`Subject: ${fetchedSmartmail.options.creationObjectRef.Subject}`);
console.log('^^^^^^^^^^^^^^^^^^^^^^^');
logger.log(
'info',
`email from ${fetchedSmartmail.options.creationObjectRef.From} to ${fetchedSmartmail.options.creationObjectRef.To} with subject '${fetchedSmartmail.options.creationObjectRef.Subject}'`,
{
eventType: 'receivedEmail',
email: {
from: fetchedSmartmail.options.creationObjectRef.From,
to: fetchedSmartmail.options.creationObjectRef.To,
subject: fetchedSmartmail.options.creationObjectRef.Subject,
},
/**
* Watch directory for incoming emails (conceptual implementation)
*/
private watchIncomingEmails(directory: string) {
console.log(`Watching for incoming emails in: ${directory}`);
// Conceptual - in a real implementation, set up proper file watching
// or modify the MTA to emit events when emails are received
/*
// Example using a file watcher:
const watcher = plugins.fs.watch(directory, async (eventType, filename) => {
if (eventType === 'rename' && filename.endsWith('.eml')) {
const filePath = plugins.path.join(directory, filename);
await this.handleMtaIncomingEmail(filePath);
}
);
});
*/
}
this.smartruleInstance.makeDecision(fetchedSmartmail);
/**
* Handle incoming email received via MTA
*/
public async handleMtaIncomingEmail(emailPath: string) {
try {
// Process the email file
const fetchedSmartmail = await this.emailRef.mtaConnector.receiveEmail(emailPath);
console.log('=======================');
console.log('Received a mail via MTA:');
console.log(`From: ${fetchedSmartmail.options.creationObjectRef.From}`);
console.log(`To: ${fetchedSmartmail.options.creationObjectRef.To}`);
console.log(`Subject: ${fetchedSmartmail.options.creationObjectRef.Subject}`);
console.log('^^^^^^^^^^^^^^^^^^^^^^^');
logger.log(
'info',
`email from ${fetchedSmartmail.options.creationObjectRef.From} to ${fetchedSmartmail.options.creationObjectRef.To} with subject '${fetchedSmartmail.options.creationObjectRef.Subject}'`,
{
eventType: 'receivedEmail',
provider: 'mta',
email: {
from: fetchedSmartmail.options.creationObjectRef.From,
to: fetchedSmartmail.options.creationObjectRef.To,
subject: fetchedSmartmail.options.creationObjectRef.Subject,
},
}
);
// Process with rules
this.smartruleInstance.makeDecision(fetchedSmartmail);
} catch (error) {
logger.log('error', `Failed to process incoming MTA email: ${error.message}`, {
eventType: 'emailError',
provider: 'mta',
error: error.message
});
}
}
public async init() {
// lets forward stuff
// Setup email rules
await this.createForwards();
}
@ -53,20 +103,7 @@ export class RuleManager {
* creates the default forwards
*/
public async createForwards() {
const forwards: { originalToAddress: string[]; forwardedToAddress: string[] }[] = [
{
originalToAddress: ['bot@mail.nevermind.group'],
forwardedToAddress: ['phil@metadata.company', 'dominik@metadata.company'],
},
{
originalToAddress: ['legal@mail.lossless.com'],
forwardedToAddress: ['phil@lossless.com'],
},
{
originalToAddress: ['christine.nyamwaro@mail.lossless.com', 'christine@nyamwaro.com'],
forwardedToAddress: ['phil@lossless.com'],
},
];
const forwards: { originalToAddress: string[]; forwardedToAddress: string[] }[] = [];
console.log(`${forwards.length} forward rules configured:`);
for (const forward of forwards) {
console.log(forward);
@ -87,7 +124,7 @@ export class RuleManager {
return 'continue';
}
},
async (smartmailArg: plugins.smartmail.Smartmail<plugins.mailgun.IMailgunMessage>) => {
async (smartmailArg: plugins.smartmail.Smartmail<any>) => {
forward.forwardedToAddress.map(async (toArg) => {
const forwardedSmartMail = new plugins.smartmail.Smartmail({
body:
@ -112,13 +149,16 @@ export class RuleManager {
for (const attachment of smartmailArg.attachments) {
forwardedSmartMail.addAttachment(attachment);
}
await this.emailRef.mailgunConnector.sendEmail(forwardedSmartMail, toArg);
// Use the EmailService's sendEmail method to send with the appropriate provider
await this.emailRef.sendEmail(forwardedSmartMail, toArg);
console.log(`forwarded mail to ${toArg}`);
logger.log(
'info',
`email from ${
smartmailArg.options.creationObjectRef.From
} to phil@lossless.com with subject '${smartmailArg.getSubject()}'`,
} to ${toArg} with subject '${smartmailArg.getSubject()}'`,
{
eventType: 'forwardedEmail',
email: {
@ -134,4 +174,4 @@ export class RuleManager {
);
}
}
}
}

View File

@ -4,5 +4,4 @@ export * from './mta.classes.dkimverifier.js';
export * from './mta.classes.mta.js';
export * from './mta.classes.smtpserver.js';
export * from './mta.classes.emailsendjob.js';
export * from './mta.classes.mta.js';
export * from './mta.classes.email.js';

View File

@ -47,9 +47,10 @@ import * as smartmail from '@push.rocks/smartmail';
import * as smartpath from '@push.rocks/smartpath';
import * as smartpromise from '@push.rocks/smartpromise';
import * as smartrequest from '@push.rocks/smartrequest';
import * as smartrule from '@push.rocks/smartrule';
import * as smartrx from '@push.rocks/smartrx';
export { projectinfo, qenv, smartdata, smartfile, smartlog, smartmail, smartpath, smartpromise, smartrequest, smartrx };
export { projectinfo, qenv, smartdata, smartfile, smartlog, smartmail, smartpath, smartpromise, smartrequest, smartrule, smartrx };
// apiclient.xyz scope
import * as letterxpress from '@apiclient.xyz/letterxpress';