[feature] Mail service

This commit is contained in:
Georgii Petrov
2024-04-09 19:13:35 +03:00
parent 92682e70ff
commit 438a9c82a1
8 changed files with 351 additions and 0 deletions

View File

@ -16,6 +16,38 @@
"visibilityTimeout": 300,
"retentionPeriod": 900
},
"mail": {
"transportList": {
"smtp": {
"host": "smtp_host",
"port": 587,
"auth": {
"type": "login",
"user": "smtp_login",
"pass": "smtp_password"
},
"connection": {
"socketTimeout": "2m",
"connectionTimeout": "2m",
"greetingTimeout": "30s",
"dnsTimeout": "30s"
},
"pool": {
"maxConnections": 5,
"maxMessages": 100
}
},
"sendmail": {
"newline": "unix",
"path": "/usr/sbin/sendmail"
}
},
"messageDefaults": {
"from": "some.mail@server.com",
"disableFileAccess": false,
"disableUrlAccess": false
}
},
"storage": {
"name": "storage-fs",
"fs": {

View File

@ -1697,6 +1697,11 @@
"resolved": "https://registry.npmjs.org/node-statsd/-/node-statsd-0.1.1.tgz",
"integrity": "sha1-J6WTSHY9CvegN6wqAx/vPwUQE9M="
},
"nodemailer": {
"version": "6.9.13",
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.13.tgz",
"integrity": "sha512-7o38Yogx6krdoBf3jCAqnIN4oSQFx+fMa0I7dK1D+me9kBxx12D+/33wSb+fhOCtIxvYJ+4x4IMEhmhCKfAiOA=="
},
"oauth-sign": {
"version": "0.9.0",
"resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz",

View File

@ -18,6 +18,7 @@
"log4js": "6.4.1",
"mime": "2.3.1",
"ms": "2.1.1",
"nodemailer": "6.9.13",
"node-cache": "4.2.1",
"node-statsd": "0.1.1",
"request": "2.88.0",

View File

@ -0,0 +1,158 @@
/*
* (c) Copyright Ascensio System SIA 2010-2023
*
* This program is a free software product. You can redistribute it and/or
* modify it under the terms of the GNU Affero General Public License (AGPL)
* version 3 as published by the Free Software Foundation. In accordance with
* Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect
* that Ascensio System SIA expressly excludes the warranty of non-infringement
* of any third-party rights.
*
* This program is distributed WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For
* details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
*
* You can contact Ascensio System SIA at 20A-6 Ernesta Birznieka-Upish
* street, Riga, Latvia, EU, LV-1050.
*
* The interactive user interfaces in modified source and object code versions
* of the Program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU AGPL version 3.
*
* Pursuant to Section 7(b) of the License you must retain the original Product
* logo when distributing the program. Pursuant to Section 7(e) we decline to
* grant you any rights under trademark law for use of our trademarks.
*
* All the Product's GUI elements, including illustrations and icon sets, as
* well as technical writing content are licensed under the terms of the
* Creative Commons Attribution-ShareAlike 4.0 International. See the License
* terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
*
*/
'use strict';
const config = require('config');
const ms = require('ms');
const nodemailer = require('nodemailer');
const cfgMail = config.get('mail');
const cfgMessageDefaults = config.get('mail.messageDefaults');
const smtpTransporters = new Map();
let sendMailTransporter = null;
const getSMTPSettings = (function() {
const configParameters = cfgMail.transportList['smtp'];
let settings = {
pool: true,
socketTimeout: 1000 * 60 * 2,
connectionTimeout: 1000 * 60 * 2,
greetingTimeout: 1000 * 30,
dnsTimeout: 1000 * 30,
maxConnections: 5,
maxMessages: 100
};
if (configParameters !== undefined && Object.values(configParameters).length !== 0) {
const poolConfig = configParameters.pool ?? {};
const connectionConfig = configParameters.connection ?? {};
const timersConvert = Object.entries(connectionConfig).map(row => [row[0], ms(row[1])]);
settings = Object.assign({ pool: true }, poolConfig, Object.fromEntries(timersConvert));
}
return function() {
return settings;
};
})();
const getSendmailSettings = (function () {
const configParameters = cfgMail.transportList['sendmail'];
let settings = {
sendmail: true,
newline: 'unix',
path: '/usr/sbin/sendmail'
};
if(configParameters !== undefined && Object.values(configParameters).length !== 0) {
settings = Object.assign({ sendmail: true }, configParameters);
}
return function () {
return settings;
}
})();
function createSMTPTransporter(ctx, host, port, auth, messageCommonParameters = {}) {
const server = {
host,
port,
auth,
secure: port === 465
};
const transport = Object.assign({}, server, getSMTPSettings());
const mailDefaults = Object.assign({}, cfgMessageDefaults, messageCommonParameters);
let transporter;
try {
transporter = nodemailer.createTransport(transport, mailDefaults);
smtpTransporters.set(host, transporter);
} catch (error) {
ctx.logger.error('Mail service smtp transporter creation error: %o\nWith parameters: \n\thost - %s, \n\tport - %d, \n\tauth = %o', error.stack, host, port, auth);
}
return transporter;
}
function createSendmailTransporter(ctx, messageCommonParameters = {}) {
if (!sendMailTransporter) {
const mailDefaults = Object.assign({}, cfgMessageDefaults, messageCommonParameters);
try {
sendMailTransporter = nodemailer.createTransport(getSendmailSettings(), mailDefaults);
} catch (error) {
ctx.logger.error('Mail service sendmail transporter creation error: %o', error.stack);
}
return sendMailTransporter;
}
}
function getSMTPTransporter(ctx, host) {
const transporter = smtpTransporters.get(host);
if (!transporter) {
ctx.logger.error(`MailService getSMTPTransporter(): no transporter exists for host "${host}"`);
return;
}
return transporter;
}
function getSendmailTransporter(ctx) {
if (!sendMailTransporter) {
ctx.logger.error(`MailService getSendmailTransporter(): no sendmail transporter exists`);
return;
}
return sendMailTransporter;
}
function deleteSMTPTransporter(host) {
smtpTransporters.delete(host);
}
function transportersRelease() {
smtpTransporters.forEach(transporter => transporter.close());
smtpTransporters.clear();
sendMailTransporter = null;
}
module.exports = {
createSMTPTransporter,
createSendmailTransporter,
getSMTPTransporter,
getSendmailTransporter,
deleteSMTPTransporter,
transportersRelease
};

View File

@ -0,0 +1,88 @@
const { describe, test, expect, afterAll} = require('@jest/globals');
const config = require('../../Common/node_modules/config');
const mailService = require('../../Common/sources/mailService');
const operationContext = require('../../Common/sources/operationContext');
const fs = require('fs');
const ctx = new operationContext.Context();
const bulkTestTimeout = 1000 * 10;
const largeMailContentTestTimeout = 1000 * 7;
const testFolder = '../tests/unit'
const defaultTestSMTPServer = {
host: 'smtp.ethereal.email',
port: 587,
// Account created at https://ethereal.email/, all messages in tests goes here: https://ethereal.email/messages
// Ethereial is a special SMTP sever for mailing tests in collaboration with Nodemailer.
auth: {
type: 'login',
user: 'madie.wiegand79@ethereal.email',
pass: 'ZUSjtcbaBKQdN4BGNx'
},
};
const expectedEnvelope = { from: 'some.mail@server.com', to: ['madie.wiegand79@ethereal.email'] };
afterAll(function () {
mailService.transportersRelease();
})
describe('Mail service', function () {
describe('SMTP', function () {
const { host, port, auth } = defaultTestSMTPServer;
const transporter = mailService.createSMTPTransporter(
ctx,
host,
port,
auth,
{ from: 'some.mail@server.com', to: 'madie.wiegand79@ethereal.email' }
);
test('Simple mail', async function () {
const mailData = await transporter.sendMail({ text: 'simple test text', subject: 'simple mail test' });
expect(mailData.envelope).toEqual(expectedEnvelope);
});
test('Bulk mails', async function () {
const promises = [];
for (let i = 1; i <= 100; i++) {
promises.push(transporter.sendMail({ text: `bulk test text #${i}`, subject: 'bulk mails test' }));
}
const result = await Promise.all(promises);
result.forEach(data => expect(data.envelope).toEqual(expectedEnvelope));
}, bulkTestTimeout);
test('Large mail content', async function () {
const readStream = fs.createReadStream(`${testFolder}/resources/16MiBFile.txt`);
const mailData = await transporter.sendMail({ text: readStream, subject: 'large mail test' });
expect(mailData.envelope).toEqual(expectedEnvelope);
}, largeMailContentTestTimeout);
test('HTML mail content', async function () {
const readStream = fs.createReadStream(`${testFolder}/resources/htmlContent.html`);
const mailData = await transporter.sendMail({ html: readStream, subject: 'html mail test' });
expect(mailData.envelope).toEqual(expectedEnvelope);
});
test('Mail with attachments content', async function () {
const readStream = fs.createReadStream(`${testFolder}/resources/htmlContent.html`);
const message = {
text: 'File added below',
subject: 'attachment mail test',
attachments: [
{
filename: 'report.docx',
// Will stream the file from this path.
path: `${testFolder}/resources/new.docx`
},
{
filename: 'page.html',
content: readStream
}
]
};
const mailData = await transporter.sendMail(message);
expect(mailData.envelope).toEqual(expectedEnvelope);
});
});
});

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,66 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Our Pets</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 20px;
background-color: #f4f4f4;
color: #333;
}
header {
background-color: #4CAF50;
color: #fff;
padding: 20px;
text-align: center;
}
.container {
max-width: 800px;
margin: 0 auto;
padding: 20px;
background-color: #fff;
border-radius: 8px;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}
h1, h2 {
text-align: center;
}
p {
line-height: 1.6;
}
</style>
</head>
<body>
<header>
<h1>Our Pets</h1>
<p>Welcome to our page about our beloved pets!</p>
</header>
<div class="container">
<h2>Meet Our Furry Friends</h2>
<p>We have a variety of pets that bring joy to our lives:</p>
<h3>Dog: Bella</h3>
<p>Bella is a friendly golden retriever who loves playing fetch and going on long walks.</p>
<h3>Cat: Whiskers</h3>
<p>Whiskers is a curious tabby cat who enjoys lounging in sunny spots and chasing toy mice.</p>
<h3>Rabbit: Snowball</h3>
<p>Snowball is an adorable white rabbit with floppy ears. She hops around the garden and munches on carrots.</p>
<h3>Parrot: Rio</h3>
<p>Rio is a colorful parrot who loves mimicking sounds and learning new words. His favorite treat is sunflower seeds.</p>
<h2>Why We Love Our Pets</h2>
<p>Our pets bring so much happiness and companionship into our lives. They are always there to greet us with excitement and provide unconditional love.</p>
<p>Do you have pets too? Share your pet stories with us!</p>
</div>
</body>
</html>

Binary file not shown.