Merge pull request #573 from trucknet-io/development
feat(api): Transactional mail rest api
This commit is contained in:
commit
3a45443b64
9 changed files with 171 additions and 6 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,4 +1,5 @@
|
||||||
/.idea
|
/.idea
|
||||||
|
/.vscode
|
||||||
/last-failed-e2e-test.*
|
/last-failed-e2e-test.*
|
||||||
|
|
||||||
node_modules
|
node_modules
|
||||||
|
|
|
@ -368,6 +368,37 @@ export default class API extends Component {
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<pre>curl -XGET '{getUrl(`api/rss/fetch/5OOnZKrp0?access_token=${accessToken}`)}'</pre>
|
<pre>curl -XGET '{getUrl(`api/rss/fetch/5OOnZKrp0?access_token=${accessToken}`)}'</pre>
|
||||||
|
|
||||||
|
<h4>POST /api/templates/:templateId/send – {t('sendTransactionalEmail')}</h4>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
{t('sendSingleEmailByTemplateId')}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<strong>GET</strong> {t('arguments')}
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li><strong>access_token</strong> – {t('yourPersonalAccessToken')}</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<strong>POST</strong> {t('arguments')}
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li><strong>EMAIL</strong> – {t('emailAddress')} (<em>{t('required')}</em>)</li>
|
||||||
|
<li><strong>SEND_CONFIGURATION_ID</strong> – {t('sendConfigurationId')}</li>
|
||||||
|
<li><strong>SUBJECT</strong> – {t('subject')}</li>
|
||||||
|
<li><strong>DATA</strong> – {t('templateData')}: <em>{'{'} "any": ["type", {'{'}"of": "data"{'}'}] {'}'}</em></li>
|
||||||
|
<li><strong>VARIABLES</strong> – {t('templateVariables')}: <em>{'{'} "FOO": "bar" {'}'}</em></li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<strong>{t('example')}</strong>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<pre>curl -XPOST '{getUrl(`api/templates/1/send?access_token={accessToken}`)}' \
|
||||||
|
--data 'EMAIL=test@example.com&SUBJECT=Test&VARIABLES[FOO]=bar&VARIABLES[TEST]=example'</pre>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -964,5 +964,10 @@
|
||||||
"thePasswordMustContainAtLeastOne": "The password must contain at least one lowercase letter",
|
"thePasswordMustContainAtLeastOne": "The password must contain at least one lowercase letter",
|
||||||
"thePasswordMustContainAtLeastOne-1": "The password must contain at least one uppercase letter",
|
"thePasswordMustContainAtLeastOne-1": "The password must contain at least one uppercase letter",
|
||||||
"thePasswordMustContainAtLeastOneNumber": "The password must contain at least one number",
|
"thePasswordMustContainAtLeastOneNumber": "The password must contain at least one number",
|
||||||
"thePasswordMustContainAtLeastOneSpecial": "The password must contain at least one special character"
|
"thePasswordMustContainAtLeastOneSpecial": "The password must contain at least one special character",
|
||||||
|
"templateData": "Data passed to template when compiling with Handlebars",
|
||||||
|
"templateVariables": "Map of template/subject variables to replace",
|
||||||
|
"sendTransactionalEmail": "Send transactional email",
|
||||||
|
"sendSingleEmailByTemplateId": "Send single template by :templateId",
|
||||||
|
"sendConfigurationId": "ID of configuration used to create mailer instance"
|
||||||
}
|
}
|
|
@ -964,6 +964,11 @@
|
||||||
"thePasswordMustContainAtLeastOne": "The password must contain at least one lowercase letter",
|
"thePasswordMustContainAtLeastOne": "The password must contain at least one lowercase letter",
|
||||||
"thePasswordMustContainAtLeastOne-1": "The password must contain at least one uppercase letter",
|
"thePasswordMustContainAtLeastOne-1": "The password must contain at least one uppercase letter",
|
||||||
"thePasswordMustContainAtLeastOneNumber": "The password must contain at least one number",
|
"thePasswordMustContainAtLeastOneNumber": "The password must contain at least one number",
|
||||||
"thePasswordMustContainAtLeastOneSpecial": "The password must contain at least one special character"
|
"thePasswordMustContainAtLeastOneSpecial": "The password must contain at least one special character",
|
||||||
|
"templateData": "Data passed to template when compiling with Handlebars",
|
||||||
|
"templateVariables": "Map of template/subject variables to replace",
|
||||||
|
"sendTransactionalEmail": "Send transactional email",
|
||||||
|
"sendSingleEmailByTemplateId": "Send single template by :templateId",
|
||||||
|
"sendConfigurationId": "ID of configuration used to create mailer instance"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
{
|
{
|
||||||
"extends": "nodemailer"
|
"extends": "nodemailer",
|
||||||
|
"parserOptions": { "ecmaVersion": 2018 }
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,10 @@ const fs = require('fs-extra-promise');
|
||||||
const tryRequire = require('try-require');
|
const tryRequire = require('try-require');
|
||||||
const posix = tryRequire('posix');
|
const posix = tryRequire('posix');
|
||||||
|
|
||||||
|
// process.getuid and process.getgid are not supported on Windows
|
||||||
|
process.getuid = process.getuid || (() => 100);
|
||||||
|
process.getgid = process.getuid || (() => 100);
|
||||||
|
|
||||||
function _getConfigUidGid(userKey, groupKey, defaultUid, defaultGid) {
|
function _getConfigUidGid(userKey, groupKey, defaultUid, defaultGid) {
|
||||||
let uid = defaultUid;
|
let uid = defaultUid;
|
||||||
let gid = defaultGid;
|
let gid = defaultGid;
|
||||||
|
|
79
server/lib/template-sender.js
Normal file
79
server/lib/template-sender.js
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const mailers = require('./mailers');
|
||||||
|
const tools = require('./tools');
|
||||||
|
const templates = require('../models/templates');
|
||||||
|
|
||||||
|
class TemplateSender {
|
||||||
|
constructor(options) {
|
||||||
|
this.defaultOptions = {
|
||||||
|
maxMails: 100,
|
||||||
|
...options
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async send(params) {
|
||||||
|
const options = { ...this.defaultOptions, ...params };
|
||||||
|
this._validateMailOptions(options);
|
||||||
|
|
||||||
|
const [mailer, template] = await Promise.all([
|
||||||
|
mailers.getOrCreateMailer(
|
||||||
|
options.sendConfigurationId
|
||||||
|
),
|
||||||
|
templates.getById(
|
||||||
|
options.context,
|
||||||
|
options.templateId,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
]);
|
||||||
|
|
||||||
|
const html = tools.formatTemplate(
|
||||||
|
template.html,
|
||||||
|
null,
|
||||||
|
options.variables,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
const subject = tools.formatTemplate(
|
||||||
|
options.subject || template.description || template.name,
|
||||||
|
options.variables
|
||||||
|
);
|
||||||
|
return mailer.sendTransactionalMail(
|
||||||
|
{
|
||||||
|
to: options.email,
|
||||||
|
subject
|
||||||
|
},
|
||||||
|
{
|
||||||
|
html: { template: html },
|
||||||
|
data: options.data,
|
||||||
|
locale: options.locale
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
_validateMailOptions(options) {
|
||||||
|
let { context, email, locale, templateId } = options;
|
||||||
|
|
||||||
|
if (!templateId) {
|
||||||
|
throw new Error('Missing templateId');
|
||||||
|
}
|
||||||
|
if (!context) {
|
||||||
|
throw new Error('Missing context');
|
||||||
|
}
|
||||||
|
if (!email || email.length === 0) {
|
||||||
|
throw new Error('Missing email');
|
||||||
|
}
|
||||||
|
if (typeof email === 'string') {
|
||||||
|
email = email.split(',');
|
||||||
|
}
|
||||||
|
if (email.length > options.maxMails) {
|
||||||
|
throw new Error(
|
||||||
|
`Cannot send more than ${options.maxMails} emails at once`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (!locale) {
|
||||||
|
throw new Error('Missing locale');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = TemplateSender;
|
|
@ -14,6 +14,7 @@ const mjml2html = require('mjml');
|
||||||
const hbs = require('hbs');
|
const hbs = require('hbs');
|
||||||
const juice = require('juice');
|
const juice = require('juice');
|
||||||
const he = require('he');
|
const he = require('he');
|
||||||
|
const htmlToText = require('html-to-text');
|
||||||
|
|
||||||
const fs = require('fs-extra');
|
const fs = require('fs-extra');
|
||||||
|
|
||||||
|
@ -148,14 +149,21 @@ function validateEmailGetMessage(result, address, language) {
|
||||||
|
|
||||||
function formatMessage(campaign, list, subscription, mergeTags, message, isHTML) {
|
function formatMessage(campaign, list, subscription, mergeTags, message, isHTML) {
|
||||||
const links = getMessageLinks(campaign, list, subscription);
|
const links = getMessageLinks(campaign, list, subscription);
|
||||||
|
return formatTemplate(message, links, mergeTags, isHTML);
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatTemplate(template, links, mergeTags, isHTML) {
|
||||||
|
if (!links && !mergeTags) { return template; }
|
||||||
|
|
||||||
const getValue = fullKey => {
|
const getValue = fullKey => {
|
||||||
const keys = (fullKey || '').split('.');
|
const keys = (fullKey || '').split('.');
|
||||||
|
|
||||||
if (links.hasOwnProperty(keys[0])) {
|
if (links && links.hasOwnProperty(keys[0])) {
|
||||||
return links[keys[0]];
|
return links[keys[0]];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!mergeTags) { return false; }
|
||||||
|
|
||||||
let value = mergeTags;
|
let value = mergeTags;
|
||||||
while (keys.length > 0) {
|
while (keys.length > 0) {
|
||||||
let key = keys.shift();
|
let key = keys.shift();
|
||||||
|
@ -173,7 +181,7 @@ function formatMessage(campaign, list, subscription, mergeTags, message, isHTML)
|
||||||
}) : (containsHTML ? htmlToText.fromString(value) : value);
|
}) : (containsHTML ? htmlToText.fromString(value) : value);
|
||||||
};
|
};
|
||||||
|
|
||||||
return message.replace(/\[([a-z0-9_.]+)(?:\/([^\]]+))?\]/ig, (match, identifier, fallback) => {
|
return template.replace(/\[([a-z0-9_.]+)(?:\/([^\]]+))?\]/ig, (match, identifier, fallback) => {
|
||||||
let value = getValue(identifier);
|
let value = getValue(identifier);
|
||||||
if (value === false) {
|
if (value === false) {
|
||||||
return match;
|
return match;
|
||||||
|
@ -229,6 +237,7 @@ module.exports = {
|
||||||
getTemplate,
|
getTemplate,
|
||||||
prepareHtml,
|
prepareHtml,
|
||||||
getMessageLinks,
|
getMessageLinks,
|
||||||
formatMessage
|
formatMessage,
|
||||||
|
formatTemplate
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,7 @@ const contextHelpers = require('../lib/context-helpers');
|
||||||
const shares = require('../models/shares');
|
const shares = require('../models/shares');
|
||||||
const slugify = require('slugify');
|
const slugify = require('slugify');
|
||||||
const passport = require('../lib/passport');
|
const passport = require('../lib/passport');
|
||||||
|
const TemplateSender = require('../lib/template-sender');
|
||||||
const campaigns = require('../models/campaigns');
|
const campaigns = require('../models/campaigns');
|
||||||
|
|
||||||
class APIError extends Error {
|
class APIError extends Error {
|
||||||
|
@ -285,5 +286,34 @@ router.getAsync('/rss/fetch/:campaignCid', passport.loggedIn, async (req, res) =
|
||||||
return res.json();
|
return res.json();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
router.postAsync('/templates/:templateId/send', async (req, res) => {
|
||||||
|
const input = {};
|
||||||
|
Object.keys(req.body).forEach(key => {
|
||||||
|
input[
|
||||||
|
(key || '')
|
||||||
|
.toString()
|
||||||
|
.trim()
|
||||||
|
.toUpperCase()
|
||||||
|
] = req.body[key] || '';
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
const templateSender = new TemplateSender({
|
||||||
|
context: req.context,
|
||||||
|
locale: req.locale,
|
||||||
|
templateId: req.params.templateId
|
||||||
|
});
|
||||||
|
const info = await templateSender.send({
|
||||||
|
data: input.DATA,
|
||||||
|
email: input.EMAIL,
|
||||||
|
sendConfigurationId: input.SEND_CONFIGURATION_ID,
|
||||||
|
subject: input.SUBJECT,
|
||||||
|
variables: input.VARIABLES
|
||||||
|
});
|
||||||
|
res.status(200).json({ data: info });
|
||||||
|
} catch (e) {
|
||||||
|
throw new APIError(e.message, 400);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue