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
|
# 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
|
## 1.20.0 2016-12-11
|
||||||
|
|
||||||
* Added option to distribute sending queue between multiple processes to speed up delivery
|
* 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
|
European Union Public Licence
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
V. 1.1
|
||||||
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:
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
EUPL (c) the European Community 2007
|
||||||
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
|
This European Union Public Licence (the "EUPL") applies to the Work or Software
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
(as defined below) which is provided under the terms of this Licence. Any use
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
of the Work, other than as authorised under this Licence is prohibited (to the
|
||||||
SOFTWARE.
|
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
|
## License
|
||||||
|
|
||||||
|
* Versions 1.21.0 and up: **EUPL-1.1**
|
||||||
* Versions 1.19.0 and up: **MIT**
|
* Versions 1.19.0 and up: **MIT**
|
||||||
* Up to versions 1.18.0 **GPL-V3.0**
|
* 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
|
# In most cases you do not want to use it
|
||||||
# Requires root privileges
|
# Requires root privileges
|
||||||
enabled=false
|
enabled=false
|
||||||
port=25
|
port=2525
|
||||||
host="0.0.0.0"
|
host="0.0.0.0"
|
||||||
|
|
||||||
[testserver]
|
[testserver]
|
||||||
|
|
126
lib/mailer.js
126
lib/mailer.js
|
@ -12,6 +12,7 @@ let fs = require('fs');
|
||||||
let path = require('path');
|
let path = require('path');
|
||||||
let templates = new Map();
|
let templates = new Map();
|
||||||
let htmlToText = require('html-to-text');
|
let htmlToText = require('html-to-text');
|
||||||
|
let aws = require('aws-sdk');
|
||||||
|
|
||||||
module.exports.transport = false;
|
module.exports.transport = false;
|
||||||
|
|
||||||
|
@ -121,7 +122,7 @@ function getTemplate(template, callback) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function createMailer(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) {
|
if (err) {
|
||||||
return callback(err);
|
return callback(err);
|
||||||
}
|
}
|
||||||
|
@ -134,28 +135,70 @@ function createMailer(callback) {
|
||||||
module.exports.transport.checkThrottling = null;
|
module.exports.transport.checkThrottling = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports.transport = nodemailer.createTransport({
|
let throttling = Number(configItems.smtpThrottling) || 0;
|
||||||
pool: true,
|
if (throttling) {
|
||||||
host: configItems.smtpHostname,
|
// convert to messages/second
|
||||||
port: Number(configItems.smtpPort) || false,
|
throttling = 1 / (throttling / (3600 * 1000));
|
||||||
secure: configItems.smtpEncryption === 'TLS',
|
}
|
||||||
ignoreTLS: configItems.smtpEncryption === 'NONE',
|
|
||||||
auth: configItems.smtpDisableAuth ? false : {
|
let transportOptions;
|
||||||
user: configItems.smtpUser,
|
|
||||||
pass: configItems.smtpPass
|
let logfunc = function () {
|
||||||
},
|
let args = [].slice.call(arguments);
|
||||||
debug: !!configItems.smtpLog,
|
let level = args.shift();
|
||||||
logger: !configItems.smtpLog ? false : {
|
args.shift();
|
||||||
debug: log.verbose.bind(log, 'Mail'),
|
args.unshift('Mail');
|
||||||
info: log.info.bind(log, 'Mail'),
|
log[level].apply(log, args);
|
||||||
error: log.error.bind(log, 'Mail')
|
};
|
||||||
},
|
|
||||||
maxConnections: Number(configItems.smtpMaxConnections),
|
if (configItems.mailTransport === 'smtp' || !configItems.mailTransport) {
|
||||||
maxMessages: Number(configItems.smtpMaxMessages),
|
transportOptions = {
|
||||||
tls: {
|
pool: true,
|
||||||
rejectUnauthorized: !configItems.smtpSelfSigned
|
host: configItems.smtpHostname,
|
||||||
}
|
port: Number(configItems.smtpPort) || false,
|
||||||
}, config.nodemailer);
|
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({
|
module.exports.transport.use('stream', openpgpEncrypt({
|
||||||
signingKey: configItems.pgpPrivateKey,
|
signingKey: configItems.pgpPrivateKey,
|
||||||
|
@ -167,26 +210,25 @@ function createMailer(callback) {
|
||||||
oldListeners.forEach(listener => module.exports.transport.on('idle', listener));
|
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();
|
let lastCheck = Date.now();
|
||||||
module.exports.transport.checkThrottling = function (next) {
|
if (configItems.mailTransport === 'smtp' || !configItems.mailTransport) {
|
||||||
if (!throttling) {
|
module.exports.transport.checkThrottling = function (next) {
|
||||||
return next();
|
if (!throttling) {
|
||||||
}
|
return next();
|
||||||
let nextCheck = Date.now();
|
}
|
||||||
let checkDiff = (nextCheck - lastCheck);
|
let nextCheck = Date.now();
|
||||||
lastCheck = nextCheck;
|
let checkDiff = (nextCheck - lastCheck);
|
||||||
if (checkDiff < throttling) {
|
lastCheck = nextCheck;
|
||||||
log.verbose('Mail', 'Throttling next message in %s sec.', (throttling - checkDiff) / 1000);
|
if (checkDiff < throttling) {
|
||||||
setTimeout(next, throttling - checkDiff);
|
log.verbose('Mail', 'Throttling next message in %s sec.', (throttling - checkDiff) / 1000);
|
||||||
} else {
|
setTimeout(next, throttling - checkDiff);
|
||||||
next();
|
} else {
|
||||||
}
|
next();
|
||||||
};
|
}
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
module.exports.transport.checkThrottling = next => next();
|
||||||
|
}
|
||||||
|
|
||||||
db.clearCache('sender', () => {
|
db.clearCache('sender', () => {
|
||||||
callback(null, module.exports.transport);
|
callback(null, module.exports.transport);
|
||||||
|
|
|
@ -266,7 +266,7 @@ module.exports.add = (url, campaignId, callback) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports.updateLinks = (campaign, list, subscription, serviceUrl, message, 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
|
// tracking is disabled, do not modify the message
|
||||||
return setImmediate(() => callback(null, message));
|
return setImmediate(() => callback(null, message));
|
||||||
}
|
}
|
||||||
|
|
27
package.json
27
package.json
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "mailtrain",
|
"name": "mailtrain",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "1.20.0",
|
"version": "1.21.0",
|
||||||
"description": "Self hosted email newsletter app",
|
"description": "Self hosted email newsletter app",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
@ -17,8 +17,8 @@
|
||||||
"url": "git://github.com/andris9/mailtrain.git"
|
"url": "git://github.com/andris9/mailtrain.git"
|
||||||
},
|
},
|
||||||
"author": "Andris Reinman",
|
"author": "Andris Reinman",
|
||||||
"license": "MIT",
|
"license": "EUPL-1.1",
|
||||||
"homepage": "http://mailtrain.org",
|
"homepage": "https://mailtrain.org/",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=5.0.0"
|
"node": ">=5.0.0"
|
||||||
},
|
},
|
||||||
|
@ -29,11 +29,12 @@
|
||||||
"grunt-eslint": "^19.0.0"
|
"grunt-eslint": "^19.0.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"aws-sdk": "^2.15.0",
|
||||||
"bcrypt-nodejs": "0.0.3",
|
"bcrypt-nodejs": "0.0.3",
|
||||||
"body-parser": "^1.16.0",
|
"body-parser": "^1.16.1",
|
||||||
"bounce-handler": "^7.3.2-fork.2",
|
"bounce-handler": "^7.3.2-fork.2",
|
||||||
"compression": "^1.6.2",
|
"compression": "^1.6.2",
|
||||||
"config": "^1.24.0",
|
"config": "^1.25.1",
|
||||||
"connect-flash": "^0.1.1",
|
"connect-flash": "^0.1.1",
|
||||||
"connect-redis": "^3.2.0",
|
"connect-redis": "^3.2.0",
|
||||||
"cookie-parser": "^1.4.3",
|
"cookie-parser": "^1.4.3",
|
||||||
|
@ -42,30 +43,30 @@
|
||||||
"csv-parse": "^1.2.0",
|
"csv-parse": "^1.2.0",
|
||||||
"escape-html": "^1.0.3",
|
"escape-html": "^1.0.3",
|
||||||
"express": "^4.14.1",
|
"express": "^4.14.1",
|
||||||
"express-session": "^1.15.0",
|
"express-session": "^1.15.1",
|
||||||
"faker": "^3.1.0",
|
"faker": "^3.1.0",
|
||||||
"feedparser": "^2.1.0",
|
"feedparser": "^2.1.0",
|
||||||
"geoip-ultralight": "^0.1.4",
|
"geoip-ultralight": "^0.1.4",
|
||||||
"handlebars": "^4.0.6",
|
"handlebars": "^4.0.6",
|
||||||
"hbs": "^4.0.1",
|
"hbs": "^4.0.1",
|
||||||
"he": "^1.1.1",
|
"he": "^1.1.1",
|
||||||
"html-to-text": "^3.0.0",
|
"html-to-text": "^3.1.0",
|
||||||
"humanize": "0.0.9",
|
"humanize": "0.0.9",
|
||||||
"is-url": "^1.2.2",
|
"is-url": "^1.2.2",
|
||||||
"isemail": "^2.2.1",
|
"isemail": "^2.2.1",
|
||||||
"jsdom": "^9.9.1",
|
"jsdom": "^9.11.0",
|
||||||
"juice": "^4.0.2",
|
"juice": "^4.0.2",
|
||||||
"libmime": "^3.1.0",
|
"libmime": "^3.1.0",
|
||||||
"marked": "^0.3.6",
|
"marked": "^0.3.6",
|
||||||
"mkdirp": "^0.5.1",
|
"mkdirp": "^0.5.1",
|
||||||
"moment-timezone": "^0.5.11",
|
"moment-timezone": "^0.5.11",
|
||||||
"morgan": "^1.7.0",
|
"morgan": "^1.8.1",
|
||||||
"multer": "^1.3.0",
|
"multer": "^1.3.0",
|
||||||
"mysql": "^2.13.0",
|
"mysql": "^2.13.0",
|
||||||
"nodemailer": "^2.7.2",
|
"nodemailer": "^3.1.3",
|
||||||
"nodemailer-openpgp": "^1.0.2",
|
"nodemailer-openpgp": "^1.0.2",
|
||||||
"npmlog": "^4.0.2",
|
"npmlog": "^4.0.2",
|
||||||
"openpgp": "^2.3.6",
|
"openpgp": "^2.3.7",
|
||||||
"passport": "^0.3.2",
|
"passport": "^0.3.2",
|
||||||
"passport-local": "^1.0.0",
|
"passport-local": "^1.0.0",
|
||||||
"redfour": "^1.0.0",
|
"redfour": "^1.0.0",
|
||||||
|
@ -74,8 +75,8 @@
|
||||||
"serve-favicon": "^2.3.2",
|
"serve-favicon": "^2.3.2",
|
||||||
"shortid": "^2.2.6",
|
"shortid": "^2.2.6",
|
||||||
"slugify": "^1.1.0",
|
"slugify": "^1.1.0",
|
||||||
"smtp-server": "^1.17.0",
|
"smtp-server": "^2.0.2",
|
||||||
"striptags": "^2.2.1",
|
"striptags": "^3.0.1",
|
||||||
"toml": "^2.3.1"
|
"toml": "^2.3.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -157,7 +157,7 @@ if (smtpForm) {
|
||||||
result.then(function (res) {
|
result.then(function (res) {
|
||||||
return res.json();
|
return res.json();
|
||||||
}).then(function (data) {
|
}).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');
|
$btn.button('reset');
|
||||||
}).catch(function (err) {
|
}).catch(function (err) {
|
||||||
alert(err.message);
|
alert(err.message);
|
||||||
|
|
|
@ -10,10 +10,11 @@ let mailer = require('../lib/mailer');
|
||||||
let url = require('url');
|
let url = require('url');
|
||||||
let multer = require('multer');
|
let multer = require('multer');
|
||||||
let upload = multer();
|
let upload = multer();
|
||||||
|
let aws = require('aws-sdk');
|
||||||
|
|
||||||
let settings = require('../lib/models/settings');
|
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) => {
|
router.all('/*', (req, res, next) => {
|
||||||
if (!req.user) {
|
if (!req.user) {
|
||||||
|
@ -46,6 +47,23 @@ router.get('/', passport.csrfProtection, (req, res, next) => {
|
||||||
value: 'Do not use encryption'
|
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);
|
let urlparts = url.parse(configItems.serviceUrl);
|
||||||
configItems.verpHostname = configItems.verpHostname || 'bounces.' + (urlparts.hostname || 'localhost');
|
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({
|
let transportOptions;
|
||||||
host: data.smtpHostname,
|
if (data.mailTransport === 'smtp') {
|
||||||
port: Number(data.smtpPort) || false,
|
transportOptions = {
|
||||||
secure: data.smtpEncryption === 'TLS',
|
host: data.smtpHostname,
|
||||||
ignoreTLS: data.smtpEncryption === 'NONE',
|
port: Number(data.smtpPort) || false,
|
||||||
auth: data.smtpDisableAuth ? false : {
|
secure: data.smtpEncryption === 'TLS',
|
||||||
user: data.smtpUser,
|
ignoreTLS: data.smtpEncryption === 'NONE',
|
||||||
pass: data.smtpPass
|
auth: data.smtpDisableAuth ? false : {
|
||||||
},
|
user: data.smtpUser,
|
||||||
tls: {
|
pass: data.smtpPass
|
||||||
rejectUnauthorized: !data.smtpSelfSigned
|
},
|
||||||
}
|
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 => {
|
transport.verify(err => {
|
||||||
if (err) {
|
if (err) {
|
||||||
let message = '';
|
let message = '';
|
||||||
switch (err.code) {
|
switch (err.code) {
|
||||||
|
case 'InvalidClientTokenId':
|
||||||
|
message = 'Invalid Access Key';
|
||||||
|
break;
|
||||||
|
case 'SignatureDoesNotMatch':
|
||||||
|
message = 'Invalid AWS credentials';
|
||||||
|
break;
|
||||||
case 'ECONNREFUSED':
|
case 'ECONNREFUSED':
|
||||||
message = 'Connection refused, check hostname and port.';
|
message = 'Connection refused, check hostname and port.';
|
||||||
break;
|
break;
|
||||||
|
@ -170,11 +212,11 @@ router.post('/smtp-verify', upload.array(), passport.parseForm, passport.csrfPro
|
||||||
}
|
}
|
||||||
|
|
||||||
res.json({
|
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 {
|
} else {
|
||||||
res.json({
|
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 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();
|
let responseId = response.split(/\s+/).pop();
|
||||||
|
|
||||||
db.getConnection((err, connection) => {
|
db.getConnection((err, connection) => {
|
||||||
|
|
|
@ -127,66 +127,132 @@
|
||||||
|
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend>
|
<legend>
|
||||||
SMTP Settings
|
Mailer Settings
|
||||||
</legend>
|
</legend>
|
||||||
|
|
||||||
<p class="text-info">These settings are required to send out e-mail messages</p>
|
<p class="text-info">These settings are required to send out e-mail messages</p>
|
||||||
|
|
||||||
<div class="form-group">
|
<div>
|
||||||
<label for="smtp-hostname" class="col-sm-2 control-label">Hostname</label>
|
<ul class="nav nav-tabs" role="tablist">
|
||||||
<div class="col-sm-10">
|
<li role="presentation" class="{{#if useSMTP}} active {{/if}}"><a href="#smtp-settings" aria-controls="smtp-settings" role="tab" data-toggle="tab">SMTP</a></li>
|
||||||
<input type="text" class="form-control" name="smtp-hostname" id="smtp-hostname" placeholder="Hostname, eg. smtp.example.com" value="{{smtpHostname}}" required>
|
<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>
|
||||||
</div>
|
</ul>
|
||||||
</div>
|
<div class="tab-content">
|
||||||
|
<div role="tabpanel" class="tab-pane {{#if useSMTP}} active {{/if}}" id="smtp-settings">
|
||||||
|
<p></p>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="smtp-port" class="col-sm-2 control-label">Port</label>
|
<div class="col-sm-offset-2 col-xs-4">
|
||||||
<div class="col-sm-10">
|
<div class="radio">
|
||||||
<input type="number" class="form-control" name="smtp-port" id="smtp-port" placeholder="Port, eg. 465. Autodetected if left blank" value="{{smtpPort}}">
|
<label>
|
||||||
</div>
|
<input type="radio" name="mail-transport" id="transport-smtp" value="smtp" {{#if useSMTP}} checked {{/if}}>
|
||||||
</div>
|
Use SMTP for sending mail
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="smtp-encryption" class="col-sm-2 control-label">Encryption</label>
|
<label for="smtp-hostname" class="col-sm-2 control-label">Hostname</label>
|
||||||
<div class="col-sm-10">
|
<div class="col-sm-10">
|
||||||
<select class="form-control" id="smtp-encryption" name="smtp-encryption">
|
<input type="text" class="form-control" name="smtp-hostname" id="smtp-hostname" placeholder="Hostname, eg. smtp.example.com" value="{{smtpHostname}}" required>
|
||||||
{{#each smtpEncryption}}
|
</div>
|
||||||
<option value="{{key}}" {{#if checked}} selected {{/if}}>
|
</div>
|
||||||
{{value}}
|
|
||||||
{{#if description}} <span class="text-muted"> — {{description}}</span>{{/if}}
|
|
||||||
</option>
|
|
||||||
{{/each}}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="col-sm-offset-2 col-xs-4">
|
<label for="smtp-port" class="col-sm-2 control-label">Port</label>
|
||||||
<div class="checkbox">
|
<div class="col-sm-10">
|
||||||
<label>
|
<input type="number" class="form-control" name="smtp-port" id="smtp-port" placeholder="Port, eg. 465. Autodetected if left blank" value="{{smtpPort}}">
|
||||||
<input type="checkbox" name="smtp-disable-auth" {{#if smtpDisableAuth}} checked {{/if}}> Disable SMTP authentication
|
</div>
|
||||||
</label>
|
</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>
|
||||||
</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="form-group">
|
||||||
<div class="pull-right">
|
<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>
|
||||||
<div class="col-sm-offset-2 col-xs-6">
|
<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>
|
<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>
|
<fieldset>
|
||||||
<legend>
|
<legend>
|
||||||
Advanced SMTP settings
|
Advanced Mailer settings
|
||||||
</legend>
|
</legend>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue