added support for ses
This commit is contained in:
parent
7fde2a9619
commit
66bfcebd47
11 changed files with 575 additions and 136 deletions
|
@ -1,5 +1,10 @@
|
|||
# Changelog
|
||||
|
||||
## 1.21.0 2017-02-17
|
||||
|
||||
* Changed license from MIT to EUPL-1.1
|
||||
* Added support for sending mail using AWS SES
|
||||
|
||||
## 1.20.0 2016-12-11
|
||||
|
||||
* Added option to distribute sending queue between multiple processes to speed up delivery
|
||||
|
|
310
LICENSE
310
LICENSE
|
@ -1,16 +1,298 @@
|
|||
Copyright (c) 2016 Andris Reinman
|
||||
Copyright (c) 2016-2017 Andris Reinman
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
European Union Public Licence
|
||||
V. 1.1
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
EUPL (c) the European Community 2007
|
||||
|
||||
|
||||
This European Union Public Licence (the "EUPL") applies to the Work or Software
|
||||
(as defined below) which is provided under the terms of this Licence. Any use
|
||||
of the Work, other than as authorised under this Licence is prohibited (to the
|
||||
extent such use is covered by a right of the copyright holder of the Work).
|
||||
|
||||
The Original Work is provided under the terms of this Licence when the Licensor
|
||||
(as defined below) has placed the following notice immediately following the
|
||||
copyright notice for the Original Work:
|
||||
|
||||
Licensed under the EUPL V.1.1
|
||||
|
||||
or has expressed by any other mean his willingness to license under the EUPL.
|
||||
|
||||
|
||||
1. Definitions
|
||||
|
||||
In this Licence, the following terms have the following meaning:
|
||||
|
||||
* The Licence: this Licence.
|
||||
|
||||
* The Original Work or the Software: the software distributed and/or
|
||||
communicated by the Licensor under this Licence, available as Source Code
|
||||
and also as Executable Code as the case may be.
|
||||
|
||||
* Derivative Works: the works or software that could be created by the
|
||||
Licensee, based upon the Original Work or modifications thereof. This
|
||||
Licence does not define the extent of modification or dependence on the
|
||||
Original Work required in order to classify a work as a Derivative Work;
|
||||
this extent is determined by copyright law applicable in the country
|
||||
mentioned in Article 15.
|
||||
|
||||
* The Work: the Original Work and/or its Derivative Works.
|
||||
|
||||
* The Source Code: the human-readable form of the Work which is the most
|
||||
convenient for people to study and modify.
|
||||
|
||||
* The Executable Code: any code which has generally been compiled and which is
|
||||
meant to be interpreted by a computer as a program.
|
||||
|
||||
* The Licensor: the natural or legal person that distributes and/or
|
||||
communicates the Work under the Licence.
|
||||
|
||||
* Contributor(s): any natural or legal person who modifies the Work under the
|
||||
Licence, or otherwise contributes to the creation of a Derivative Work.
|
||||
|
||||
* The Licensee or "You": any natural or legal person who makes any usage of
|
||||
the Software under the terms of the Licence.
|
||||
|
||||
* Distribution and/or Communication: any act of selling, giving, lending,
|
||||
renting, distributing, communicating, transmitting, or otherwise making
|
||||
available, on-line or off-line, copies of the Work or providing access to
|
||||
its essential functionalities at the disposal of any other natural or legal
|
||||
person.
|
||||
|
||||
|
||||
2. Scope of the rights granted by the Licence
|
||||
|
||||
The Licensor hereby grants You a world-wide, royalty-free, non-exclusive,
|
||||
sublicensable licence to do the following, for the duration of copyright vested
|
||||
in the Original Work:
|
||||
|
||||
* use the Work in any circumstance and for all usage,
|
||||
* reproduce the Work,
|
||||
* modify the Original Work, and make Derivative Works based upon the Work,
|
||||
* communicate to the public, including the right to make available or display
|
||||
the Work or copies thereof to the public and perform publicly, as the case
|
||||
may be, the Work,
|
||||
* distribute the Work or copies thereof,
|
||||
* lend and rent the Work or copies thereof,
|
||||
* sub-license rights in the Work or copies thereof.
|
||||
|
||||
Those rights can be exercised on any media, supports and formats, whether now
|
||||
known or later invented, as far as the applicable law permits so.
|
||||
|
||||
In the countries where moral rights apply, the Licensor waives his right to
|
||||
exercise his moral right to the extent allowed by law in order to make
|
||||
effective the licence of the economic rights here above listed.
|
||||
|
||||
The Licensor grants to the Licensee royalty-free, non exclusive usage rights to
|
||||
any patents held by the Licensor, to the extent necessary to make use of the
|
||||
rights granted on the Work under this Licence.
|
||||
|
||||
|
||||
3. Communication of the Source Code
|
||||
|
||||
The Licensor may provide the Work either in its Source Code form, or as
|
||||
Executable Code. If the Work is provided as Executable Code, the Licensor
|
||||
provides in addition a machine-readable copy of the Source Code of the Work
|
||||
along with each copy of the Work that the Licensor distributes or indicates, in
|
||||
a notice following the copyright notice attached to the Work, a repository
|
||||
where the Source Code is easily and freely accessible for as long as the
|
||||
Licensor continues to distribute and/or communicate the Work.
|
||||
|
||||
|
||||
4. Limitations on copyright
|
||||
|
||||
Nothing in this Licence is intended to deprive the Licensee of the benefits
|
||||
from any exception or limitation to the exclusive rights of the rights owners
|
||||
in the Original Work or Software, of the exhaustion of those rights or of other
|
||||
applicable limitations thereto.
|
||||
|
||||
|
||||
5. Obligations of the Licensee
|
||||
|
||||
The grant of the rights mentioned above is subject to some restrictions and
|
||||
obligations imposed on the Licensee. Those obligations are the following:
|
||||
|
||||
- Attribution right: the Licensee shall keep intact all copyright, patent or
|
||||
trademarks notices and all notices that refer to the Licence and to the
|
||||
disclaimer of warranties. The Licensee must include a copy of such notices
|
||||
and a copy of the Licence with every copy of the Work he/she distributes
|
||||
and/or communicates. The Licensee must cause any Derivative Work to carry
|
||||
prominent notices stating that the Work has been modified and the date of
|
||||
modification.
|
||||
|
||||
- Copyleft clause: If the Licensee distributes and/or communicates copies of
|
||||
the Original Works or Derivative Works based upon the Original Work, this
|
||||
Distribution and/or Communication will be done under the terms of this
|
||||
Licence or of a later version of this Licence unless the Original Work is
|
||||
expressly distributed only under this version of the Licence. The Licensee
|
||||
(becoming Licensor) cannot offer or impose any additional terms or
|
||||
conditions on the Work or Derivative Work that alter or restrict the terms
|
||||
of the Licence.
|
||||
|
||||
- Compatibility clause: If the Licensee Distributes and/or Communicates
|
||||
Derivative Works or copies thereof based upon both the Original Work and
|
||||
another work licensed under a Compatible Licence, this Distribution and/or
|
||||
Communication can be done under the terms of this Compatible Licence. For
|
||||
the sake of this clause, "Compatible Licence" refers to the licences listed
|
||||
in the appendix attached to this Licence. Should the Licensee's obligations
|
||||
under the Compatible Licence conflict with his/her obligations under this
|
||||
Licence, the obligations of the Compatible Licence shall prevail.
|
||||
|
||||
- Provision of Source Code: When distributing and/or communicating copies of
|
||||
the Work, the Licensee will provide a machine-readable copy of the Source
|
||||
Code or indicate a repository where this Source will be easily and freely
|
||||
available for as long as the Licensee continues to distribute and/or
|
||||
communicate the Work. Legal Protection: This Licence does not grant
|
||||
permission to use the trade names, trademarks, service marks, or names of
|
||||
the Licensor, except as required for reasonable and customary use in
|
||||
describing the origin of the Work and reproducing the content of the
|
||||
copyright notice.
|
||||
|
||||
|
||||
6. Chain of Authorship
|
||||
|
||||
The original Licensor warrants that the copyright in the Original Work granted
|
||||
hereunder is owned by him/her or licensed to him/her and that he/she has the
|
||||
power and authority to grant the Licence.
|
||||
|
||||
Each Contributor warrants that the copyright in the modifications he/she brings
|
||||
to the Work are owned by him/her or licensed to him/her and that he/she has the
|
||||
power and authority to grant the Licence.
|
||||
|
||||
Each time You accept the Licence, the original Licensor and subsequent
|
||||
Contributors grant You a licence to their contributions to the Work, under the
|
||||
terms of this Licence.
|
||||
|
||||
|
||||
7. Disclaimer of Warranty
|
||||
|
||||
The Work is a work in progress, which is continuously improved by numerous
|
||||
contributors. It is not a finished work and may therefore contain defects or
|
||||
"bugs" inherent to this type of software development.
|
||||
|
||||
For the above reason, the Work is provided under the Licence on an "as is"
|
||||
basis and without warranties of any kind concerning the Work, including without
|
||||
limitation merchantability, fitness for a particular purpose, absence of
|
||||
defects or errors, accuracy, non-infringement of intellectual property rights
|
||||
other than copyright as stated in Article 6 of this Licence.
|
||||
|
||||
This disclaimer of warranty is an essential part of the Licence and a condition
|
||||
for the grant of any rights to the Work.
|
||||
|
||||
|
||||
8. Disclaimer of Liability
|
||||
|
||||
Except in the cases of wilful misconduct or damages directly caused to natural
|
||||
persons, the Licensor will in no event be liable for any direct or indirect,
|
||||
material or moral, damages of any kind, arising out of the Licence or of the
|
||||
use of the Work, including without limitation, damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, loss of data or any commercial
|
||||
damage, even if the Licensor has been advised of the possibility of such
|
||||
damage. However, the Licensor will be liable under statutory product liability
|
||||
laws as far such laws apply to the Work.
|
||||
|
||||
|
||||
9. Additional agreements
|
||||
|
||||
While distributing the Original Work or Derivative Works, You may choose to
|
||||
conclude an additional agreement to offer, and charge a fee for, acceptance of
|
||||
support, warranty, indemnity, or other liability obligations and/or services
|
||||
consistent with this Licence. However, in accepting such obligations, You may
|
||||
act only on your own behalf and on your sole responsibility, not on behalf of
|
||||
the original Licensor or any other Contributor, and only if You agree to
|
||||
indemnify, defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against such Contributor by the fact You have
|
||||
accepted any such warranty or additional liability.
|
||||
|
||||
|
||||
10. Acceptance of the Licence
|
||||
|
||||
The provisions of this Licence can be accepted by clicking on an icon "I agree"
|
||||
placed under the bottom of a window displaying the text of this Licence or by
|
||||
affirming consent in any other similar way, in accordance with the rules of
|
||||
applicable law. Clicking on that icon indicates your clear and irrevocable
|
||||
acceptance of this Licence and all of its terms and conditions.
|
||||
|
||||
Similarly, you irrevocably accept this Licence and all of its terms and
|
||||
conditions by exercising any rights granted to You by Article 2 of this
|
||||
Licence, such as the use of the Work, the creation by You of a Derivative Work
|
||||
or the Distribution and/or Communication by You of the Work or copies thereof.
|
||||
|
||||
|
||||
11. Information to the public
|
||||
|
||||
In case of any Distribution and/or Communication of the Work by means of
|
||||
electronic communication by You (for example, by offering to download the Work
|
||||
from a remote location) the distribution channel or media (for example, a
|
||||
website) must at least provide to the public the information requested by the
|
||||
applicable law regarding the Licensor, the Licence and the way it may be
|
||||
accessible, concluded, stored and reproduced by the Licensee.
|
||||
|
||||
|
||||
12. Termination of the Licence
|
||||
|
||||
The Licence and the rights granted hereunder will terminate automatically upon
|
||||
any breach by the Licensee of the terms of the Licence.
|
||||
|
||||
Such a termination will not terminate the licences of any person who has
|
||||
received the Work from the Licensee under the Licence, provided such persons
|
||||
remain in full compliance with the Licence.
|
||||
|
||||
|
||||
13. Miscellaneous
|
||||
|
||||
Without prejudice of Article 9 above, the Licence represents the complete
|
||||
agreement between the Parties as to the Work licensed hereunder.
|
||||
|
||||
If any provision of the Licence is invalid or unenforceable under applicable
|
||||
law, this will not affect the validity or enforceability of the Licence as a
|
||||
whole. Such provision will be construed and/or reformed so as necessary to make
|
||||
it valid and enforceable.
|
||||
|
||||
The European Commission may publish other linguistic versions and/or new
|
||||
versions of this Licence, so far this is required and reasonable, without
|
||||
reducing the scope of the rights granted by the Licence. New versions of the
|
||||
Licence will be published with a unique version number.
|
||||
|
||||
All linguistic versions of this Licence, approved by the European Commission,
|
||||
have identical value. Parties can take advantage of the linguistic version of
|
||||
their choice.
|
||||
|
||||
|
||||
14. Jurisdiction
|
||||
|
||||
Any litigation resulting from the interpretation of this License, arising
|
||||
between the European Commission, as a Licensor, and any Licensee, will be
|
||||
subject to the jurisdiction of the Court of Justice of the European
|
||||
Communities, as laid down in article 238 of the Treaty establishing the
|
||||
European Community.
|
||||
|
||||
Any litigation arising between Parties, other than the European Commission, and
|
||||
resulting from the interpretation of this License, will be subject to the
|
||||
exclusive jurisdiction of the competent court where the Licensor resides or
|
||||
conducts its primary business.
|
||||
|
||||
|
||||
15. Applicable Law
|
||||
|
||||
This Licence shall be governed by the law of the European Union country where
|
||||
the Licensor resides or has his registered office.
|
||||
|
||||
This licence shall be governed by the Belgian law if:
|
||||
|
||||
* a litigation arises between the European Commission, as a Licensor, and any
|
||||
Licensee;
|
||||
* the Licensor, other than the European Commission, has no residence or
|
||||
registered office inside a European Union country.
|
||||
|
||||
|
||||
Appendix
|
||||
|
||||
"Compatible Licences" according to article 5 EUPL are:
|
||||
|
||||
* GNU General Public License (GNU GPL) v. 2
|
||||
* Open Software License (OSL) v. 2.1, v. 3.0
|
||||
* Common Public License v. 1.0
|
||||
* Eclipse Public License v. 1.0
|
||||
* Cecill v. 2.0
|
||||
|
|
|
@ -180,5 +180,6 @@ This command generates a CSV file with 100 000 subscriber accounts
|
|||
|
||||
## License
|
||||
|
||||
* Versions 1.21.0 and up: **EUPL-1.1**
|
||||
* Versions 1.19.0 and up: **MIT**
|
||||
* Up to versions 1.18.0 **GPL-V3.0**
|
||||
|
|
|
@ -75,7 +75,7 @@ db=5
|
|||
# In most cases you do not want to use it
|
||||
# Requires root privileges
|
||||
enabled=false
|
||||
port=25
|
||||
port=2525
|
||||
host="0.0.0.0"
|
||||
|
||||
[testserver]
|
||||
|
|
126
lib/mailer.js
126
lib/mailer.js
|
@ -12,6 +12,7 @@ let fs = require('fs');
|
|||
let path = require('path');
|
||||
let templates = new Map();
|
||||
let htmlToText = require('html-to-text');
|
||||
let aws = require('aws-sdk');
|
||||
|
||||
module.exports.transport = false;
|
||||
|
||||
|
@ -121,7 +122,7 @@ function getTemplate(template, callback) {
|
|||
}
|
||||
|
||||
function createMailer(callback) {
|
||||
settings.list(['smtpHostname', 'smtpPort', 'smtpEncryption', 'smtpUser', 'smtpPass', 'smtpLog', 'smtpDisableAuth', 'smtpMaxConnections', 'smtpMaxMessages', 'smtpSelfSigned', 'pgpPrivateKey', 'pgpPassphrase', 'smtpThrottling'], (err, configItems) => {
|
||||
settings.list(['smtpHostname', 'smtpPort', 'smtpEncryption', 'smtpUser', 'smtpPass', 'smtpLog', 'smtpDisableAuth', 'smtpMaxConnections', 'smtpMaxMessages', 'smtpSelfSigned', 'pgpPrivateKey', 'pgpPassphrase', 'smtpThrottling', 'mailTransport', 'sesKey', 'sesSecret', 'sesRegion'], (err, configItems) => {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
@ -134,28 +135,70 @@ function createMailer(callback) {
|
|||
module.exports.transport.checkThrottling = null;
|
||||
}
|
||||
|
||||
module.exports.transport = nodemailer.createTransport({
|
||||
pool: true,
|
||||
host: configItems.smtpHostname,
|
||||
port: Number(configItems.smtpPort) || false,
|
||||
secure: configItems.smtpEncryption === 'TLS',
|
||||
ignoreTLS: configItems.smtpEncryption === 'NONE',
|
||||
auth: configItems.smtpDisableAuth ? false : {
|
||||
user: configItems.smtpUser,
|
||||
pass: configItems.smtpPass
|
||||
},
|
||||
debug: !!configItems.smtpLog,
|
||||
logger: !configItems.smtpLog ? false : {
|
||||
debug: log.verbose.bind(log, 'Mail'),
|
||||
info: log.info.bind(log, 'Mail'),
|
||||
error: log.error.bind(log, 'Mail')
|
||||
},
|
||||
maxConnections: Number(configItems.smtpMaxConnections),
|
||||
maxMessages: Number(configItems.smtpMaxMessages),
|
||||
tls: {
|
||||
rejectUnauthorized: !configItems.smtpSelfSigned
|
||||
}
|
||||
}, config.nodemailer);
|
||||
let throttling = Number(configItems.smtpThrottling) || 0;
|
||||
if (throttling) {
|
||||
// convert to messages/second
|
||||
throttling = 1 / (throttling / (3600 * 1000));
|
||||
}
|
||||
|
||||
let transportOptions;
|
||||
|
||||
let logfunc = function () {
|
||||
let args = [].slice.call(arguments);
|
||||
let level = args.shift();
|
||||
args.shift();
|
||||
args.unshift('Mail');
|
||||
log[level].apply(log, args);
|
||||
};
|
||||
|
||||
if (configItems.mailTransport === 'smtp' || !configItems.mailTransport) {
|
||||
transportOptions = {
|
||||
pool: true,
|
||||
host: configItems.smtpHostname,
|
||||
port: Number(configItems.smtpPort) || false,
|
||||
secure: configItems.smtpEncryption === 'TLS',
|
||||
ignoreTLS: configItems.smtpEncryption === 'NONE',
|
||||
auth: configItems.smtpDisableAuth ? false : {
|
||||
user: configItems.smtpUser,
|
||||
pass: configItems.smtpPass
|
||||
},
|
||||
debug: !!configItems.smtpLog,
|
||||
logger: !configItems.smtpLog ? false : {
|
||||
debug: logfunc.bind(null, 'verbose'),
|
||||
info: logfunc.bind(null, 'info'),
|
||||
error: logfunc.bind(null, 'error')
|
||||
},
|
||||
maxConnections: Number(configItems.smtpMaxConnections),
|
||||
maxMessages: Number(configItems.smtpMaxMessages),
|
||||
tls: {
|
||||
rejectUnauthorized: !configItems.smtpSelfSigned
|
||||
}
|
||||
};
|
||||
} else if (configItems.mailTransport === 'ses') {
|
||||
transportOptions = {
|
||||
SES: new aws.SES({
|
||||
apiVersion: '2010-12-01',
|
||||
accessKeyId: configItems.sesKey,
|
||||
secretAccessKey: configItems.sesSecret,
|
||||
region: configItems.sesRegion
|
||||
}),
|
||||
debug: !!configItems.smtpLog,
|
||||
logger: !configItems.smtpLog ? false : {
|
||||
debug: logfunc.bind(null, 'verbose'),
|
||||
info: logfunc.bind(null, 'info'),
|
||||
error: logfunc.bind(null, 'error')
|
||||
},
|
||||
maxConnections: Number(configItems.smtpMaxConnections),
|
||||
sendingRate: throttling,
|
||||
tls: {
|
||||
rejectUnauthorized: !configItems.smtpSelfSigned
|
||||
}
|
||||
};
|
||||
} else {
|
||||
return callback(new Error('Invalid mail transport'));
|
||||
}
|
||||
|
||||
module.exports.transport = nodemailer.createTransport(transportOptions, config.nodemailer);
|
||||
|
||||
module.exports.transport.use('stream', openpgpEncrypt({
|
||||
signingKey: configItems.pgpPrivateKey,
|
||||
|
@ -167,26 +210,25 @@ function createMailer(callback) {
|
|||
oldListeners.forEach(listener => module.exports.transport.on('idle', listener));
|
||||
}
|
||||
|
||||
let throttling = Number(configItems.smtpThrottling) || 0;
|
||||
if (throttling) {
|
||||
// convert to messages/second
|
||||
throttling = 1 / (throttling / (3600 * 1000));
|
||||
}
|
||||
let lastCheck = Date.now();
|
||||
module.exports.transport.checkThrottling = function (next) {
|
||||
if (!throttling) {
|
||||
return next();
|
||||
}
|
||||
let nextCheck = Date.now();
|
||||
let checkDiff = (nextCheck - lastCheck);
|
||||
lastCheck = nextCheck;
|
||||
if (checkDiff < throttling) {
|
||||
log.verbose('Mail', 'Throttling next message in %s sec.', (throttling - checkDiff) / 1000);
|
||||
setTimeout(next, throttling - checkDiff);
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
};
|
||||
if (configItems.mailTransport === 'smtp' || !configItems.mailTransport) {
|
||||
module.exports.transport.checkThrottling = function (next) {
|
||||
if (!throttling) {
|
||||
return next();
|
||||
}
|
||||
let nextCheck = Date.now();
|
||||
let checkDiff = (nextCheck - lastCheck);
|
||||
lastCheck = nextCheck;
|
||||
if (checkDiff < throttling) {
|
||||
log.verbose('Mail', 'Throttling next message in %s sec.', (throttling - checkDiff) / 1000);
|
||||
setTimeout(next, throttling - checkDiff);
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
};
|
||||
} else {
|
||||
module.exports.transport.checkThrottling = next => next();
|
||||
}
|
||||
|
||||
db.clearCache('sender', () => {
|
||||
callback(null, module.exports.transport);
|
||||
|
|
|
@ -266,7 +266,7 @@ module.exports.add = (url, campaignId, callback) => {
|
|||
};
|
||||
|
||||
module.exports.updateLinks = (campaign, list, subscription, serviceUrl, message, callback) => {
|
||||
if (campaign.trackingDisabled || !message.trim()) {
|
||||
if (campaign.trackingDisabled || !message || !message.trim()) {
|
||||
// tracking is disabled, do not modify the message
|
||||
return setImmediate(() => callback(null, message));
|
||||
}
|
||||
|
|
27
package.json
27
package.json
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "mailtrain",
|
||||
"private": true,
|
||||
"version": "1.20.0",
|
||||
"version": "1.21.0",
|
||||
"description": "Self hosted email newsletter app",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
|
@ -17,8 +17,8 @@
|
|||
"url": "git://github.com/andris9/mailtrain.git"
|
||||
},
|
||||
"author": "Andris Reinman",
|
||||
"license": "MIT",
|
||||
"homepage": "http://mailtrain.org",
|
||||
"license": "EUPL-1.1",
|
||||
"homepage": "https://mailtrain.org/",
|
||||
"engines": {
|
||||
"node": ">=5.0.0"
|
||||
},
|
||||
|
@ -29,11 +29,12 @@
|
|||
"grunt-eslint": "^19.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"aws-sdk": "^2.15.0",
|
||||
"bcrypt-nodejs": "0.0.3",
|
||||
"body-parser": "^1.16.0",
|
||||
"body-parser": "^1.16.1",
|
||||
"bounce-handler": "^7.3.2-fork.2",
|
||||
"compression": "^1.6.2",
|
||||
"config": "^1.24.0",
|
||||
"config": "^1.25.1",
|
||||
"connect-flash": "^0.1.1",
|
||||
"connect-redis": "^3.2.0",
|
||||
"cookie-parser": "^1.4.3",
|
||||
|
@ -42,30 +43,30 @@
|
|||
"csv-parse": "^1.2.0",
|
||||
"escape-html": "^1.0.3",
|
||||
"express": "^4.14.1",
|
||||
"express-session": "^1.15.0",
|
||||
"express-session": "^1.15.1",
|
||||
"faker": "^3.1.0",
|
||||
"feedparser": "^2.1.0",
|
||||
"geoip-ultralight": "^0.1.4",
|
||||
"handlebars": "^4.0.6",
|
||||
"hbs": "^4.0.1",
|
||||
"he": "^1.1.1",
|
||||
"html-to-text": "^3.0.0",
|
||||
"html-to-text": "^3.1.0",
|
||||
"humanize": "0.0.9",
|
||||
"is-url": "^1.2.2",
|
||||
"isemail": "^2.2.1",
|
||||
"jsdom": "^9.9.1",
|
||||
"jsdom": "^9.11.0",
|
||||
"juice": "^4.0.2",
|
||||
"libmime": "^3.1.0",
|
||||
"marked": "^0.3.6",
|
||||
"mkdirp": "^0.5.1",
|
||||
"moment-timezone": "^0.5.11",
|
||||
"morgan": "^1.7.0",
|
||||
"morgan": "^1.8.1",
|
||||
"multer": "^1.3.0",
|
||||
"mysql": "^2.13.0",
|
||||
"nodemailer": "^2.7.2",
|
||||
"nodemailer": "^3.1.3",
|
||||
"nodemailer-openpgp": "^1.0.2",
|
||||
"npmlog": "^4.0.2",
|
||||
"openpgp": "^2.3.6",
|
||||
"openpgp": "^2.3.7",
|
||||
"passport": "^0.3.2",
|
||||
"passport-local": "^1.0.0",
|
||||
"redfour": "^1.0.0",
|
||||
|
@ -74,8 +75,8 @@
|
|||
"serve-favicon": "^2.3.2",
|
||||
"shortid": "^2.2.6",
|
||||
"slugify": "^1.1.0",
|
||||
"smtp-server": "^1.17.0",
|
||||
"striptags": "^2.2.1",
|
||||
"smtp-server": "^2.0.2",
|
||||
"striptags": "^3.0.1",
|
||||
"toml": "^2.3.1"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -157,7 +157,7 @@ if (smtpForm) {
|
|||
result.then(function (res) {
|
||||
return res.json();
|
||||
}).then(function (data) {
|
||||
alert(data.error ? 'Invalid SMTP settings\n' + data.error : data.message);
|
||||
alert(data.error ? 'Invalid Mailer settings\n' + data.error : data.message);
|
||||
$btn.button('reset');
|
||||
}).catch(function (err) {
|
||||
alert(err.message);
|
||||
|
|
|
@ -10,10 +10,11 @@ let mailer = require('../lib/mailer');
|
|||
let url = require('url');
|
||||
let multer = require('multer');
|
||||
let upload = multer();
|
||||
let aws = require('aws-sdk');
|
||||
|
||||
let settings = require('../lib/models/settings');
|
||||
|
||||
let allowedKeys = ['service_url', 'smtp_hostname', 'smtp_port', 'smtp_encryption', 'smtp_disable_auth', 'smtp_user', 'smtp_pass', 'admin_email', 'smtp_log', 'smtp_max_connections', 'smtp_max_messages', 'smtp_self_signed', 'default_from', 'default_address', 'default_subject', 'default_homepage', 'default_postaddress', 'default_sender', 'verp_hostname', 'verp_use', 'disable_wysiwyg', 'pgp_private_key', 'pgp_passphrase', 'ua_code', 'shoutout', 'disable_confirmations', 'smtp_throttling', 'dkim_api_key', 'dkim_private_key', 'dkim_selector', 'dkim_domain'];
|
||||
let allowedKeys = ['service_url', 'smtp_hostname', 'smtp_port', 'smtp_encryption', 'smtp_disable_auth', 'smtp_user', 'smtp_pass', 'admin_email', 'smtp_log', 'smtp_max_connections', 'smtp_max_messages', 'smtp_self_signed', 'default_from', 'default_address', 'default_subject', 'default_homepage', 'default_postaddress', 'default_sender', 'verp_hostname', 'verp_use', 'disable_wysiwyg', 'pgp_private_key', 'pgp_passphrase', 'ua_code', 'shoutout', 'disable_confirmations', 'smtp_throttling', 'dkim_api_key', 'dkim_private_key', 'dkim_selector', 'dkim_domain', 'mail_transport', 'ses_key', 'ses_secret', 'ses_region'];
|
||||
|
||||
router.all('/*', (req, res, next) => {
|
||||
if (!req.user) {
|
||||
|
@ -46,6 +47,23 @@ router.get('/', passport.csrfProtection, (req, res, next) => {
|
|||
value: 'Do not use encryption'
|
||||
}];
|
||||
|
||||
configItems.sesRegion = [{
|
||||
checked: configItems.sesRegion === 'us-east-1' || !configItems.sesRegion,
|
||||
key: 'us-east-1',
|
||||
value: 'US-EAST-1'
|
||||
}, {
|
||||
checked: configItems.sesRegion === 'us-west-2',
|
||||
key: 'us-west-2',
|
||||
value: 'US-WEST-2'
|
||||
}, {
|
||||
checked: configItems.sesRegion === 'eu-west-1',
|
||||
key: 'eu-west-1',
|
||||
value: 'EU-WEST-1'
|
||||
}];
|
||||
|
||||
configItems.useSMTP = configItems.mailTransport === 'smtp' || !configItems.mailTransport;
|
||||
configItems.useSES = configItems.mailTransport === 'ses';
|
||||
|
||||
let urlparts = url.parse(configItems.serviceUrl);
|
||||
configItems.verpHostname = configItems.verpHostname || 'bounces.' + (urlparts.hostname || 'localhost');
|
||||
|
||||
|
@ -124,24 +142,48 @@ router.post('/smtp-verify', upload.array(), passport.parseForm, passport.csrfPro
|
|||
}
|
||||
});
|
||||
|
||||
let transport = nodemailer.createTransport({
|
||||
host: data.smtpHostname,
|
||||
port: Number(data.smtpPort) || false,
|
||||
secure: data.smtpEncryption === 'TLS',
|
||||
ignoreTLS: data.smtpEncryption === 'NONE',
|
||||
auth: data.smtpDisableAuth ? false : {
|
||||
user: data.smtpUser,
|
||||
pass: data.smtpPass
|
||||
},
|
||||
tls: {
|
||||
rejectUnauthorized: !data.smtpSelfSigned
|
||||
}
|
||||
});
|
||||
let transportOptions;
|
||||
if (data.mailTransport === 'smtp') {
|
||||
transportOptions = {
|
||||
host: data.smtpHostname,
|
||||
port: Number(data.smtpPort) || false,
|
||||
secure: data.smtpEncryption === 'TLS',
|
||||
ignoreTLS: data.smtpEncryption === 'NONE',
|
||||
auth: data.smtpDisableAuth ? false : {
|
||||
user: data.smtpUser,
|
||||
pass: data.smtpPass
|
||||
},
|
||||
tls: {
|
||||
rejectUnauthorized: !data.smtpSelfSigned
|
||||
}
|
||||
};
|
||||
} else if (data.mailTransport === 'ses') {
|
||||
transportOptions = {
|
||||
SES: new aws.SES({
|
||||
apiVersion: '2010-12-01',
|
||||
accessKeyId: data.sesKey,
|
||||
secretAccessKey: data.sesSecret,
|
||||
region: data.sesRegion
|
||||
})
|
||||
};
|
||||
} else {
|
||||
return res.json({
|
||||
error: 'Invalid mail transport type'
|
||||
});
|
||||
}
|
||||
|
||||
let transport = nodemailer.createTransport(transportOptions);
|
||||
|
||||
transport.verify(err => {
|
||||
if (err) {
|
||||
let message = '';
|
||||
switch (err.code) {
|
||||
case 'InvalidClientTokenId':
|
||||
message = 'Invalid Access Key';
|
||||
break;
|
||||
case 'SignatureDoesNotMatch':
|
||||
message = 'Invalid AWS credentials';
|
||||
break;
|
||||
case 'ECONNREFUSED':
|
||||
message = 'Connection refused, check hostname and port.';
|
||||
break;
|
||||
|
@ -170,11 +212,11 @@ router.post('/smtp-verify', upload.array(), passport.parseForm, passport.csrfPro
|
|||
}
|
||||
|
||||
res.json({
|
||||
error: (message || 'Failed SMTP verification.') + (err.response ? ' Server responded with: "' + err.response + '"' : '')
|
||||
error: (message || 'Failed Mailer verification.') + (err.response ? ' Server responded with: "' + err.response + '"' : '')
|
||||
});
|
||||
} else {
|
||||
res.json({
|
||||
message: 'SMTP settings verified, ready to send some mail!'
|
||||
message: 'Mailer settings verified, ready to send some mail!'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
|
@ -504,7 +504,7 @@ let sendLoop = () => {
|
|||
}
|
||||
|
||||
let status = err ? 2 : 1;
|
||||
let response = err && (err.response || err.message) || info.response;
|
||||
let response = err && (err.response || err.message) || info.response || info.messageId;
|
||||
let responseId = response.split(/\s+/).pop();
|
||||
|
||||
db.getConnection((err, connection) => {
|
||||
|
|
|
@ -127,66 +127,132 @@
|
|||
|
||||
<fieldset>
|
||||
<legend>
|
||||
SMTP Settings
|
||||
Mailer Settings
|
||||
</legend>
|
||||
|
||||
<p class="text-info">These settings are required to send out e-mail messages</p>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="smtp-hostname" class="col-sm-2 control-label">Hostname</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="text" class="form-control" name="smtp-hostname" id="smtp-hostname" placeholder="Hostname, eg. smtp.example.com" value="{{smtpHostname}}" required>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<ul class="nav nav-tabs" role="tablist">
|
||||
<li role="presentation" class="{{#if useSMTP}} active {{/if}}"><a href="#smtp-settings" aria-controls="smtp-settings" role="tab" data-toggle="tab">SMTP</a></li>
|
||||
<li role="presentation" class="{{#if useSES}} active {{/if}}"><a href="#aws-ses" aria-controls="aws-ses" role="tab" data-toggle="tab">AWS SES</a></li>
|
||||
</ul>
|
||||
<div class="tab-content">
|
||||
<div role="tabpanel" class="tab-pane {{#if useSMTP}} active {{/if}}" id="smtp-settings">
|
||||
<p></p>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="smtp-port" class="col-sm-2 control-label">Port</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="number" class="form-control" name="smtp-port" id="smtp-port" placeholder="Port, eg. 465. Autodetected if left blank" value="{{smtpPort}}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-offset-2 col-xs-4">
|
||||
<div class="radio">
|
||||
<label>
|
||||
<input type="radio" name="mail-transport" id="transport-smtp" value="smtp" {{#if useSMTP}} checked {{/if}}>
|
||||
Use SMTP for sending mail
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="smtp-encryption" class="col-sm-2 control-label">Encryption</label>
|
||||
<div class="col-sm-10">
|
||||
<select class="form-control" id="smtp-encryption" name="smtp-encryption">
|
||||
{{#each smtpEncryption}}
|
||||
<option value="{{key}}" {{#if checked}} selected {{/if}}>
|
||||
{{value}}
|
||||
{{#if description}} <span class="text-muted"> — {{description}}</span>{{/if}}
|
||||
</option>
|
||||
{{/each}}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="smtp-hostname" class="col-sm-2 control-label">Hostname</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="text" class="form-control" name="smtp-hostname" id="smtp-hostname" placeholder="Hostname, eg. smtp.example.com" value="{{smtpHostname}}" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="col-sm-offset-2 col-xs-4">
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" name="smtp-disable-auth" {{#if smtpDisableAuth}} checked {{/if}}> Disable SMTP authentication
|
||||
</label>
|
||||
<div class="form-group">
|
||||
<label for="smtp-port" class="col-sm-2 control-label">Port</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="number" class="form-control" name="smtp-port" id="smtp-port" placeholder="Port, eg. 465. Autodetected if left blank" value="{{smtpPort}}">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="smtp-encryption" class="col-sm-2 control-label">Encryption</label>
|
||||
<div class="col-sm-10">
|
||||
<select class="form-control" id="smtp-encryption" name="smtp-encryption">
|
||||
{{#each smtpEncryption}}
|
||||
<option value="{{key}}" {{#if checked}} selected {{/if}}>
|
||||
{{value}}
|
||||
{{#if description}} <span class="text-muted"> — {{description}}</span>{{/if}}
|
||||
</option>
|
||||
{{/each}}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="col-sm-offset-2 col-xs-4">
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" name="smtp-disable-auth" {{#if smtpDisableAuth}} checked {{/if}}> Disable SMTP authentication
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="smtp-user" class="col-sm-2 control-label">Username</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="text" class="form-control" name="smtp-user" id="smtp-user" placeholder="Username, eg. myaccount@example.com" value="{{smtpUser}}">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="smtp-pass" class="col-sm-2 control-label">Password</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="password" class="form-control" name="smtp-pass" id="smtp-pass" placeholder="Password" value="{{smtpPass}}">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div role="tabpanel" class="tab-pane {{#if useSES}} active {{/if}}" id="aws-ses">
|
||||
<p></p>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="col-sm-offset-2 col-xs-4">
|
||||
<div class="radio">
|
||||
<label>
|
||||
<input type="radio" name="mail-transport" id="transport-ses" value="ses" {{#if useSES}} checked {{/if}}>
|
||||
Use SES API for sending mail
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="ses-key" class="col-sm-2 control-label">Access Key</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="text" class="form-control" name="ses-key" id="ses-key" placeholder="AWS Access Key Id" value="{{sesKey}}">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="ses-secret" class="col-sm-2 control-label">Secret Key</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="password" class="form-control" name="ses-secret" id="ses-secret" placeholder="AES Secret Access Key" value="{{sesSecret}}">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="ses-region" class="col-sm-2 control-label">Region</label>
|
||||
<div class="col-sm-10">
|
||||
<select class="form-control" id="ses-region" name="ses-region">
|
||||
{{#each sesRegion}}
|
||||
<option value="{{key}}" {{#if checked}} selected {{/if}}>
|
||||
{{value}}
|
||||
</option>
|
||||
{{/each}}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="smtp-user" class="col-sm-2 control-label">Username</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="text" class="form-control" name="smtp-user" id="smtp-port" placeholder="Username, eg. myaccount@example.com" value="{{smtpUser}}">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="smtp-pass" class="col-sm-2 control-label">Password</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="password" class="form-control" name="smtp-pass" id="smtp-pass" placeholder="Password" value="{{smtpPass}}">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="pull-right">
|
||||
<button type="submit" id="verify-button" form="smtp-verify" class="btn btn-info" data-loading-text="Checking..."><span class="glyphicon glyphicon-refresh" aria-hidden="true"></span> Check SMTP config</button>
|
||||
<button type="submit" id="verify-button" form="smtp-verify" class="btn btn-info" data-loading-text="Checking..."><span class="glyphicon glyphicon-refresh" aria-hidden="true"></span> Check Mailer config</button>
|
||||
</div>
|
||||
<div class="col-sm-offset-2 col-xs-6">
|
||||
<p class="form-control-static">Don't have an SMTP account yet? Create a free SendPulse account <a href="https://sendpulse.com/?utm_source=mailtrain&utm_medium=settings">here</a></p>
|
||||
|
@ -196,7 +262,7 @@
|
|||
|
||||
<fieldset>
|
||||
<legend>
|
||||
Advanced SMTP settings
|
||||
Advanced Mailer settings
|
||||
</legend>
|
||||
|
||||
<div class="form-group">
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue