diff --git a/.gitignore b/.gitignore
index 47825b2b..b9a299f9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -15,19 +15,6 @@ dump.rdb
# generate POT file every time you want to update your PO file
languages/mailtrain.pot
-public/mosaico/uploads/*
-!public/mosaico/uploads/README.md
-public/mosaico/custom/*
-!public/mosaico/custom/README.md
-public/mosaico/templates/*
-!public/mosaico/templates/versafix-1
-
-public/grapejs/uploads/*
-!public/grapejs/uploads/README.md
-public/grapejs/templates/*
-!public/grapejs/templates/demo
-!public/grapejs/templates/aves
-
config/production.toml
workers/reports/config/production.toml
docker-compose.override.yml
diff --git a/UPGRADE.md b/UPGRADE.md
index 9d16d0dd..4e5b8d8f 100644
--- a/UPGRADE.md
+++ b/UPGRADE.md
@@ -6,8 +6,8 @@ The migration should almost happen automatically. There are however the followin
and update your configs accordingly.
2. Images uploaded in a template editor (Mosaico, GrapeJS, etc.) need to be manually moved to a new destination (under `client`).
- For Mosaico, this means to move folders named by a number from `public/mosaico` to `client/public/mosaico`.
+ For Mosaico, this means to move folders named by a number from `public/mosaico` to `client/static/mosaico`.
-3. Directory for custom Mosaico templates has changed from `public/mosaico/templates` to `client/public/mosaico/templates`.
+3. Directory for custom Mosaico templates has changed from `public/mosaico/templates` to `client/static/mosaico/templates`.
4. Imports are not migrated. If you have any pending imports, complete them before migration to v2.
\ No newline at end of file
diff --git a/app-builder.js b/app-builder.js
index 2c173562..fbf03ef5 100644
--- a/app-builder.js
+++ b/app-builder.js
@@ -54,6 +54,9 @@ const index = require('./routes/index');
const interoperableErrors = require('./shared/interoperable-errors');
+const { getTrustedUrl } = require('./lib/urls');
+const { AppType } = require('./shared/app');
+
hbs.registerPartials(__dirname + '/views/partials');
hbs.registerPartials(__dirname + '/views/subscription/partials/');
@@ -104,7 +107,8 @@ hbs.registerHelper('flash_messages', function () { // eslint-disable-line prefer
handlebarsHelpers.registerHelpers(hbs.handlebars);
-function createApp(trusted) {
+
+function createApp(appType) {
const app = express();
function install404Fallback(url) {
@@ -171,9 +175,9 @@ function createApp(trusted) {
limit: config.www.postSize
}));
- if (trusted) {
+ if (appType === AppType.TRUSTED) {
passport.setupRegularAuth(app);
- } else {
+ } else if (appType === AppType.SANDBOXED) {
app.use(passport.tryAuthByRestrictedAccessToken);
}
@@ -189,52 +193,6 @@ function createApp(trusted) {
next();
});
- /* FIXME - can we remove this???
- app.use((req, res, next) => {
- res.locals.flash = req.flash.bind(req);
- res.locals.user = req.user;
- res.locals.admin = req.user && req.user.id == 1; // FIXME, this should verify the admin privileges and set this accordingly
- res.locals.ldap = {
- enabled: config.ldap.enabled,
- passwordresetlink: config.ldap.passwordresetlink
- };
-
- let menu = [{
- title: _('Home'),
- url: '/',
- selected: true
- }];
-
- res.setSelectedMenu = key => {
- menu.forEach(item => {
- item.selected = (item.key === key);
- });
- };
-
- res.locals.menu = menu;
- tools.updateMenu(res);
-
- res.locals.customStyles = config.customstyles || [];
- res.locals.customScripts = config.customscripts || [];
-
- let bodyClasses = [];
- if (req.user) {
- bodyClasses.push('logged-in user-' + req.user.username);
- }
- res.locals.bodyClass = bodyClasses.join(' ');
-
- getSettings(['uaCode', 'shoutout'], (err, configItems) => {
- if (err) {
- return next(err);
- }
- Object.keys(configItems).forEach(key => {
- res.locals[key] = configItems[key];
- });
- next();
- });
- });
- */
-
// Endpoint under /api are authenticated by access token
app.all('/api/*', passport.authByAccessToken);
@@ -244,62 +202,64 @@ function createApp(trusted) {
next();
});
-
app.all('/rest/*', (req, res, next) => {
req.needsRESTJSONResponse = true;
next();
});
-
// Initializes the request context to be used for authorization
app.use((req, res, next) => {
req.context = contextHelpers.getRequestContext(req);
next();
});
-
- // Regular endpoints
- useWith404Fallback('/subscription', subscription);
- useWith404Fallback('/files', files);
- useWith404Fallback('/mosaico', mosaico.getRouter(trusted));
-
- if (config.reports && config.reports.enabled === true) {
- useWith404Fallback('/reports', reports);
+ if (appType === AppType.PUBLIC) {
+ useWith404Fallback('/subscription', subscription);
}
+ if (appType === AppType.TRUSTED || appType === AppType.SANDBOXED) {
+ // Regular endpoints
+ useWith404Fallback('/files', files);
+ useWith404Fallback('/mosaico', mosaico.getRouter(appType));
- // API endpoints
- useWith404Fallback('/api', api);
+ if (config.reports && config.reports.enabled === true) {
+ useWith404Fallback('/reports', reports);
+ }
- // REST endpoints
- app.use('/rest', namespacesRest);
- app.use('/rest', sendConfigurationsRest);
- app.use('/rest', usersRest);
- app.use('/rest', accountRest);
- app.use('/rest', campaignsRest);
- app.use('/rest', triggersRest);
- app.use('/rest', listsRest);
- app.use('/rest', formsRest);
- app.use('/rest', fieldsRest);
- app.use('/rest', importsRest);
- app.use('/rest', importRunsRest);
- app.use('/rest', sharesRest);
- app.use('/rest', segmentsRest);
- app.use('/rest', subscriptionsRest);
- app.use('/rest', templatesRest);
- app.use('/rest', mosaicoTemplatesRest);
- app.use('/rest', blacklistRest);
- app.use('/rest', editorsRest);
- app.use('/rest', filesRest);
- app.use('/rest', settingsRest);
- if (config.reports && config.reports.enabled === true) {
- app.use('/rest', reportTemplatesRest);
- app.use('/rest', reportsRest);
+ // API endpoints
+ useWith404Fallback('/api', api);
+
+ // REST endpoints
+ app.use('/rest', namespacesRest);
+ app.use('/rest', sendConfigurationsRest);
+ app.use('/rest', usersRest);
+ app.use('/rest', accountRest);
+ app.use('/rest', campaignsRest);
+ app.use('/rest', triggersRest);
+ app.use('/rest', listsRest);
+ app.use('/rest', formsRest);
+ app.use('/rest', fieldsRest);
+ app.use('/rest', importsRest);
+ app.use('/rest', importRunsRest);
+ app.use('/rest', sharesRest);
+ app.use('/rest', segmentsRest);
+ app.use('/rest', subscriptionsRest);
+ app.use('/rest', templatesRest);
+ app.use('/rest', mosaicoTemplatesRest);
+ app.use('/rest', blacklistRest);
+ app.use('/rest', editorsRest);
+ app.use('/rest', filesRest);
+ app.use('/rest', settingsRest);
+
+ if (config.reports && config.reports.enabled === true) {
+ app.use('/rest', reportTemplatesRest);
+ app.use('/rest', reportsRest);
+ }
+ install404Fallback('/rest');
}
- install404Fallback('/rest');
- app.use('/', index.getRouter(trusted));
+ app.use('/', index.getRouter(appType));
// Error handlers
if (app.get('env') === 'development') {
@@ -333,7 +293,7 @@ function createApp(trusted) {
} else {
if (err instanceof interoperableErrors.NotLoggedInError) {
- return res.redirect('/account/login?next=' + encodeURIComponent(req.originalUrl));
+ return res.redirect(getTrustedUrl('/account/login?next=' + encodeURIComponent(req.originalUrl)));
} else {
res.status(err.status || 500);
res.render('error', {
@@ -378,7 +338,7 @@ function createApp(trusted) {
// TODO: Render interoperable errors using a special client that does internationalization of the error message
if (err instanceof interoperableErrors.NotLoggedInError) {
- return res.redirect('/account/login?next=' + encodeURIComponent(req.originalUrl));
+ return res.redirect(getTrustedUrl('/account/login?next=' + encodeURIComponent(req.originalUrl)));
} else {
res.status(err.status || 500);
res.render('error', {
@@ -393,6 +353,4 @@ function createApp(trusted) {
return app;
}
-module.exports = {
- createApp
-};
+module.exports.createApp = createApp;
diff --git a/client/src/campaigns/Content.js b/client/src/campaigns/Content.js
index af42eddc..c421281a 100644
--- a/client/src/campaigns/Content.js
+++ b/client/src/campaigns/Content.js
@@ -40,7 +40,6 @@ export default class CustomContent extends Component {
const t = props.t;
- console.log(props);
this.templateTypes = getTemplateTypes(props.t, 'data_sourceCustom_', ResourceType.CAMPAIGN);
this.customTemplateTypeOptions = [];
diff --git a/client/src/campaigns/helpers.js b/client/src/campaigns/helpers.js
index b78cbeb2..f4fd10de 100644
--- a/client/src/campaigns/helpers.js
+++ b/client/src/campaigns/helpers.js
@@ -29,3 +29,56 @@ export function getCampaignLabels(t) {
campaignTypeLabels
};
}
+
+/* FIXME - this is not used at the moment, but it's kept here because it will be probably needed at some later point of time.
+export function getDefaultMergeTags(t) {
+ return [{
+ key: 'LINK_UNSUBSCRIBE',
+ value: t('URL that points to the unsubscribe page')
+ }, {
+ key: 'LINK_PREFERENCES',
+ value: t('URL that points to the preferences page of the subscriber')
+ }, {
+ key: 'LINK_BROWSER',
+ value: t('URL to preview the message in a browser')
+ }, {
+ key: 'EMAIL',
+ value: t('Email address')
+ }, {
+ key: 'SUBSCRIPTION_ID',
+ value: t('Unique ID that identifies the recipient')
+ }, {
+ key: 'LIST_ID',
+ value: t('Unique ID that identifies the list used for this campaign')
+ }, {
+ key: 'CAMPAIGN_ID',
+ value: t('Unique ID that identifies current campaign')
+ }];
+}
+
+export function getRSSMergeTags(t) {
+ return [{
+ key: 'RSS_ENTRY',
+ value: t('content from an RSS entry')
+ }, {
+ key: 'RSS_ENTRY_TITLE',
+ value: t('RSS entry title')
+ }, {
+ key: 'RSS_ENTRY_DATE',
+ value: t('RSS entry date')
+ }, {
+ key: 'RSS_ENTRY_LINK',
+ value: t('RSS entry link')
+ }, {
+ key: 'RSS_ENTRY_CONTENT',
+ value: t('content from an RSS entry')
+ }, {
+ key: 'RSS_ENTRY_SUMMARY',
+ value: t('RSS entry summary')
+ }, {
+ key: 'RSS_ENTRY_IMAGE_URL',
+ value: t('RSS entry image URL')
+ }];
+}
+*/
+
diff --git a/client/src/lib/mosaico.js b/client/src/lib/mosaico.js
index fada9847..cd4c53f9 100644
--- a/client/src/lib/mosaico.js
+++ b/client/src/lib/mosaico.js
@@ -70,7 +70,7 @@ export class MosaicoEditor extends Component {
return (
- {this.state.fullscreen &&
}/)
}
+ {this.state.fullscreen &&
}/)
}
{this.props.title}
@@ -142,7 +142,7 @@ export class MosaicoSandbox extends Component {
plugins.unshift(vm => {
// This is an override of the default paths in Mosaico
- vm.logoPath = getTrustedUrl('public/mosaico/img/mosaico32.png');
+ vm.logoPath = getTrustedUrl('static/mosaico/img/mosaico32.png');
vm.logoUrl = '#';
});
diff --git a/client/src/lib/urls.js b/client/src/lib/urls.js
index d42c112e..4374498a 100644
--- a/client/src/lib/urls.js
+++ b/client/src/lib/urls.js
@@ -1,6 +1,7 @@
'use strict';
import {anonymousRestrictedAccessToken} from '../../../shared/urls';
+import {AppType} from '../../../shared/app';
import mailtrainConfig from "mailtrainConfig";
let restrictedAccessToken = anonymousRestrictedAccessToken;
@@ -17,25 +18,34 @@ function getSandboxUrl(path) {
return mailtrainConfig.sandboxUrlBase + restrictedAccessToken + '/' + (path || '');
}
+function getPublicUrl(path) {
+ return mailtrainConfig.publicUrlBase + (path || '');
+}
+
function getUrl(path) {
- if (mailtrainConfig.trusted) {
+ if (mailtrainConfig.appType === AppType.TRUSTED) {
return getTrustedUrl(path);
- } else {
+ } else if (mailtrainConfig.appType === AppType.SANDBOXED) {
return getSandboxUrl(path);
+ } else if (mailtrainConfig.appType === AppType.PUBLIC) {
+ return getPublicUrl(path);
}
}
function getBaseDir() {
- if (mailtrainConfig.trusted) {
+ if (mailtrainConfig.appType === AppType.TRUSTED) {
return mailtrainConfig.trustedUrlBaseDir;
- } else {
+ } else if (mailtrainConfig.appType === AppType.SANDBOXED) {
return mailtrainConfig.sandboxUrlBaseDir + anonymousRestrictedAccessToken;
+ } else if (mailtrainConfig.appType === AppType.PUBLIC) {
+ return mailtrainConfig.publicUrlBaseDir;
}
}
export {
getTrustedUrl,
getSandboxUrl,
+ getPublicUrl,
getUrl,
getBaseDir,
setRestrictedAccessToken
diff --git a/client/src/lists/CUD.js b/client/src/lists/CUD.js
index 877cd3da..119e82bd 100644
--- a/client/src/lists/CUD.js
+++ b/client/src/lists/CUD.js
@@ -52,7 +52,8 @@ export default class CUD extends Component {
contact_email: '',
homepage: '',
unsubscription_mode: UnsubscriptionMode.ONE_STEP,
- namespace: mailtrainConfig.user.namespace
+ namespace: mailtrainConfig.user.namespace,
+ to_name: '[FIRST_NAME] [LAST_NAME]'
});
}
}
@@ -186,6 +187,7 @@ export default class CUD extends Component {
+
diff --git a/client/src/lists/subscriptions/List.js b/client/src/lists/subscriptions/List.js
index 50fe95df..fe0faa6d 100644
--- a/client/src/lists/subscriptions/List.js
+++ b/client/src/lists/subscriptions/List.js
@@ -15,7 +15,7 @@ import {
import {Icon, Button} from "../../lib/bootstrap-components";
import axios from '../../lib/axios';
import {getFieldTypes, getSubscriptionStatusLabels} from './helpers';
-import {getUrl} from "../../lib/urls";
+import {getUrl, getPublicUrl} from "../../lib/urls";
@translate()
@withForm
@@ -158,7 +158,7 @@ export default class List extends Component {
return (
-
+
diff --git a/client/src/templates/helpers.js b/client/src/templates/helpers.js
index 22b58965..ed360f67 100644
--- a/client/src/templates/helpers.js
+++ b/client/src/templates/helpers.js
@@ -134,7 +134,7 @@ export function getTemplateTypes(t, prefix = '', entityTypeId = ResourceType.TEM
entity={owner.props.entity}
initialModel={owner.getFormValue(prefix + 'mosaicoData').model}
initialMetadata={owner.getFormValue(prefix + 'mosaicoData').metadata}
- templatePath={getSandboxUrl(`public/mosaico/templates/${owner.getFormValue(prefix + 'mosaicoFsTemplate')}/index.html`)}
+ templatePath={getSandboxUrl(`static/mosaico/templates/${owner.getFormValue(prefix + 'mosaicoFsTemplate')}/index.html`)}
entityTypeId={entityTypeId}
title={t('Mosaico Template Designer')}
onFullscreenAsync={::owner.setElementInFullscreen}/>
@@ -285,6 +285,14 @@ export function getEditForm(owner, typeKey, prefix = '') {
Email address
+
+
+ [TO_NAME]
+ |
+
+ Recipient name as it appears in email's 'To' header
+ |
+
[SUBSCRIPTION_ID]
diff --git a/client/public/bootstrap/css/bootstrap-theme.css b/client/static/bootstrap/css/bootstrap-theme.css
similarity index 100%
rename from client/public/bootstrap/css/bootstrap-theme.css
rename to client/static/bootstrap/css/bootstrap-theme.css
diff --git a/client/public/bootstrap/css/bootstrap-theme.css.map b/client/static/bootstrap/css/bootstrap-theme.css.map
similarity index 100%
rename from client/public/bootstrap/css/bootstrap-theme.css.map
rename to client/static/bootstrap/css/bootstrap-theme.css.map
diff --git a/client/public/bootstrap/css/bootstrap-theme.min.css b/client/static/bootstrap/css/bootstrap-theme.min.css
similarity index 100%
rename from client/public/bootstrap/css/bootstrap-theme.min.css
rename to client/static/bootstrap/css/bootstrap-theme.min.css
diff --git a/client/public/bootstrap/css/bootstrap-theme.min.css.map b/client/static/bootstrap/css/bootstrap-theme.min.css.map
similarity index 100%
rename from client/public/bootstrap/css/bootstrap-theme.min.css.map
rename to client/static/bootstrap/css/bootstrap-theme.min.css.map
diff --git a/client/public/bootstrap/css/bootstrap.css b/client/static/bootstrap/css/bootstrap.css
similarity index 100%
rename from client/public/bootstrap/css/bootstrap.css
rename to client/static/bootstrap/css/bootstrap.css
diff --git a/client/public/bootstrap/css/bootstrap.css.map b/client/static/bootstrap/css/bootstrap.css.map
similarity index 100%
rename from client/public/bootstrap/css/bootstrap.css.map
rename to client/static/bootstrap/css/bootstrap.css.map
diff --git a/client/public/bootstrap/css/bootstrap.min.css b/client/static/bootstrap/css/bootstrap.min.css
similarity index 100%
rename from client/public/bootstrap/css/bootstrap.min.css
rename to client/static/bootstrap/css/bootstrap.min.css
diff --git a/client/public/bootstrap/css/bootstrap.min.css.map b/client/static/bootstrap/css/bootstrap.min.css.map
similarity index 100%
rename from client/public/bootstrap/css/bootstrap.min.css.map
rename to client/static/bootstrap/css/bootstrap.min.css.map
diff --git a/client/public/bootstrap/fonts/glyphicons-halflings-regular.eot b/client/static/bootstrap/fonts/glyphicons-halflings-regular.eot
similarity index 100%
rename from client/public/bootstrap/fonts/glyphicons-halflings-regular.eot
rename to client/static/bootstrap/fonts/glyphicons-halflings-regular.eot
diff --git a/client/public/bootstrap/fonts/glyphicons-halflings-regular.svg b/client/static/bootstrap/fonts/glyphicons-halflings-regular.svg
similarity index 100%
rename from client/public/bootstrap/fonts/glyphicons-halflings-regular.svg
rename to client/static/bootstrap/fonts/glyphicons-halflings-regular.svg
diff --git a/client/public/bootstrap/fonts/glyphicons-halflings-regular.ttf b/client/static/bootstrap/fonts/glyphicons-halflings-regular.ttf
similarity index 100%
rename from client/public/bootstrap/fonts/glyphicons-halflings-regular.ttf
rename to client/static/bootstrap/fonts/glyphicons-halflings-regular.ttf
diff --git a/client/public/bootstrap/fonts/glyphicons-halflings-regular.woff b/client/static/bootstrap/fonts/glyphicons-halflings-regular.woff
similarity index 100%
rename from client/public/bootstrap/fonts/glyphicons-halflings-regular.woff
rename to client/static/bootstrap/fonts/glyphicons-halflings-regular.woff
diff --git a/client/public/bootstrap/fonts/glyphicons-halflings-regular.woff2 b/client/static/bootstrap/fonts/glyphicons-halflings-regular.woff2
similarity index 100%
rename from client/public/bootstrap/fonts/glyphicons-halflings-regular.woff2
rename to client/static/bootstrap/fonts/glyphicons-halflings-regular.woff2
diff --git a/client/public/bootstrap/js/bootstrap.js b/client/static/bootstrap/js/bootstrap.js
similarity index 100%
rename from client/public/bootstrap/js/bootstrap.js
rename to client/static/bootstrap/js/bootstrap.js
diff --git a/client/public/bootstrap/js/bootstrap.min.js b/client/static/bootstrap/js/bootstrap.min.js
similarity index 100%
rename from client/public/bootstrap/js/bootstrap.min.js
rename to client/static/bootstrap/js/bootstrap.min.js
diff --git a/client/public/bootstrap/js/npm.js b/client/static/bootstrap/js/npm.js
similarity index 100%
rename from client/public/bootstrap/js/npm.js
rename to client/static/bootstrap/js/npm.js
diff --git a/client/public/bootstrap/themes/cosmo.min.css b/client/static/bootstrap/themes/cosmo.min.css
similarity index 100%
rename from client/public/bootstrap/themes/cosmo.min.css
rename to client/static/bootstrap/themes/cosmo.min.css
diff --git a/client/public/bootstrap/themes/flatly.min.css b/client/static/bootstrap/themes/flatly.min.css
similarity index 100%
rename from client/public/bootstrap/themes/flatly.min.css
rename to client/static/bootstrap/themes/flatly.min.css
diff --git a/client/public/bootstrap/themes/paper.min.css b/client/static/bootstrap/themes/paper.min.css
similarity index 100%
rename from client/public/bootstrap/themes/paper.min.css
rename to client/static/bootstrap/themes/paper.min.css
diff --git a/client/public/bootstrap/themes/simplex.min.css b/client/static/bootstrap/themes/simplex.min.css
similarity index 100%
rename from client/public/bootstrap/themes/simplex.min.css
rename to client/static/bootstrap/themes/simplex.min.css
diff --git a/client/public/bootstrap/themes/slate.min.css b/client/static/bootstrap/themes/slate.min.css
similarity index 100%
rename from client/public/bootstrap/themes/slate.min.css
rename to client/static/bootstrap/themes/slate.min.css
diff --git a/client/public/bootstrap/themes/superhero.min.css b/client/static/bootstrap/themes/superhero.min.css
similarity index 100%
rename from client/public/bootstrap/themes/superhero.min.css
rename to client/static/bootstrap/themes/superhero.min.css
diff --git a/client/public/bootstrap/themes/united.min.css b/client/static/bootstrap/themes/united.min.css
similarity index 100%
rename from client/public/bootstrap/themes/united.min.css
rename to client/static/bootstrap/themes/united.min.css
diff --git a/client/public/bootstrap/themes/yeti.min.css b/client/static/bootstrap/themes/yeti.min.css
similarity index 100%
rename from client/public/bootstrap/themes/yeti.min.css
rename to client/static/bootstrap/themes/yeti.min.css
diff --git a/client/public/css/footer.css b/client/static/css/footer.css
similarity index 100%
rename from client/public/css/footer.css
rename to client/static/css/footer.css
diff --git a/client/public/css/mailtrain.css b/client/static/css/mailtrain.css
similarity index 100%
rename from client/public/css/mailtrain.css
rename to client/static/css/mailtrain.css
diff --git a/client/public/css/narrow.css b/client/static/css/narrow.css
similarity index 100%
rename from client/public/css/narrow.css
rename to client/static/css/narrow.css
diff --git a/client/public/favicon.ico b/client/static/favicon.ico
similarity index 100%
rename from client/public/favicon.ico
rename to client/static/favicon.ico
diff --git a/client/public/jquery-2.2.1.min.js b/client/static/jquery-2.2.1.min.js
similarity index 100%
rename from client/public/jquery-2.2.1.min.js
rename to client/static/jquery-2.2.1.min.js
diff --git a/client/public/mailtrain-header.png b/client/static/mailtrain-header.png
similarity index 100%
rename from client/public/mailtrain-header.png
rename to client/static/mailtrain-header.png
diff --git a/client/public/mailtrain-notext.png b/client/static/mailtrain-notext.png
similarity index 100%
rename from client/public/mailtrain-notext.png
rename to client/static/mailtrain-notext.png
diff --git a/client/public/mailtrain.png b/client/static/mailtrain.png
similarity index 100%
rename from client/public/mailtrain.png
rename to client/static/mailtrain.png
diff --git a/client/public/mosaico/fa/fonts/fontawesome-webfont.eot b/client/static/mosaico/fa/fonts/fontawesome-webfont.eot
similarity index 100%
rename from client/public/mosaico/fa/fonts/fontawesome-webfont.eot
rename to client/static/mosaico/fa/fonts/fontawesome-webfont.eot
diff --git a/client/public/mosaico/fa/fonts/fontawesome-webfont.svg b/client/static/mosaico/fa/fonts/fontawesome-webfont.svg
similarity index 100%
rename from client/public/mosaico/fa/fonts/fontawesome-webfont.svg
rename to client/static/mosaico/fa/fonts/fontawesome-webfont.svg
diff --git a/client/public/mosaico/fa/fonts/fontawesome-webfont.ttf b/client/static/mosaico/fa/fonts/fontawesome-webfont.ttf
similarity index 100%
rename from client/public/mosaico/fa/fonts/fontawesome-webfont.ttf
rename to client/static/mosaico/fa/fonts/fontawesome-webfont.ttf
diff --git a/client/public/mosaico/fa/fonts/fontawesome-webfont.woff b/client/static/mosaico/fa/fonts/fontawesome-webfont.woff
similarity index 100%
rename from client/public/mosaico/fa/fonts/fontawesome-webfont.woff
rename to client/static/mosaico/fa/fonts/fontawesome-webfont.woff
diff --git a/client/public/mosaico/fa/fonts/fontawesome-webfont.woff2 b/client/static/mosaico/fa/fonts/fontawesome-webfont.woff2
similarity index 100%
rename from client/public/mosaico/fa/fonts/fontawesome-webfont.woff2
rename to client/static/mosaico/fa/fonts/fontawesome-webfont.woff2
diff --git a/client/public/mosaico/img/byvoxmail.png b/client/static/mosaico/img/byvoxmail.png
similarity index 100%
rename from client/public/mosaico/img/byvoxmail.png
rename to client/static/mosaico/img/byvoxmail.png
diff --git a/client/public/mosaico/img/mosaico-badge.gif b/client/static/mosaico/img/mosaico-badge.gif
similarity index 100%
rename from client/public/mosaico/img/mosaico-badge.gif
rename to client/static/mosaico/img/mosaico-badge.gif
diff --git a/client/public/mosaico/img/mosaico-v.gif b/client/static/mosaico/img/mosaico-v.gif
similarity index 100%
rename from client/public/mosaico/img/mosaico-v.gif
rename to client/static/mosaico/img/mosaico-v.gif
diff --git a/client/public/mosaico/img/mosaico32.png b/client/static/mosaico/img/mosaico32.png
similarity index 100%
rename from client/public/mosaico/img/mosaico32.png
rename to client/static/mosaico/img/mosaico32.png
diff --git a/client/public/mosaico/img/mosaicologo.png b/client/static/mosaico/img/mosaicologo.png
similarity index 100%
rename from client/public/mosaico/img/mosaicologo.png
rename to client/static/mosaico/img/mosaicologo.png
diff --git a/client/public/mosaico/img/screenshot-orig.png b/client/static/mosaico/img/screenshot-orig.png
similarity index 100%
rename from client/public/mosaico/img/screenshot-orig.png
rename to client/static/mosaico/img/screenshot-orig.png
diff --git a/client/public/mosaico/img/screenshot.png b/client/static/mosaico/img/screenshot.png
similarity index 100%
rename from client/public/mosaico/img/screenshot.png
rename to client/static/mosaico/img/screenshot.png
diff --git a/client/public/mosaico/lang/LICENSE b/client/static/mosaico/lang/LICENSE
similarity index 100%
rename from client/public/mosaico/lang/LICENSE
rename to client/static/mosaico/lang/LICENSE
diff --git a/client/public/mosaico/lang/README.md b/client/static/mosaico/lang/README.md
similarity index 100%
rename from client/public/mosaico/lang/README.md
rename to client/static/mosaico/lang/README.md
diff --git a/client/public/mosaico/lang/mosaico-de.json b/client/static/mosaico/lang/mosaico-de.json
similarity index 100%
rename from client/public/mosaico/lang/mosaico-de.json
rename to client/static/mosaico/lang/mosaico-de.json
diff --git a/client/public/mosaico/lang/mosaico-en.json b/client/static/mosaico/lang/mosaico-en.json
similarity index 100%
rename from client/public/mosaico/lang/mosaico-en.json
rename to client/static/mosaico/lang/mosaico-en.json
diff --git a/client/public/mosaico/lang/mosaico-es.json b/client/static/mosaico/lang/mosaico-es.json
similarity index 100%
rename from client/public/mosaico/lang/mosaico-es.json
rename to client/static/mosaico/lang/mosaico-es.json
diff --git a/client/public/mosaico/lang/mosaico-fr.json b/client/static/mosaico/lang/mosaico-fr.json
similarity index 100%
rename from client/public/mosaico/lang/mosaico-fr.json
rename to client/static/mosaico/lang/mosaico-fr.json
diff --git a/client/public/mosaico/lang/mosaico-it.json b/client/static/mosaico/lang/mosaico-it.json
similarity index 100%
rename from client/public/mosaico/lang/mosaico-it.json
rename to client/static/mosaico/lang/mosaico-it.json
diff --git a/client/public/mosaico/lang/mosaico-nl.json b/client/static/mosaico/lang/mosaico-nl.json
similarity index 100%
rename from client/public/mosaico/lang/mosaico-nl.json
rename to client/static/mosaico/lang/mosaico-nl.json
diff --git a/client/public/mosaico/lang/mosaico-ru.json b/client/static/mosaico/lang/mosaico-ru.json
similarity index 100%
rename from client/public/mosaico/lang/mosaico-ru.json
rename to client/static/mosaico/lang/mosaico-ru.json
diff --git a/client/public/mosaico/lang/mosaico-sr_RS.json b/client/static/mosaico/lang/mosaico-sr_RS.json
similarity index 100%
rename from client/public/mosaico/lang/mosaico-sr_RS.json
rename to client/static/mosaico/lang/mosaico-sr_RS.json
diff --git a/client/public/mosaico/lang/mosaico-sv.json b/client/static/mosaico/lang/mosaico-sv.json
similarity index 100%
rename from client/public/mosaico/lang/mosaico-sv.json
rename to client/static/mosaico/lang/mosaico-sv.json
diff --git a/client/public/mosaico/mosaico-material.min.css b/client/static/mosaico/mosaico-material.min.css
similarity index 100%
rename from client/public/mosaico/mosaico-material.min.css
rename to client/static/mosaico/mosaico-material.min.css
diff --git a/client/public/mosaico/mosaico-material.min.css.map b/client/static/mosaico/mosaico-material.min.css.map
similarity index 100%
rename from client/public/mosaico/mosaico-material.min.css.map
rename to client/static/mosaico/mosaico-material.min.css.map
diff --git a/client/public/mosaico/mosaico.min.css b/client/static/mosaico/mosaico.min.css
similarity index 100%
rename from client/public/mosaico/mosaico.min.css
rename to client/static/mosaico/mosaico.min.css
diff --git a/client/public/mosaico/mosaico.min.css.map b/client/static/mosaico/mosaico.min.css.map
similarity index 100%
rename from client/public/mosaico/mosaico.min.css.map
rename to client/static/mosaico/mosaico.min.css.map
diff --git a/client/public/mosaico/mosaico.min.js b/client/static/mosaico/mosaico.min.js
similarity index 100%
rename from client/public/mosaico/mosaico.min.js
rename to client/static/mosaico/mosaico.min.js
diff --git a/client/public/mosaico/mosaico.min.js.map b/client/static/mosaico/mosaico.min.js.map
similarity index 100%
rename from client/public/mosaico/mosaico.min.js.map
rename to client/static/mosaico/mosaico.min.js.map
diff --git a/client/public/mosaico/templates/versafix-1/edres/_full.png b/client/static/mosaico/templates/versafix-1/edres/_full.png
similarity index 100%
rename from client/public/mosaico/templates/versafix-1/edres/_full.png
rename to client/static/mosaico/templates/versafix-1/edres/_full.png
diff --git a/client/public/mosaico/templates/versafix-1/edres/buttonBlock.png b/client/static/mosaico/templates/versafix-1/edres/buttonBlock.png
similarity index 100%
rename from client/public/mosaico/templates/versafix-1/edres/buttonBlock.png
rename to client/static/mosaico/templates/versafix-1/edres/buttonBlock.png
diff --git a/client/public/mosaico/templates/versafix-1/edres/doubleArticleBlock.png b/client/static/mosaico/templates/versafix-1/edres/doubleArticleBlock.png
similarity index 100%
rename from client/public/mosaico/templates/versafix-1/edres/doubleArticleBlock.png
rename to client/static/mosaico/templates/versafix-1/edres/doubleArticleBlock.png
diff --git a/client/public/mosaico/templates/versafix-1/edres/doubleImageBlock.png b/client/static/mosaico/templates/versafix-1/edres/doubleImageBlock.png
similarity index 100%
rename from client/public/mosaico/templates/versafix-1/edres/doubleImageBlock.png
rename to client/static/mosaico/templates/versafix-1/edres/doubleImageBlock.png
diff --git a/client/public/mosaico/templates/versafix-1/edres/hrBlock.png b/client/static/mosaico/templates/versafix-1/edres/hrBlock.png
similarity index 100%
rename from client/public/mosaico/templates/versafix-1/edres/hrBlock.png
rename to client/static/mosaico/templates/versafix-1/edres/hrBlock.png
diff --git a/client/public/mosaico/templates/versafix-1/edres/imageBlock.png b/client/static/mosaico/templates/versafix-1/edres/imageBlock.png
similarity index 100%
rename from client/public/mosaico/templates/versafix-1/edres/imageBlock.png
rename to client/static/mosaico/templates/versafix-1/edres/imageBlock.png
diff --git a/client/public/mosaico/templates/versafix-1/edres/logoBlock.png b/client/static/mosaico/templates/versafix-1/edres/logoBlock.png
similarity index 100%
rename from client/public/mosaico/templates/versafix-1/edres/logoBlock.png
rename to client/static/mosaico/templates/versafix-1/edres/logoBlock.png
diff --git a/client/public/mosaico/templates/versafix-1/edres/sideArticleBlock.png b/client/static/mosaico/templates/versafix-1/edres/sideArticleBlock.png
similarity index 100%
rename from client/public/mosaico/templates/versafix-1/edres/sideArticleBlock.png
rename to client/static/mosaico/templates/versafix-1/edres/sideArticleBlock.png
diff --git a/client/public/mosaico/templates/versafix-1/edres/singleArticleBlock.png b/client/static/mosaico/templates/versafix-1/edres/singleArticleBlock.png
similarity index 100%
rename from client/public/mosaico/templates/versafix-1/edres/singleArticleBlock.png
rename to client/static/mosaico/templates/versafix-1/edres/singleArticleBlock.png
diff --git a/client/public/mosaico/templates/versafix-1/edres/socialBlock.png b/client/static/mosaico/templates/versafix-1/edres/socialBlock.png
similarity index 100%
rename from client/public/mosaico/templates/versafix-1/edres/socialBlock.png
rename to client/static/mosaico/templates/versafix-1/edres/socialBlock.png
diff --git a/client/public/mosaico/templates/versafix-1/edres/spacerBlock.png b/client/static/mosaico/templates/versafix-1/edres/spacerBlock.png
similarity index 100%
rename from client/public/mosaico/templates/versafix-1/edres/spacerBlock.png
rename to client/static/mosaico/templates/versafix-1/edres/spacerBlock.png
diff --git a/client/public/mosaico/templates/versafix-1/edres/textBlock.png b/client/static/mosaico/templates/versafix-1/edres/textBlock.png
similarity index 100%
rename from client/public/mosaico/templates/versafix-1/edres/textBlock.png
rename to client/static/mosaico/templates/versafix-1/edres/textBlock.png
diff --git a/client/public/mosaico/templates/versafix-1/edres/titleBlock.png b/client/static/mosaico/templates/versafix-1/edres/titleBlock.png
similarity index 100%
rename from client/public/mosaico/templates/versafix-1/edres/titleBlock.png
rename to client/static/mosaico/templates/versafix-1/edres/titleBlock.png
diff --git a/client/public/mosaico/templates/versafix-1/edres/tripleArticleBlock.png b/client/static/mosaico/templates/versafix-1/edres/tripleArticleBlock.png
similarity index 100%
rename from client/public/mosaico/templates/versafix-1/edres/tripleArticleBlock.png
rename to client/static/mosaico/templates/versafix-1/edres/tripleArticleBlock.png
diff --git a/client/public/mosaico/templates/versafix-1/edres/tripleImageBlock.png b/client/static/mosaico/templates/versafix-1/edres/tripleImageBlock.png
similarity index 100%
rename from client/public/mosaico/templates/versafix-1/edres/tripleImageBlock.png
rename to client/static/mosaico/templates/versafix-1/edres/tripleImageBlock.png
diff --git a/client/public/mosaico/templates/versafix-1/img/social_def/facebook_bw_ok.png b/client/static/mosaico/templates/versafix-1/img/social_def/facebook_bw_ok.png
similarity index 100%
rename from client/public/mosaico/templates/versafix-1/img/social_def/facebook_bw_ok.png
rename to client/static/mosaico/templates/versafix-1/img/social_def/facebook_bw_ok.png
diff --git a/client/public/mosaico/templates/versafix-1/img/social_def/facebook_ok.png b/client/static/mosaico/templates/versafix-1/img/social_def/facebook_ok.png
similarity index 100%
rename from client/public/mosaico/templates/versafix-1/img/social_def/facebook_ok.png
rename to client/static/mosaico/templates/versafix-1/img/social_def/facebook_ok.png
diff --git a/client/public/mosaico/templates/versafix-1/img/social_def/flickr_bw_ok.png b/client/static/mosaico/templates/versafix-1/img/social_def/flickr_bw_ok.png
similarity index 100%
rename from client/public/mosaico/templates/versafix-1/img/social_def/flickr_bw_ok.png
rename to client/static/mosaico/templates/versafix-1/img/social_def/flickr_bw_ok.png
diff --git a/client/public/mosaico/templates/versafix-1/img/social_def/flickr_ok.png b/client/static/mosaico/templates/versafix-1/img/social_def/flickr_ok.png
similarity index 100%
rename from client/public/mosaico/templates/versafix-1/img/social_def/flickr_ok.png
rename to client/static/mosaico/templates/versafix-1/img/social_def/flickr_ok.png
diff --git a/client/public/mosaico/templates/versafix-1/img/social_def/google+_bw_ok.png b/client/static/mosaico/templates/versafix-1/img/social_def/google+_bw_ok.png
similarity index 100%
rename from client/public/mosaico/templates/versafix-1/img/social_def/google+_bw_ok.png
rename to client/static/mosaico/templates/versafix-1/img/social_def/google+_bw_ok.png
diff --git a/client/public/mosaico/templates/versafix-1/img/social_def/google+_ok.png b/client/static/mosaico/templates/versafix-1/img/social_def/google+_ok.png
similarity index 100%
rename from client/public/mosaico/templates/versafix-1/img/social_def/google+_ok.png
rename to client/static/mosaico/templates/versafix-1/img/social_def/google+_ok.png
diff --git a/client/public/mosaico/templates/versafix-1/img/social_def/instagram_bw_ok.png b/client/static/mosaico/templates/versafix-1/img/social_def/instagram_bw_ok.png
similarity index 100%
rename from client/public/mosaico/templates/versafix-1/img/social_def/instagram_bw_ok.png
rename to client/static/mosaico/templates/versafix-1/img/social_def/instagram_bw_ok.png
diff --git a/client/public/mosaico/templates/versafix-1/img/social_def/instagram_ok.png b/client/static/mosaico/templates/versafix-1/img/social_def/instagram_ok.png
similarity index 100%
rename from client/public/mosaico/templates/versafix-1/img/social_def/instagram_ok.png
rename to client/static/mosaico/templates/versafix-1/img/social_def/instagram_ok.png
diff --git a/client/public/mosaico/templates/versafix-1/img/social_def/linkedin_bw_ok.png b/client/static/mosaico/templates/versafix-1/img/social_def/linkedin_bw_ok.png
similarity index 100%
rename from client/public/mosaico/templates/versafix-1/img/social_def/linkedin_bw_ok.png
rename to client/static/mosaico/templates/versafix-1/img/social_def/linkedin_bw_ok.png
diff --git a/client/public/mosaico/templates/versafix-1/img/social_def/linkedin_ok.png b/client/static/mosaico/templates/versafix-1/img/social_def/linkedin_ok.png
similarity index 100%
rename from client/public/mosaico/templates/versafix-1/img/social_def/linkedin_ok.png
rename to client/static/mosaico/templates/versafix-1/img/social_def/linkedin_ok.png
diff --git a/client/public/mosaico/templates/versafix-1/img/social_def/twitter_bw_ok.png b/client/static/mosaico/templates/versafix-1/img/social_def/twitter_bw_ok.png
similarity index 100%
rename from client/public/mosaico/templates/versafix-1/img/social_def/twitter_bw_ok.png
rename to client/static/mosaico/templates/versafix-1/img/social_def/twitter_bw_ok.png
diff --git a/client/public/mosaico/templates/versafix-1/img/social_def/twitter_ok.png b/client/static/mosaico/templates/versafix-1/img/social_def/twitter_ok.png
similarity index 100%
rename from client/public/mosaico/templates/versafix-1/img/social_def/twitter_ok.png
rename to client/static/mosaico/templates/versafix-1/img/social_def/twitter_ok.png
diff --git a/client/public/mosaico/templates/versafix-1/img/social_def/vimeo_bw_ok.png b/client/static/mosaico/templates/versafix-1/img/social_def/vimeo_bw_ok.png
similarity index 100%
rename from client/public/mosaico/templates/versafix-1/img/social_def/vimeo_bw_ok.png
rename to client/static/mosaico/templates/versafix-1/img/social_def/vimeo_bw_ok.png
diff --git a/client/public/mosaico/templates/versafix-1/img/social_def/vimeo_ok.png b/client/static/mosaico/templates/versafix-1/img/social_def/vimeo_ok.png
similarity index 100%
rename from client/public/mosaico/templates/versafix-1/img/social_def/vimeo_ok.png
rename to client/static/mosaico/templates/versafix-1/img/social_def/vimeo_ok.png
diff --git a/client/public/mosaico/templates/versafix-1/img/social_def/web_bw_ok.png b/client/static/mosaico/templates/versafix-1/img/social_def/web_bw_ok.png
similarity index 100%
rename from client/public/mosaico/templates/versafix-1/img/social_def/web_bw_ok.png
rename to client/static/mosaico/templates/versafix-1/img/social_def/web_bw_ok.png
diff --git a/client/public/mosaico/templates/versafix-1/img/social_def/web_ok.png b/client/static/mosaico/templates/versafix-1/img/social_def/web_ok.png
similarity index 100%
rename from client/public/mosaico/templates/versafix-1/img/social_def/web_ok.png
rename to client/static/mosaico/templates/versafix-1/img/social_def/web_ok.png
diff --git a/client/public/mosaico/templates/versafix-1/img/social_def/youtube_bw_ok.png b/client/static/mosaico/templates/versafix-1/img/social_def/youtube_bw_ok.png
similarity index 100%
rename from client/public/mosaico/templates/versafix-1/img/social_def/youtube_bw_ok.png
rename to client/static/mosaico/templates/versafix-1/img/social_def/youtube_bw_ok.png
diff --git a/client/public/mosaico/templates/versafix-1/img/social_def/youtube_ok.png b/client/static/mosaico/templates/versafix-1/img/social_def/youtube_ok.png
similarity index 100%
rename from client/public/mosaico/templates/versafix-1/img/social_def/youtube_ok.png
rename to client/static/mosaico/templates/versafix-1/img/social_def/youtube_ok.png
diff --git a/client/public/mosaico/templates/versafix-1/img/sponsor.gif b/client/static/mosaico/templates/versafix-1/img/sponsor.gif
similarity index 100%
rename from client/public/mosaico/templates/versafix-1/img/sponsor.gif
rename to client/static/mosaico/templates/versafix-1/img/sponsor.gif
diff --git a/client/public/mosaico/templates/versafix-1/index.html b/client/static/mosaico/templates/versafix-1/index.html
similarity index 100%
rename from client/public/mosaico/templates/versafix-1/index.html
rename to client/static/mosaico/templates/versafix-1/index.html
diff --git a/client/public/mosaico/uploads/.gitignore b/client/static/mosaico/uploads/.gitignore
similarity index 100%
rename from client/public/mosaico/uploads/.gitignore
rename to client/static/mosaico/uploads/.gitignore
diff --git a/client/public/mosaico/uploads/README.md b/client/static/mosaico/uploads/README.md
similarity index 76%
rename from client/public/mosaico/uploads/README.md
rename to client/static/mosaico/uploads/README.md
index b03f7887..97dd7a7d 100644
--- a/client/public/mosaico/uploads/README.md
+++ b/client/static/mosaico/uploads/README.md
@@ -1,2 +1,2 @@
This is a directory for images uploaded to Mosaico in Mailtrain ver. 1.
-Move the content from /public/mosaico/uploads to here (/client/public/mosaico/uploads).
+Move the content from /public/mosaico/uploads to here (/client/static/mosaico/uploads).
diff --git a/client/public/mosaico/vendor/canvas-to-blob.min.js b/client/static/mosaico/vendor/canvas-to-blob.min.js
similarity index 100%
rename from client/public/mosaico/vendor/canvas-to-blob.min.js
rename to client/static/mosaico/vendor/canvas-to-blob.min.js
diff --git a/client/public/mosaico/vendor/jquery-migrate.min.js b/client/static/mosaico/vendor/jquery-migrate.min.js
similarity index 100%
rename from client/public/mosaico/vendor/jquery-migrate.min.js
rename to client/static/mosaico/vendor/jquery-migrate.min.js
diff --git a/client/public/mosaico/vendor/jquery-ui.min.css b/client/static/mosaico/vendor/jquery-ui.min.css
similarity index 100%
rename from client/public/mosaico/vendor/jquery-ui.min.css
rename to client/static/mosaico/vendor/jquery-ui.min.css
diff --git a/client/public/mosaico/vendor/jquery-ui.min.js b/client/static/mosaico/vendor/jquery-ui.min.js
similarity index 100%
rename from client/public/mosaico/vendor/jquery-ui.min.js
rename to client/static/mosaico/vendor/jquery-ui.min.js
diff --git a/client/public/mosaico/vendor/jquery.fileupload-image.js b/client/static/mosaico/vendor/jquery.fileupload-image.js
similarity index 100%
rename from client/public/mosaico/vendor/jquery.fileupload-image.js
rename to client/static/mosaico/vendor/jquery.fileupload-image.js
diff --git a/client/public/mosaico/vendor/jquery.fileupload-process.js b/client/static/mosaico/vendor/jquery.fileupload-process.js
similarity index 100%
rename from client/public/mosaico/vendor/jquery.fileupload-process.js
rename to client/static/mosaico/vendor/jquery.fileupload-process.js
diff --git a/client/public/mosaico/vendor/jquery.fileupload-validate.js b/client/static/mosaico/vendor/jquery.fileupload-validate.js
similarity index 100%
rename from client/public/mosaico/vendor/jquery.fileupload-validate.js
rename to client/static/mosaico/vendor/jquery.fileupload-validate.js
diff --git a/client/public/mosaico/vendor/jquery.fileupload.js b/client/static/mosaico/vendor/jquery.fileupload.js
similarity index 100%
rename from client/public/mosaico/vendor/jquery.fileupload.js
rename to client/static/mosaico/vendor/jquery.fileupload.js
diff --git a/client/public/mosaico/vendor/jquery.iframe-transport.js b/client/static/mosaico/vendor/jquery.iframe-transport.js
similarity index 100%
rename from client/public/mosaico/vendor/jquery.iframe-transport.js
rename to client/static/mosaico/vendor/jquery.iframe-transport.js
diff --git a/client/public/mosaico/vendor/jquery.min.js b/client/static/mosaico/vendor/jquery.min.js
similarity index 100%
rename from client/public/mosaico/vendor/jquery.min.js
rename to client/static/mosaico/vendor/jquery.min.js
diff --git a/client/public/mosaico/vendor/jquery.ui.touch-punch.min.js b/client/static/mosaico/vendor/jquery.ui.touch-punch.min.js
similarity index 100%
rename from client/public/mosaico/vendor/jquery.ui.touch-punch.min.js
rename to client/static/mosaico/vendor/jquery.ui.touch-punch.min.js
diff --git a/client/public/mosaico/vendor/knockout-jqueryui.min.js b/client/static/mosaico/vendor/knockout-jqueryui.min.js
similarity index 100%
rename from client/public/mosaico/vendor/knockout-jqueryui.min.js
rename to client/static/mosaico/vendor/knockout-jqueryui.min.js
diff --git a/client/public/mosaico/vendor/knockout.js b/client/static/mosaico/vendor/knockout.js
similarity index 100%
rename from client/public/mosaico/vendor/knockout.js
rename to client/static/mosaico/vendor/knockout.js
diff --git a/client/public/mosaico/vendor/load-image.all.min.js b/client/static/mosaico/vendor/load-image.all.min.js
similarity index 100%
rename from client/public/mosaico/vendor/load-image.all.min.js
rename to client/static/mosaico/vendor/load-image.all.min.js
diff --git a/client/public/mosaico/vendor/notoregular/noto-sans-400-normal.eot b/client/static/mosaico/vendor/notoregular/noto-sans-400-normal.eot
similarity index 100%
rename from client/public/mosaico/vendor/notoregular/noto-sans-400-normal.eot
rename to client/static/mosaico/vendor/notoregular/noto-sans-400-normal.eot
diff --git a/client/public/mosaico/vendor/notoregular/noto-sans-400-normal.ttf b/client/static/mosaico/vendor/notoregular/noto-sans-400-normal.ttf
similarity index 100%
rename from client/public/mosaico/vendor/notoregular/noto-sans-400-normal.ttf
rename to client/static/mosaico/vendor/notoregular/noto-sans-400-normal.ttf
diff --git a/client/public/mosaico/vendor/notoregular/noto-sans-400-normal.woff b/client/static/mosaico/vendor/notoregular/noto-sans-400-normal.woff
similarity index 100%
rename from client/public/mosaico/vendor/notoregular/noto-sans-400-normal.woff
rename to client/static/mosaico/vendor/notoregular/noto-sans-400-normal.woff
diff --git a/client/public/mosaico/vendor/notoregular/stylesheet.css b/client/static/mosaico/vendor/notoregular/stylesheet.css
similarity index 100%
rename from client/public/mosaico/vendor/notoregular/stylesheet.css
rename to client/static/mosaico/vendor/notoregular/stylesheet.css
diff --git a/client/public/mosaico/vendor/plugins/advlist/index.js b/client/static/mosaico/vendor/plugins/advlist/index.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/advlist/index.js
rename to client/static/mosaico/vendor/plugins/advlist/index.js
diff --git a/client/public/mosaico/vendor/plugins/advlist/plugin.js b/client/static/mosaico/vendor/plugins/advlist/plugin.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/advlist/plugin.js
rename to client/static/mosaico/vendor/plugins/advlist/plugin.js
diff --git a/client/public/mosaico/vendor/plugins/advlist/plugin.min.js b/client/static/mosaico/vendor/plugins/advlist/plugin.min.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/advlist/plugin.min.js
rename to client/static/mosaico/vendor/plugins/advlist/plugin.min.js
diff --git a/client/public/mosaico/vendor/plugins/anchor/index.js b/client/static/mosaico/vendor/plugins/anchor/index.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/anchor/index.js
rename to client/static/mosaico/vendor/plugins/anchor/index.js
diff --git a/client/public/mosaico/vendor/plugins/anchor/plugin.js b/client/static/mosaico/vendor/plugins/anchor/plugin.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/anchor/plugin.js
rename to client/static/mosaico/vendor/plugins/anchor/plugin.js
diff --git a/client/public/mosaico/vendor/plugins/anchor/plugin.min.js b/client/static/mosaico/vendor/plugins/anchor/plugin.min.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/anchor/plugin.min.js
rename to client/static/mosaico/vendor/plugins/anchor/plugin.min.js
diff --git a/client/public/mosaico/vendor/plugins/autolink/index.js b/client/static/mosaico/vendor/plugins/autolink/index.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/autolink/index.js
rename to client/static/mosaico/vendor/plugins/autolink/index.js
diff --git a/client/public/mosaico/vendor/plugins/autolink/plugin.js b/client/static/mosaico/vendor/plugins/autolink/plugin.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/autolink/plugin.js
rename to client/static/mosaico/vendor/plugins/autolink/plugin.js
diff --git a/client/public/mosaico/vendor/plugins/autolink/plugin.min.js b/client/static/mosaico/vendor/plugins/autolink/plugin.min.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/autolink/plugin.min.js
rename to client/static/mosaico/vendor/plugins/autolink/plugin.min.js
diff --git a/client/public/mosaico/vendor/plugins/autoresize/index.js b/client/static/mosaico/vendor/plugins/autoresize/index.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/autoresize/index.js
rename to client/static/mosaico/vendor/plugins/autoresize/index.js
diff --git a/client/public/mosaico/vendor/plugins/autoresize/plugin.js b/client/static/mosaico/vendor/plugins/autoresize/plugin.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/autoresize/plugin.js
rename to client/static/mosaico/vendor/plugins/autoresize/plugin.js
diff --git a/client/public/mosaico/vendor/plugins/autoresize/plugin.min.js b/client/static/mosaico/vendor/plugins/autoresize/plugin.min.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/autoresize/plugin.min.js
rename to client/static/mosaico/vendor/plugins/autoresize/plugin.min.js
diff --git a/client/public/mosaico/vendor/plugins/autosave/index.js b/client/static/mosaico/vendor/plugins/autosave/index.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/autosave/index.js
rename to client/static/mosaico/vendor/plugins/autosave/index.js
diff --git a/client/public/mosaico/vendor/plugins/autosave/plugin.js b/client/static/mosaico/vendor/plugins/autosave/plugin.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/autosave/plugin.js
rename to client/static/mosaico/vendor/plugins/autosave/plugin.js
diff --git a/client/public/mosaico/vendor/plugins/autosave/plugin.min.js b/client/static/mosaico/vendor/plugins/autosave/plugin.min.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/autosave/plugin.min.js
rename to client/static/mosaico/vendor/plugins/autosave/plugin.min.js
diff --git a/client/public/mosaico/vendor/plugins/bbcode/index.js b/client/static/mosaico/vendor/plugins/bbcode/index.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/bbcode/index.js
rename to client/static/mosaico/vendor/plugins/bbcode/index.js
diff --git a/client/public/mosaico/vendor/plugins/bbcode/plugin.js b/client/static/mosaico/vendor/plugins/bbcode/plugin.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/bbcode/plugin.js
rename to client/static/mosaico/vendor/plugins/bbcode/plugin.js
diff --git a/client/public/mosaico/vendor/plugins/bbcode/plugin.min.js b/client/static/mosaico/vendor/plugins/bbcode/plugin.min.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/bbcode/plugin.min.js
rename to client/static/mosaico/vendor/plugins/bbcode/plugin.min.js
diff --git a/client/public/mosaico/vendor/plugins/charmap/index.js b/client/static/mosaico/vendor/plugins/charmap/index.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/charmap/index.js
rename to client/static/mosaico/vendor/plugins/charmap/index.js
diff --git a/client/public/mosaico/vendor/plugins/charmap/plugin.js b/client/static/mosaico/vendor/plugins/charmap/plugin.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/charmap/plugin.js
rename to client/static/mosaico/vendor/plugins/charmap/plugin.js
diff --git a/client/public/mosaico/vendor/plugins/charmap/plugin.min.js b/client/static/mosaico/vendor/plugins/charmap/plugin.min.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/charmap/plugin.min.js
rename to client/static/mosaico/vendor/plugins/charmap/plugin.min.js
diff --git a/client/public/mosaico/vendor/plugins/code/index.js b/client/static/mosaico/vendor/plugins/code/index.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/code/index.js
rename to client/static/mosaico/vendor/plugins/code/index.js
diff --git a/client/public/mosaico/vendor/plugins/code/plugin.js b/client/static/mosaico/vendor/plugins/code/plugin.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/code/plugin.js
rename to client/static/mosaico/vendor/plugins/code/plugin.js
diff --git a/client/public/mosaico/vendor/plugins/code/plugin.min.js b/client/static/mosaico/vendor/plugins/code/plugin.min.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/code/plugin.min.js
rename to client/static/mosaico/vendor/plugins/code/plugin.min.js
diff --git a/client/public/mosaico/vendor/plugins/codesample/css/prism.css b/client/static/mosaico/vendor/plugins/codesample/css/prism.css
similarity index 100%
rename from client/public/mosaico/vendor/plugins/codesample/css/prism.css
rename to client/static/mosaico/vendor/plugins/codesample/css/prism.css
diff --git a/client/public/mosaico/vendor/plugins/codesample/index.js b/client/static/mosaico/vendor/plugins/codesample/index.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/codesample/index.js
rename to client/static/mosaico/vendor/plugins/codesample/index.js
diff --git a/client/public/mosaico/vendor/plugins/codesample/plugin.js b/client/static/mosaico/vendor/plugins/codesample/plugin.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/codesample/plugin.js
rename to client/static/mosaico/vendor/plugins/codesample/plugin.js
diff --git a/client/public/mosaico/vendor/plugins/codesample/plugin.min.js b/client/static/mosaico/vendor/plugins/codesample/plugin.min.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/codesample/plugin.min.js
rename to client/static/mosaico/vendor/plugins/codesample/plugin.min.js
diff --git a/client/public/mosaico/vendor/plugins/colorpicker/index.js b/client/static/mosaico/vendor/plugins/colorpicker/index.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/colorpicker/index.js
rename to client/static/mosaico/vendor/plugins/colorpicker/index.js
diff --git a/client/public/mosaico/vendor/plugins/colorpicker/plugin.js b/client/static/mosaico/vendor/plugins/colorpicker/plugin.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/colorpicker/plugin.js
rename to client/static/mosaico/vendor/plugins/colorpicker/plugin.js
diff --git a/client/public/mosaico/vendor/plugins/colorpicker/plugin.min.js b/client/static/mosaico/vendor/plugins/colorpicker/plugin.min.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/colorpicker/plugin.min.js
rename to client/static/mosaico/vendor/plugins/colorpicker/plugin.min.js
diff --git a/client/public/mosaico/vendor/plugins/contextmenu/index.js b/client/static/mosaico/vendor/plugins/contextmenu/index.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/contextmenu/index.js
rename to client/static/mosaico/vendor/plugins/contextmenu/index.js
diff --git a/client/public/mosaico/vendor/plugins/contextmenu/plugin.js b/client/static/mosaico/vendor/plugins/contextmenu/plugin.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/contextmenu/plugin.js
rename to client/static/mosaico/vendor/plugins/contextmenu/plugin.js
diff --git a/client/public/mosaico/vendor/plugins/contextmenu/plugin.min.js b/client/static/mosaico/vendor/plugins/contextmenu/plugin.min.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/contextmenu/plugin.min.js
rename to client/static/mosaico/vendor/plugins/contextmenu/plugin.min.js
diff --git a/client/public/mosaico/vendor/plugins/directionality/index.js b/client/static/mosaico/vendor/plugins/directionality/index.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/directionality/index.js
rename to client/static/mosaico/vendor/plugins/directionality/index.js
diff --git a/client/public/mosaico/vendor/plugins/directionality/plugin.js b/client/static/mosaico/vendor/plugins/directionality/plugin.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/directionality/plugin.js
rename to client/static/mosaico/vendor/plugins/directionality/plugin.js
diff --git a/client/public/mosaico/vendor/plugins/directionality/plugin.min.js b/client/static/mosaico/vendor/plugins/directionality/plugin.min.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/directionality/plugin.min.js
rename to client/static/mosaico/vendor/plugins/directionality/plugin.min.js
diff --git a/client/public/mosaico/vendor/plugins/emoticons/img/smiley-cool.gif b/client/static/mosaico/vendor/plugins/emoticons/img/smiley-cool.gif
similarity index 100%
rename from client/public/mosaico/vendor/plugins/emoticons/img/smiley-cool.gif
rename to client/static/mosaico/vendor/plugins/emoticons/img/smiley-cool.gif
diff --git a/client/public/mosaico/vendor/plugins/emoticons/img/smiley-cry.gif b/client/static/mosaico/vendor/plugins/emoticons/img/smiley-cry.gif
similarity index 100%
rename from client/public/mosaico/vendor/plugins/emoticons/img/smiley-cry.gif
rename to client/static/mosaico/vendor/plugins/emoticons/img/smiley-cry.gif
diff --git a/client/public/mosaico/vendor/plugins/emoticons/img/smiley-embarassed.gif b/client/static/mosaico/vendor/plugins/emoticons/img/smiley-embarassed.gif
similarity index 100%
rename from client/public/mosaico/vendor/plugins/emoticons/img/smiley-embarassed.gif
rename to client/static/mosaico/vendor/plugins/emoticons/img/smiley-embarassed.gif
diff --git a/client/public/mosaico/vendor/plugins/emoticons/img/smiley-foot-in-mouth.gif b/client/static/mosaico/vendor/plugins/emoticons/img/smiley-foot-in-mouth.gif
similarity index 100%
rename from client/public/mosaico/vendor/plugins/emoticons/img/smiley-foot-in-mouth.gif
rename to client/static/mosaico/vendor/plugins/emoticons/img/smiley-foot-in-mouth.gif
diff --git a/client/public/mosaico/vendor/plugins/emoticons/img/smiley-frown.gif b/client/static/mosaico/vendor/plugins/emoticons/img/smiley-frown.gif
similarity index 100%
rename from client/public/mosaico/vendor/plugins/emoticons/img/smiley-frown.gif
rename to client/static/mosaico/vendor/plugins/emoticons/img/smiley-frown.gif
diff --git a/client/public/mosaico/vendor/plugins/emoticons/img/smiley-innocent.gif b/client/static/mosaico/vendor/plugins/emoticons/img/smiley-innocent.gif
similarity index 100%
rename from client/public/mosaico/vendor/plugins/emoticons/img/smiley-innocent.gif
rename to client/static/mosaico/vendor/plugins/emoticons/img/smiley-innocent.gif
diff --git a/client/public/mosaico/vendor/plugins/emoticons/img/smiley-kiss.gif b/client/static/mosaico/vendor/plugins/emoticons/img/smiley-kiss.gif
similarity index 100%
rename from client/public/mosaico/vendor/plugins/emoticons/img/smiley-kiss.gif
rename to client/static/mosaico/vendor/plugins/emoticons/img/smiley-kiss.gif
diff --git a/client/public/mosaico/vendor/plugins/emoticons/img/smiley-laughing.gif b/client/static/mosaico/vendor/plugins/emoticons/img/smiley-laughing.gif
similarity index 100%
rename from client/public/mosaico/vendor/plugins/emoticons/img/smiley-laughing.gif
rename to client/static/mosaico/vendor/plugins/emoticons/img/smiley-laughing.gif
diff --git a/client/public/mosaico/vendor/plugins/emoticons/img/smiley-money-mouth.gif b/client/static/mosaico/vendor/plugins/emoticons/img/smiley-money-mouth.gif
similarity index 100%
rename from client/public/mosaico/vendor/plugins/emoticons/img/smiley-money-mouth.gif
rename to client/static/mosaico/vendor/plugins/emoticons/img/smiley-money-mouth.gif
diff --git a/client/public/mosaico/vendor/plugins/emoticons/img/smiley-sealed.gif b/client/static/mosaico/vendor/plugins/emoticons/img/smiley-sealed.gif
similarity index 100%
rename from client/public/mosaico/vendor/plugins/emoticons/img/smiley-sealed.gif
rename to client/static/mosaico/vendor/plugins/emoticons/img/smiley-sealed.gif
diff --git a/client/public/mosaico/vendor/plugins/emoticons/img/smiley-smile.gif b/client/static/mosaico/vendor/plugins/emoticons/img/smiley-smile.gif
similarity index 100%
rename from client/public/mosaico/vendor/plugins/emoticons/img/smiley-smile.gif
rename to client/static/mosaico/vendor/plugins/emoticons/img/smiley-smile.gif
diff --git a/client/public/mosaico/vendor/plugins/emoticons/img/smiley-surprised.gif b/client/static/mosaico/vendor/plugins/emoticons/img/smiley-surprised.gif
similarity index 100%
rename from client/public/mosaico/vendor/plugins/emoticons/img/smiley-surprised.gif
rename to client/static/mosaico/vendor/plugins/emoticons/img/smiley-surprised.gif
diff --git a/client/public/mosaico/vendor/plugins/emoticons/img/smiley-tongue-out.gif b/client/static/mosaico/vendor/plugins/emoticons/img/smiley-tongue-out.gif
similarity index 100%
rename from client/public/mosaico/vendor/plugins/emoticons/img/smiley-tongue-out.gif
rename to client/static/mosaico/vendor/plugins/emoticons/img/smiley-tongue-out.gif
diff --git a/client/public/mosaico/vendor/plugins/emoticons/img/smiley-undecided.gif b/client/static/mosaico/vendor/plugins/emoticons/img/smiley-undecided.gif
similarity index 100%
rename from client/public/mosaico/vendor/plugins/emoticons/img/smiley-undecided.gif
rename to client/static/mosaico/vendor/plugins/emoticons/img/smiley-undecided.gif
diff --git a/client/public/mosaico/vendor/plugins/emoticons/img/smiley-wink.gif b/client/static/mosaico/vendor/plugins/emoticons/img/smiley-wink.gif
similarity index 100%
rename from client/public/mosaico/vendor/plugins/emoticons/img/smiley-wink.gif
rename to client/static/mosaico/vendor/plugins/emoticons/img/smiley-wink.gif
diff --git a/client/public/mosaico/vendor/plugins/emoticons/img/smiley-yell.gif b/client/static/mosaico/vendor/plugins/emoticons/img/smiley-yell.gif
similarity index 100%
rename from client/public/mosaico/vendor/plugins/emoticons/img/smiley-yell.gif
rename to client/static/mosaico/vendor/plugins/emoticons/img/smiley-yell.gif
diff --git a/client/public/mosaico/vendor/plugins/emoticons/index.js b/client/static/mosaico/vendor/plugins/emoticons/index.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/emoticons/index.js
rename to client/static/mosaico/vendor/plugins/emoticons/index.js
diff --git a/client/public/mosaico/vendor/plugins/emoticons/plugin.js b/client/static/mosaico/vendor/plugins/emoticons/plugin.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/emoticons/plugin.js
rename to client/static/mosaico/vendor/plugins/emoticons/plugin.js
diff --git a/client/public/mosaico/vendor/plugins/emoticons/plugin.min.js b/client/static/mosaico/vendor/plugins/emoticons/plugin.min.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/emoticons/plugin.min.js
rename to client/static/mosaico/vendor/plugins/emoticons/plugin.min.js
diff --git a/client/public/mosaico/vendor/plugins/fullpage/index.js b/client/static/mosaico/vendor/plugins/fullpage/index.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/fullpage/index.js
rename to client/static/mosaico/vendor/plugins/fullpage/index.js
diff --git a/client/public/mosaico/vendor/plugins/fullpage/plugin.js b/client/static/mosaico/vendor/plugins/fullpage/plugin.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/fullpage/plugin.js
rename to client/static/mosaico/vendor/plugins/fullpage/plugin.js
diff --git a/client/public/mosaico/vendor/plugins/fullpage/plugin.min.js b/client/static/mosaico/vendor/plugins/fullpage/plugin.min.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/fullpage/plugin.min.js
rename to client/static/mosaico/vendor/plugins/fullpage/plugin.min.js
diff --git a/client/public/mosaico/vendor/plugins/fullscreen/index.js b/client/static/mosaico/vendor/plugins/fullscreen/index.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/fullscreen/index.js
rename to client/static/mosaico/vendor/plugins/fullscreen/index.js
diff --git a/client/public/mosaico/vendor/plugins/fullscreen/plugin.js b/client/static/mosaico/vendor/plugins/fullscreen/plugin.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/fullscreen/plugin.js
rename to client/static/mosaico/vendor/plugins/fullscreen/plugin.js
diff --git a/client/public/mosaico/vendor/plugins/fullscreen/plugin.min.js b/client/static/mosaico/vendor/plugins/fullscreen/plugin.min.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/fullscreen/plugin.min.js
rename to client/static/mosaico/vendor/plugins/fullscreen/plugin.min.js
diff --git a/client/public/mosaico/vendor/plugins/help/img/logo.png b/client/static/mosaico/vendor/plugins/help/img/logo.png
similarity index 100%
rename from client/public/mosaico/vendor/plugins/help/img/logo.png
rename to client/static/mosaico/vendor/plugins/help/img/logo.png
diff --git a/client/public/mosaico/vendor/plugins/help/index.js b/client/static/mosaico/vendor/plugins/help/index.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/help/index.js
rename to client/static/mosaico/vendor/plugins/help/index.js
diff --git a/client/public/mosaico/vendor/plugins/help/plugin.js b/client/static/mosaico/vendor/plugins/help/plugin.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/help/plugin.js
rename to client/static/mosaico/vendor/plugins/help/plugin.js
diff --git a/client/public/mosaico/vendor/plugins/help/plugin.min.js b/client/static/mosaico/vendor/plugins/help/plugin.min.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/help/plugin.min.js
rename to client/static/mosaico/vendor/plugins/help/plugin.min.js
diff --git a/client/public/mosaico/vendor/plugins/hr/index.js b/client/static/mosaico/vendor/plugins/hr/index.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/hr/index.js
rename to client/static/mosaico/vendor/plugins/hr/index.js
diff --git a/client/public/mosaico/vendor/plugins/hr/plugin.js b/client/static/mosaico/vendor/plugins/hr/plugin.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/hr/plugin.js
rename to client/static/mosaico/vendor/plugins/hr/plugin.js
diff --git a/client/public/mosaico/vendor/plugins/hr/plugin.min.js b/client/static/mosaico/vendor/plugins/hr/plugin.min.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/hr/plugin.min.js
rename to client/static/mosaico/vendor/plugins/hr/plugin.min.js
diff --git a/client/public/mosaico/vendor/plugins/image/index.js b/client/static/mosaico/vendor/plugins/image/index.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/image/index.js
rename to client/static/mosaico/vendor/plugins/image/index.js
diff --git a/client/public/mosaico/vendor/plugins/image/plugin.js b/client/static/mosaico/vendor/plugins/image/plugin.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/image/plugin.js
rename to client/static/mosaico/vendor/plugins/image/plugin.js
diff --git a/client/public/mosaico/vendor/plugins/image/plugin.min.js b/client/static/mosaico/vendor/plugins/image/plugin.min.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/image/plugin.min.js
rename to client/static/mosaico/vendor/plugins/image/plugin.min.js
diff --git a/client/public/mosaico/vendor/plugins/imagetools/index.js b/client/static/mosaico/vendor/plugins/imagetools/index.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/imagetools/index.js
rename to client/static/mosaico/vendor/plugins/imagetools/index.js
diff --git a/client/public/mosaico/vendor/plugins/imagetools/plugin.js b/client/static/mosaico/vendor/plugins/imagetools/plugin.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/imagetools/plugin.js
rename to client/static/mosaico/vendor/plugins/imagetools/plugin.js
diff --git a/client/public/mosaico/vendor/plugins/imagetools/plugin.min.js b/client/static/mosaico/vendor/plugins/imagetools/plugin.min.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/imagetools/plugin.min.js
rename to client/static/mosaico/vendor/plugins/imagetools/plugin.min.js
diff --git a/client/public/mosaico/vendor/plugins/importcss/index.js b/client/static/mosaico/vendor/plugins/importcss/index.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/importcss/index.js
rename to client/static/mosaico/vendor/plugins/importcss/index.js
diff --git a/client/public/mosaico/vendor/plugins/importcss/plugin.js b/client/static/mosaico/vendor/plugins/importcss/plugin.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/importcss/plugin.js
rename to client/static/mosaico/vendor/plugins/importcss/plugin.js
diff --git a/client/public/mosaico/vendor/plugins/importcss/plugin.min.js b/client/static/mosaico/vendor/plugins/importcss/plugin.min.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/importcss/plugin.min.js
rename to client/static/mosaico/vendor/plugins/importcss/plugin.min.js
diff --git a/client/public/mosaico/vendor/plugins/insertdatetime/index.js b/client/static/mosaico/vendor/plugins/insertdatetime/index.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/insertdatetime/index.js
rename to client/static/mosaico/vendor/plugins/insertdatetime/index.js
diff --git a/client/public/mosaico/vendor/plugins/insertdatetime/plugin.js b/client/static/mosaico/vendor/plugins/insertdatetime/plugin.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/insertdatetime/plugin.js
rename to client/static/mosaico/vendor/plugins/insertdatetime/plugin.js
diff --git a/client/public/mosaico/vendor/plugins/insertdatetime/plugin.min.js b/client/static/mosaico/vendor/plugins/insertdatetime/plugin.min.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/insertdatetime/plugin.min.js
rename to client/static/mosaico/vendor/plugins/insertdatetime/plugin.min.js
diff --git a/client/public/mosaico/vendor/plugins/legacyoutput/index.js b/client/static/mosaico/vendor/plugins/legacyoutput/index.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/legacyoutput/index.js
rename to client/static/mosaico/vendor/plugins/legacyoutput/index.js
diff --git a/client/public/mosaico/vendor/plugins/legacyoutput/plugin.js b/client/static/mosaico/vendor/plugins/legacyoutput/plugin.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/legacyoutput/plugin.js
rename to client/static/mosaico/vendor/plugins/legacyoutput/plugin.js
diff --git a/client/public/mosaico/vendor/plugins/legacyoutput/plugin.min.js b/client/static/mosaico/vendor/plugins/legacyoutput/plugin.min.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/legacyoutput/plugin.min.js
rename to client/static/mosaico/vendor/plugins/legacyoutput/plugin.min.js
diff --git a/client/public/mosaico/vendor/plugins/link/index.js b/client/static/mosaico/vendor/plugins/link/index.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/link/index.js
rename to client/static/mosaico/vendor/plugins/link/index.js
diff --git a/client/public/mosaico/vendor/plugins/link/plugin.js b/client/static/mosaico/vendor/plugins/link/plugin.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/link/plugin.js
rename to client/static/mosaico/vendor/plugins/link/plugin.js
diff --git a/client/public/mosaico/vendor/plugins/link/plugin.min.js b/client/static/mosaico/vendor/plugins/link/plugin.min.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/link/plugin.min.js
rename to client/static/mosaico/vendor/plugins/link/plugin.min.js
diff --git a/client/public/mosaico/vendor/plugins/lists/index.js b/client/static/mosaico/vendor/plugins/lists/index.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/lists/index.js
rename to client/static/mosaico/vendor/plugins/lists/index.js
diff --git a/client/public/mosaico/vendor/plugins/lists/plugin.js b/client/static/mosaico/vendor/plugins/lists/plugin.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/lists/plugin.js
rename to client/static/mosaico/vendor/plugins/lists/plugin.js
diff --git a/client/public/mosaico/vendor/plugins/lists/plugin.min.js b/client/static/mosaico/vendor/plugins/lists/plugin.min.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/lists/plugin.min.js
rename to client/static/mosaico/vendor/plugins/lists/plugin.min.js
diff --git a/client/public/mosaico/vendor/plugins/media/index.js b/client/static/mosaico/vendor/plugins/media/index.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/media/index.js
rename to client/static/mosaico/vendor/plugins/media/index.js
diff --git a/client/public/mosaico/vendor/plugins/media/plugin.js b/client/static/mosaico/vendor/plugins/media/plugin.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/media/plugin.js
rename to client/static/mosaico/vendor/plugins/media/plugin.js
diff --git a/client/public/mosaico/vendor/plugins/media/plugin.min.js b/client/static/mosaico/vendor/plugins/media/plugin.min.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/media/plugin.min.js
rename to client/static/mosaico/vendor/plugins/media/plugin.min.js
diff --git a/client/public/mosaico/vendor/plugins/nonbreaking/index.js b/client/static/mosaico/vendor/plugins/nonbreaking/index.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/nonbreaking/index.js
rename to client/static/mosaico/vendor/plugins/nonbreaking/index.js
diff --git a/client/public/mosaico/vendor/plugins/nonbreaking/plugin.js b/client/static/mosaico/vendor/plugins/nonbreaking/plugin.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/nonbreaking/plugin.js
rename to client/static/mosaico/vendor/plugins/nonbreaking/plugin.js
diff --git a/client/public/mosaico/vendor/plugins/nonbreaking/plugin.min.js b/client/static/mosaico/vendor/plugins/nonbreaking/plugin.min.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/nonbreaking/plugin.min.js
rename to client/static/mosaico/vendor/plugins/nonbreaking/plugin.min.js
diff --git a/client/public/mosaico/vendor/plugins/noneditable/index.js b/client/static/mosaico/vendor/plugins/noneditable/index.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/noneditable/index.js
rename to client/static/mosaico/vendor/plugins/noneditable/index.js
diff --git a/client/public/mosaico/vendor/plugins/noneditable/plugin.js b/client/static/mosaico/vendor/plugins/noneditable/plugin.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/noneditable/plugin.js
rename to client/static/mosaico/vendor/plugins/noneditable/plugin.js
diff --git a/client/public/mosaico/vendor/plugins/noneditable/plugin.min.js b/client/static/mosaico/vendor/plugins/noneditable/plugin.min.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/noneditable/plugin.min.js
rename to client/static/mosaico/vendor/plugins/noneditable/plugin.min.js
diff --git a/client/public/mosaico/vendor/plugins/pagebreak/index.js b/client/static/mosaico/vendor/plugins/pagebreak/index.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/pagebreak/index.js
rename to client/static/mosaico/vendor/plugins/pagebreak/index.js
diff --git a/client/public/mosaico/vendor/plugins/pagebreak/plugin.js b/client/static/mosaico/vendor/plugins/pagebreak/plugin.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/pagebreak/plugin.js
rename to client/static/mosaico/vendor/plugins/pagebreak/plugin.js
diff --git a/client/public/mosaico/vendor/plugins/pagebreak/plugin.min.js b/client/static/mosaico/vendor/plugins/pagebreak/plugin.min.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/pagebreak/plugin.min.js
rename to client/static/mosaico/vendor/plugins/pagebreak/plugin.min.js
diff --git a/client/public/mosaico/vendor/plugins/paste/index.js b/client/static/mosaico/vendor/plugins/paste/index.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/paste/index.js
rename to client/static/mosaico/vendor/plugins/paste/index.js
diff --git a/client/public/mosaico/vendor/plugins/paste/plugin.js b/client/static/mosaico/vendor/plugins/paste/plugin.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/paste/plugin.js
rename to client/static/mosaico/vendor/plugins/paste/plugin.js
diff --git a/client/public/mosaico/vendor/plugins/paste/plugin.min.js b/client/static/mosaico/vendor/plugins/paste/plugin.min.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/paste/plugin.min.js
rename to client/static/mosaico/vendor/plugins/paste/plugin.min.js
diff --git a/client/public/mosaico/vendor/plugins/preview/index.js b/client/static/mosaico/vendor/plugins/preview/index.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/preview/index.js
rename to client/static/mosaico/vendor/plugins/preview/index.js
diff --git a/client/public/mosaico/vendor/plugins/preview/plugin.js b/client/static/mosaico/vendor/plugins/preview/plugin.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/preview/plugin.js
rename to client/static/mosaico/vendor/plugins/preview/plugin.js
diff --git a/client/public/mosaico/vendor/plugins/preview/plugin.min.js b/client/static/mosaico/vendor/plugins/preview/plugin.min.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/preview/plugin.min.js
rename to client/static/mosaico/vendor/plugins/preview/plugin.min.js
diff --git a/client/public/mosaico/vendor/plugins/print/index.js b/client/static/mosaico/vendor/plugins/print/index.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/print/index.js
rename to client/static/mosaico/vendor/plugins/print/index.js
diff --git a/client/public/mosaico/vendor/plugins/print/plugin.js b/client/static/mosaico/vendor/plugins/print/plugin.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/print/plugin.js
rename to client/static/mosaico/vendor/plugins/print/plugin.js
diff --git a/client/public/mosaico/vendor/plugins/print/plugin.min.js b/client/static/mosaico/vendor/plugins/print/plugin.min.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/print/plugin.min.js
rename to client/static/mosaico/vendor/plugins/print/plugin.min.js
diff --git a/client/public/mosaico/vendor/plugins/save/index.js b/client/static/mosaico/vendor/plugins/save/index.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/save/index.js
rename to client/static/mosaico/vendor/plugins/save/index.js
diff --git a/client/public/mosaico/vendor/plugins/save/plugin.js b/client/static/mosaico/vendor/plugins/save/plugin.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/save/plugin.js
rename to client/static/mosaico/vendor/plugins/save/plugin.js
diff --git a/client/public/mosaico/vendor/plugins/save/plugin.min.js b/client/static/mosaico/vendor/plugins/save/plugin.min.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/save/plugin.min.js
rename to client/static/mosaico/vendor/plugins/save/plugin.min.js
diff --git a/client/public/mosaico/vendor/plugins/searchreplace/index.js b/client/static/mosaico/vendor/plugins/searchreplace/index.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/searchreplace/index.js
rename to client/static/mosaico/vendor/plugins/searchreplace/index.js
diff --git a/client/public/mosaico/vendor/plugins/searchreplace/plugin.js b/client/static/mosaico/vendor/plugins/searchreplace/plugin.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/searchreplace/plugin.js
rename to client/static/mosaico/vendor/plugins/searchreplace/plugin.js
diff --git a/client/public/mosaico/vendor/plugins/searchreplace/plugin.min.js b/client/static/mosaico/vendor/plugins/searchreplace/plugin.min.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/searchreplace/plugin.min.js
rename to client/static/mosaico/vendor/plugins/searchreplace/plugin.min.js
diff --git a/client/public/mosaico/vendor/plugins/spellchecker/index.js b/client/static/mosaico/vendor/plugins/spellchecker/index.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/spellchecker/index.js
rename to client/static/mosaico/vendor/plugins/spellchecker/index.js
diff --git a/client/public/mosaico/vendor/plugins/spellchecker/plugin.js b/client/static/mosaico/vendor/plugins/spellchecker/plugin.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/spellchecker/plugin.js
rename to client/static/mosaico/vendor/plugins/spellchecker/plugin.js
diff --git a/client/public/mosaico/vendor/plugins/spellchecker/plugin.min.js b/client/static/mosaico/vendor/plugins/spellchecker/plugin.min.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/spellchecker/plugin.min.js
rename to client/static/mosaico/vendor/plugins/spellchecker/plugin.min.js
diff --git a/client/public/mosaico/vendor/plugins/tabfocus/index.js b/client/static/mosaico/vendor/plugins/tabfocus/index.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/tabfocus/index.js
rename to client/static/mosaico/vendor/plugins/tabfocus/index.js
diff --git a/client/public/mosaico/vendor/plugins/tabfocus/plugin.js b/client/static/mosaico/vendor/plugins/tabfocus/plugin.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/tabfocus/plugin.js
rename to client/static/mosaico/vendor/plugins/tabfocus/plugin.js
diff --git a/client/public/mosaico/vendor/plugins/tabfocus/plugin.min.js b/client/static/mosaico/vendor/plugins/tabfocus/plugin.min.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/tabfocus/plugin.min.js
rename to client/static/mosaico/vendor/plugins/tabfocus/plugin.min.js
diff --git a/client/public/mosaico/vendor/plugins/table/index.js b/client/static/mosaico/vendor/plugins/table/index.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/table/index.js
rename to client/static/mosaico/vendor/plugins/table/index.js
diff --git a/client/public/mosaico/vendor/plugins/table/plugin.js b/client/static/mosaico/vendor/plugins/table/plugin.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/table/plugin.js
rename to client/static/mosaico/vendor/plugins/table/plugin.js
diff --git a/client/public/mosaico/vendor/plugins/table/plugin.min.js b/client/static/mosaico/vendor/plugins/table/plugin.min.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/table/plugin.min.js
rename to client/static/mosaico/vendor/plugins/table/plugin.min.js
diff --git a/client/public/mosaico/vendor/plugins/template/index.js b/client/static/mosaico/vendor/plugins/template/index.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/template/index.js
rename to client/static/mosaico/vendor/plugins/template/index.js
diff --git a/client/public/mosaico/vendor/plugins/template/plugin.js b/client/static/mosaico/vendor/plugins/template/plugin.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/template/plugin.js
rename to client/static/mosaico/vendor/plugins/template/plugin.js
diff --git a/client/public/mosaico/vendor/plugins/template/plugin.min.js b/client/static/mosaico/vendor/plugins/template/plugin.min.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/template/plugin.min.js
rename to client/static/mosaico/vendor/plugins/template/plugin.min.js
diff --git a/client/public/mosaico/vendor/plugins/textcolor/index.js b/client/static/mosaico/vendor/plugins/textcolor/index.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/textcolor/index.js
rename to client/static/mosaico/vendor/plugins/textcolor/index.js
diff --git a/client/public/mosaico/vendor/plugins/textcolor/plugin.js b/client/static/mosaico/vendor/plugins/textcolor/plugin.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/textcolor/plugin.js
rename to client/static/mosaico/vendor/plugins/textcolor/plugin.js
diff --git a/client/public/mosaico/vendor/plugins/textcolor/plugin.min.js b/client/static/mosaico/vendor/plugins/textcolor/plugin.min.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/textcolor/plugin.min.js
rename to client/static/mosaico/vendor/plugins/textcolor/plugin.min.js
diff --git a/client/public/mosaico/vendor/plugins/textpattern/index.js b/client/static/mosaico/vendor/plugins/textpattern/index.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/textpattern/index.js
rename to client/static/mosaico/vendor/plugins/textpattern/index.js
diff --git a/client/public/mosaico/vendor/plugins/textpattern/plugin.js b/client/static/mosaico/vendor/plugins/textpattern/plugin.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/textpattern/plugin.js
rename to client/static/mosaico/vendor/plugins/textpattern/plugin.js
diff --git a/client/public/mosaico/vendor/plugins/textpattern/plugin.min.js b/client/static/mosaico/vendor/plugins/textpattern/plugin.min.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/textpattern/plugin.min.js
rename to client/static/mosaico/vendor/plugins/textpattern/plugin.min.js
diff --git a/client/public/mosaico/vendor/plugins/toc/index.js b/client/static/mosaico/vendor/plugins/toc/index.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/toc/index.js
rename to client/static/mosaico/vendor/plugins/toc/index.js
diff --git a/client/public/mosaico/vendor/plugins/toc/plugin.js b/client/static/mosaico/vendor/plugins/toc/plugin.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/toc/plugin.js
rename to client/static/mosaico/vendor/plugins/toc/plugin.js
diff --git a/client/public/mosaico/vendor/plugins/toc/plugin.min.js b/client/static/mosaico/vendor/plugins/toc/plugin.min.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/toc/plugin.min.js
rename to client/static/mosaico/vendor/plugins/toc/plugin.min.js
diff --git a/client/public/mosaico/vendor/plugins/visualblocks/css/visualblocks.css b/client/static/mosaico/vendor/plugins/visualblocks/css/visualblocks.css
similarity index 100%
rename from client/public/mosaico/vendor/plugins/visualblocks/css/visualblocks.css
rename to client/static/mosaico/vendor/plugins/visualblocks/css/visualblocks.css
diff --git a/client/public/mosaico/vendor/plugins/visualblocks/index.js b/client/static/mosaico/vendor/plugins/visualblocks/index.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/visualblocks/index.js
rename to client/static/mosaico/vendor/plugins/visualblocks/index.js
diff --git a/client/public/mosaico/vendor/plugins/visualblocks/plugin.js b/client/static/mosaico/vendor/plugins/visualblocks/plugin.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/visualblocks/plugin.js
rename to client/static/mosaico/vendor/plugins/visualblocks/plugin.js
diff --git a/client/public/mosaico/vendor/plugins/visualblocks/plugin.min.js b/client/static/mosaico/vendor/plugins/visualblocks/plugin.min.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/visualblocks/plugin.min.js
rename to client/static/mosaico/vendor/plugins/visualblocks/plugin.min.js
diff --git a/client/public/mosaico/vendor/plugins/visualchars/index.js b/client/static/mosaico/vendor/plugins/visualchars/index.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/visualchars/index.js
rename to client/static/mosaico/vendor/plugins/visualchars/index.js
diff --git a/client/public/mosaico/vendor/plugins/visualchars/plugin.js b/client/static/mosaico/vendor/plugins/visualchars/plugin.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/visualchars/plugin.js
rename to client/static/mosaico/vendor/plugins/visualchars/plugin.js
diff --git a/client/public/mosaico/vendor/plugins/visualchars/plugin.min.js b/client/static/mosaico/vendor/plugins/visualchars/plugin.min.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/visualchars/plugin.min.js
rename to client/static/mosaico/vendor/plugins/visualchars/plugin.min.js
diff --git a/client/public/mosaico/vendor/plugins/wordcount/index.js b/client/static/mosaico/vendor/plugins/wordcount/index.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/wordcount/index.js
rename to client/static/mosaico/vendor/plugins/wordcount/index.js
diff --git a/client/public/mosaico/vendor/plugins/wordcount/plugin.js b/client/static/mosaico/vendor/plugins/wordcount/plugin.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/wordcount/plugin.js
rename to client/static/mosaico/vendor/plugins/wordcount/plugin.js
diff --git a/client/public/mosaico/vendor/plugins/wordcount/plugin.min.js b/client/static/mosaico/vendor/plugins/wordcount/plugin.min.js
similarity index 100%
rename from client/public/mosaico/vendor/plugins/wordcount/plugin.min.js
rename to client/static/mosaico/vendor/plugins/wordcount/plugin.min.js
diff --git a/client/public/mosaico/vendor/skins/gray-flat/Variables.less b/client/static/mosaico/vendor/skins/gray-flat/Variables.less
similarity index 100%
rename from client/public/mosaico/vendor/skins/gray-flat/Variables.less
rename to client/static/mosaico/vendor/skins/gray-flat/Variables.less
diff --git a/client/public/mosaico/vendor/skins/gray-flat/content.inline.min.css b/client/static/mosaico/vendor/skins/gray-flat/content.inline.min.css
similarity index 100%
rename from client/public/mosaico/vendor/skins/gray-flat/content.inline.min.css
rename to client/static/mosaico/vendor/skins/gray-flat/content.inline.min.css
diff --git a/client/public/mosaico/vendor/skins/gray-flat/content.min.css b/client/static/mosaico/vendor/skins/gray-flat/content.min.css
similarity index 100%
rename from client/public/mosaico/vendor/skins/gray-flat/content.min.css
rename to client/static/mosaico/vendor/skins/gray-flat/content.min.css
diff --git a/client/public/mosaico/vendor/skins/gray-flat/fonts/readme.md b/client/static/mosaico/vendor/skins/gray-flat/fonts/readme.md
similarity index 100%
rename from client/public/mosaico/vendor/skins/gray-flat/fonts/readme.md
rename to client/static/mosaico/vendor/skins/gray-flat/fonts/readme.md
diff --git a/client/public/mosaico/vendor/skins/gray-flat/fonts/tinymce-small.eot b/client/static/mosaico/vendor/skins/gray-flat/fonts/tinymce-small.eot
similarity index 100%
rename from client/public/mosaico/vendor/skins/gray-flat/fonts/tinymce-small.eot
rename to client/static/mosaico/vendor/skins/gray-flat/fonts/tinymce-small.eot
diff --git a/client/public/mosaico/vendor/skins/gray-flat/fonts/tinymce-small.json b/client/static/mosaico/vendor/skins/gray-flat/fonts/tinymce-small.json
similarity index 100%
rename from client/public/mosaico/vendor/skins/gray-flat/fonts/tinymce-small.json
rename to client/static/mosaico/vendor/skins/gray-flat/fonts/tinymce-small.json
diff --git a/client/public/mosaico/vendor/skins/gray-flat/fonts/tinymce-small.svg b/client/static/mosaico/vendor/skins/gray-flat/fonts/tinymce-small.svg
similarity index 100%
rename from client/public/mosaico/vendor/skins/gray-flat/fonts/tinymce-small.svg
rename to client/static/mosaico/vendor/skins/gray-flat/fonts/tinymce-small.svg
diff --git a/client/public/mosaico/vendor/skins/gray-flat/fonts/tinymce-small.ttf b/client/static/mosaico/vendor/skins/gray-flat/fonts/tinymce-small.ttf
similarity index 100%
rename from client/public/mosaico/vendor/skins/gray-flat/fonts/tinymce-small.ttf
rename to client/static/mosaico/vendor/skins/gray-flat/fonts/tinymce-small.ttf
diff --git a/client/public/mosaico/vendor/skins/gray-flat/fonts/tinymce-small.woff b/client/static/mosaico/vendor/skins/gray-flat/fonts/tinymce-small.woff
similarity index 100%
rename from client/public/mosaico/vendor/skins/gray-flat/fonts/tinymce-small.woff
rename to client/static/mosaico/vendor/skins/gray-flat/fonts/tinymce-small.woff
diff --git a/client/public/mosaico/vendor/skins/gray-flat/fonts/tinymce.eot b/client/static/mosaico/vendor/skins/gray-flat/fonts/tinymce.eot
similarity index 100%
rename from client/public/mosaico/vendor/skins/gray-flat/fonts/tinymce.eot
rename to client/static/mosaico/vendor/skins/gray-flat/fonts/tinymce.eot
diff --git a/client/public/mosaico/vendor/skins/gray-flat/fonts/tinymce.json b/client/static/mosaico/vendor/skins/gray-flat/fonts/tinymce.json
similarity index 100%
rename from client/public/mosaico/vendor/skins/gray-flat/fonts/tinymce.json
rename to client/static/mosaico/vendor/skins/gray-flat/fonts/tinymce.json
diff --git a/client/public/mosaico/vendor/skins/gray-flat/fonts/tinymce.svg b/client/static/mosaico/vendor/skins/gray-flat/fonts/tinymce.svg
similarity index 100%
rename from client/public/mosaico/vendor/skins/gray-flat/fonts/tinymce.svg
rename to client/static/mosaico/vendor/skins/gray-flat/fonts/tinymce.svg
diff --git a/client/public/mosaico/vendor/skins/gray-flat/fonts/tinymce.ttf b/client/static/mosaico/vendor/skins/gray-flat/fonts/tinymce.ttf
similarity index 100%
rename from client/public/mosaico/vendor/skins/gray-flat/fonts/tinymce.ttf
rename to client/static/mosaico/vendor/skins/gray-flat/fonts/tinymce.ttf
diff --git a/client/public/mosaico/vendor/skins/gray-flat/fonts/tinymce.woff b/client/static/mosaico/vendor/skins/gray-flat/fonts/tinymce.woff
similarity index 100%
rename from client/public/mosaico/vendor/skins/gray-flat/fonts/tinymce.woff
rename to client/static/mosaico/vendor/skins/gray-flat/fonts/tinymce.woff
diff --git a/client/public/mosaico/vendor/skins/gray-flat/img/anchor.gif b/client/static/mosaico/vendor/skins/gray-flat/img/anchor.gif
similarity index 100%
rename from client/public/mosaico/vendor/skins/gray-flat/img/anchor.gif
rename to client/static/mosaico/vendor/skins/gray-flat/img/anchor.gif
diff --git a/client/public/mosaico/vendor/skins/gray-flat/img/loader.gif b/client/static/mosaico/vendor/skins/gray-flat/img/loader.gif
similarity index 100%
rename from client/public/mosaico/vendor/skins/gray-flat/img/loader.gif
rename to client/static/mosaico/vendor/skins/gray-flat/img/loader.gif
diff --git a/client/public/mosaico/vendor/skins/gray-flat/img/object.gif b/client/static/mosaico/vendor/skins/gray-flat/img/object.gif
similarity index 100%
rename from client/public/mosaico/vendor/skins/gray-flat/img/object.gif
rename to client/static/mosaico/vendor/skins/gray-flat/img/object.gif
diff --git a/client/public/mosaico/vendor/skins/gray-flat/img/trans.gif b/client/static/mosaico/vendor/skins/gray-flat/img/trans.gif
similarity index 100%
rename from client/public/mosaico/vendor/skins/gray-flat/img/trans.gif
rename to client/static/mosaico/vendor/skins/gray-flat/img/trans.gif
diff --git a/client/public/mosaico/vendor/skins/gray-flat/skin.ie7.min.css b/client/static/mosaico/vendor/skins/gray-flat/skin.ie7.min.css
similarity index 100%
rename from client/public/mosaico/vendor/skins/gray-flat/skin.ie7.min.css
rename to client/static/mosaico/vendor/skins/gray-flat/skin.ie7.min.css
diff --git a/client/public/mosaico/vendor/skins/gray-flat/skin.json b/client/static/mosaico/vendor/skins/gray-flat/skin.json
similarity index 100%
rename from client/public/mosaico/vendor/skins/gray-flat/skin.json
rename to client/static/mosaico/vendor/skins/gray-flat/skin.json
diff --git a/client/public/mosaico/vendor/skins/gray-flat/skin.min.css b/client/static/mosaico/vendor/skins/gray-flat/skin.min.css
similarity index 100%
rename from client/public/mosaico/vendor/skins/gray-flat/skin.min.css
rename to client/static/mosaico/vendor/skins/gray-flat/skin.min.css
diff --git a/client/public/mosaico/vendor/skins/lightgray/content.inline.min.css b/client/static/mosaico/vendor/skins/lightgray/content.inline.min.css
similarity index 100%
rename from client/public/mosaico/vendor/skins/lightgray/content.inline.min.css
rename to client/static/mosaico/vendor/skins/lightgray/content.inline.min.css
diff --git a/client/public/mosaico/vendor/skins/lightgray/content.min.css b/client/static/mosaico/vendor/skins/lightgray/content.min.css
similarity index 100%
rename from client/public/mosaico/vendor/skins/lightgray/content.min.css
rename to client/static/mosaico/vendor/skins/lightgray/content.min.css
diff --git a/client/public/mosaico/vendor/skins/lightgray/content.mobile.min.css b/client/static/mosaico/vendor/skins/lightgray/content.mobile.min.css
similarity index 100%
rename from client/public/mosaico/vendor/skins/lightgray/content.mobile.min.css
rename to client/static/mosaico/vendor/skins/lightgray/content.mobile.min.css
diff --git a/client/public/mosaico/vendor/skins/lightgray/fonts/tinymce-mobile.woff b/client/static/mosaico/vendor/skins/lightgray/fonts/tinymce-mobile.woff
similarity index 100%
rename from client/public/mosaico/vendor/skins/lightgray/fonts/tinymce-mobile.woff
rename to client/static/mosaico/vendor/skins/lightgray/fonts/tinymce-mobile.woff
diff --git a/client/public/mosaico/vendor/skins/lightgray/fonts/tinymce-small.eot b/client/static/mosaico/vendor/skins/lightgray/fonts/tinymce-small.eot
similarity index 100%
rename from client/public/mosaico/vendor/skins/lightgray/fonts/tinymce-small.eot
rename to client/static/mosaico/vendor/skins/lightgray/fonts/tinymce-small.eot
diff --git a/client/public/mosaico/vendor/skins/lightgray/fonts/tinymce-small.svg b/client/static/mosaico/vendor/skins/lightgray/fonts/tinymce-small.svg
similarity index 100%
rename from client/public/mosaico/vendor/skins/lightgray/fonts/tinymce-small.svg
rename to client/static/mosaico/vendor/skins/lightgray/fonts/tinymce-small.svg
diff --git a/client/public/mosaico/vendor/skins/lightgray/fonts/tinymce-small.ttf b/client/static/mosaico/vendor/skins/lightgray/fonts/tinymce-small.ttf
similarity index 100%
rename from client/public/mosaico/vendor/skins/lightgray/fonts/tinymce-small.ttf
rename to client/static/mosaico/vendor/skins/lightgray/fonts/tinymce-small.ttf
diff --git a/client/public/mosaico/vendor/skins/lightgray/fonts/tinymce-small.woff b/client/static/mosaico/vendor/skins/lightgray/fonts/tinymce-small.woff
similarity index 100%
rename from client/public/mosaico/vendor/skins/lightgray/fonts/tinymce-small.woff
rename to client/static/mosaico/vendor/skins/lightgray/fonts/tinymce-small.woff
diff --git a/client/public/mosaico/vendor/skins/lightgray/fonts/tinymce.eot b/client/static/mosaico/vendor/skins/lightgray/fonts/tinymce.eot
similarity index 100%
rename from client/public/mosaico/vendor/skins/lightgray/fonts/tinymce.eot
rename to client/static/mosaico/vendor/skins/lightgray/fonts/tinymce.eot
diff --git a/client/public/mosaico/vendor/skins/lightgray/fonts/tinymce.svg b/client/static/mosaico/vendor/skins/lightgray/fonts/tinymce.svg
similarity index 100%
rename from client/public/mosaico/vendor/skins/lightgray/fonts/tinymce.svg
rename to client/static/mosaico/vendor/skins/lightgray/fonts/tinymce.svg
diff --git a/client/public/mosaico/vendor/skins/lightgray/fonts/tinymce.ttf b/client/static/mosaico/vendor/skins/lightgray/fonts/tinymce.ttf
similarity index 100%
rename from client/public/mosaico/vendor/skins/lightgray/fonts/tinymce.ttf
rename to client/static/mosaico/vendor/skins/lightgray/fonts/tinymce.ttf
diff --git a/client/public/mosaico/vendor/skins/lightgray/fonts/tinymce.woff b/client/static/mosaico/vendor/skins/lightgray/fonts/tinymce.woff
similarity index 100%
rename from client/public/mosaico/vendor/skins/lightgray/fonts/tinymce.woff
rename to client/static/mosaico/vendor/skins/lightgray/fonts/tinymce.woff
diff --git a/client/public/mosaico/vendor/skins/lightgray/img/anchor.gif b/client/static/mosaico/vendor/skins/lightgray/img/anchor.gif
similarity index 100%
rename from client/public/mosaico/vendor/skins/lightgray/img/anchor.gif
rename to client/static/mosaico/vendor/skins/lightgray/img/anchor.gif
diff --git a/client/public/mosaico/vendor/skins/lightgray/img/loader.gif b/client/static/mosaico/vendor/skins/lightgray/img/loader.gif
similarity index 100%
rename from client/public/mosaico/vendor/skins/lightgray/img/loader.gif
rename to client/static/mosaico/vendor/skins/lightgray/img/loader.gif
diff --git a/client/public/mosaico/vendor/skins/lightgray/img/object.gif b/client/static/mosaico/vendor/skins/lightgray/img/object.gif
similarity index 100%
rename from client/public/mosaico/vendor/skins/lightgray/img/object.gif
rename to client/static/mosaico/vendor/skins/lightgray/img/object.gif
diff --git a/client/public/mosaico/vendor/skins/lightgray/img/trans.gif b/client/static/mosaico/vendor/skins/lightgray/img/trans.gif
similarity index 100%
rename from client/public/mosaico/vendor/skins/lightgray/img/trans.gif
rename to client/static/mosaico/vendor/skins/lightgray/img/trans.gif
diff --git a/client/public/mosaico/vendor/skins/lightgray/skin.min.css b/client/static/mosaico/vendor/skins/lightgray/skin.min.css
similarity index 100%
rename from client/public/mosaico/vendor/skins/lightgray/skin.min.css
rename to client/static/mosaico/vendor/skins/lightgray/skin.min.css
diff --git a/client/public/mosaico/vendor/skins/lightgray/skin.mobile.min.css b/client/static/mosaico/vendor/skins/lightgray/skin.mobile.min.css
similarity index 100%
rename from client/public/mosaico/vendor/skins/lightgray/skin.mobile.min.css
rename to client/static/mosaico/vendor/skins/lightgray/skin.mobile.min.css
diff --git a/client/public/mosaico/vendor/themes/inlite/index.js b/client/static/mosaico/vendor/themes/inlite/index.js
similarity index 100%
rename from client/public/mosaico/vendor/themes/inlite/index.js
rename to client/static/mosaico/vendor/themes/inlite/index.js
diff --git a/client/public/mosaico/vendor/themes/inlite/theme.js b/client/static/mosaico/vendor/themes/inlite/theme.js
similarity index 100%
rename from client/public/mosaico/vendor/themes/inlite/theme.js
rename to client/static/mosaico/vendor/themes/inlite/theme.js
diff --git a/client/public/mosaico/vendor/themes/inlite/theme.min.js b/client/static/mosaico/vendor/themes/inlite/theme.min.js
similarity index 100%
rename from client/public/mosaico/vendor/themes/inlite/theme.min.js
rename to client/static/mosaico/vendor/themes/inlite/theme.min.js
diff --git a/client/public/mosaico/vendor/themes/mobile/index.js b/client/static/mosaico/vendor/themes/mobile/index.js
similarity index 100%
rename from client/public/mosaico/vendor/themes/mobile/index.js
rename to client/static/mosaico/vendor/themes/mobile/index.js
diff --git a/client/public/mosaico/vendor/themes/mobile/theme.js b/client/static/mosaico/vendor/themes/mobile/theme.js
similarity index 100%
rename from client/public/mosaico/vendor/themes/mobile/theme.js
rename to client/static/mosaico/vendor/themes/mobile/theme.js
diff --git a/client/public/mosaico/vendor/themes/mobile/theme.min.js b/client/static/mosaico/vendor/themes/mobile/theme.min.js
similarity index 100%
rename from client/public/mosaico/vendor/themes/mobile/theme.min.js
rename to client/static/mosaico/vendor/themes/mobile/theme.min.js
diff --git a/client/public/mosaico/vendor/themes/modern/index.js b/client/static/mosaico/vendor/themes/modern/index.js
similarity index 100%
rename from client/public/mosaico/vendor/themes/modern/index.js
rename to client/static/mosaico/vendor/themes/modern/index.js
diff --git a/client/public/mosaico/vendor/themes/modern/theme.js b/client/static/mosaico/vendor/themes/modern/theme.js
similarity index 100%
rename from client/public/mosaico/vendor/themes/modern/theme.js
rename to client/static/mosaico/vendor/themes/modern/theme.js
diff --git a/client/public/mosaico/vendor/themes/modern/theme.min.js b/client/static/mosaico/vendor/themes/modern/theme.min.js
similarity index 100%
rename from client/public/mosaico/vendor/themes/modern/theme.min.js
rename to client/static/mosaico/vendor/themes/modern/theme.min.js
diff --git a/client/public/mosaico/vendor/tinymce.min.js b/client/static/mosaico/vendor/tinymce.min.js
similarity index 100%
rename from client/public/mosaico/vendor/tinymce.min.js
rename to client/static/mosaico/vendor/tinymce.min.js
diff --git a/client/public/subscription/form-input-style.css b/client/static/subscription/form-input-style.css
similarity index 100%
rename from client/public/subscription/form-input-style.css
rename to client/static/subscription/form-input-style.css
diff --git a/client/public/subscription/widget.js b/client/static/subscription/widget.js
similarity index 100%
rename from client/public/subscription/widget.js
rename to client/static/subscription/widget.js
diff --git a/config/default.yaml b/config/default.yaml
index 232fba7d..e374f68f 100644
--- a/config/default.yaml
+++ b/config/default.yaml
@@ -43,17 +43,22 @@ log:
level: verbose
www:
- # HTTP port to listen on
- port: 3000
- # HTTP port to listen on for sandboxed requests
+ # HTTP port to listen on for trusted requests (logged-in users)
+ trustedPort: 3000
+ # HTTP port to listen on for sandboxed requests (logged-in users)
sandboxPort: 8081
+ # HTTP port to listen on for public requests (campaign recipients)
+ publicPort: 8082
# HTTP interface to listen on
host: 0.0.0.0
- # URL base for trusted urls. It must be absolute (starting with http:// or https://). If Mailtrain is served on
+ # URL base for trusted urls (logged-in users). It must be absolute (starting with http:// or https://). If Mailtrain is served on
# a non-standard port (e.g. 3000), the URL must also specify the port.
trustedUrlBase: http://localhost:3000
- # URL base for sandbox urls. It must be absolute (starting with http:// or https://) and contain the sandbox port.
+ # URL base for sandbox urls (logged-in users). It must be absolute (starting with http:// or https://) and contain the sandbox port.
sandboxUrlBase: http://localhost:8081
+ # URL base for public urls (campaign recipients). It must be absolute (starting with http:// or https://) and contain the sandbox port.
+ publicUrlBase: http://localhost:8082
+
# Secret for signing the session ID cookie
secret: a cat
diff --git a/docker-compose.yml b/docker-compose.yml
index ba00757e..82c2b332 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -1,3 +1,5 @@
+# FIXME - this is not yet upgraded to Mailtrain 2
+
version: '2'
services:
mysql:
diff --git a/index.js b/index.js
index 967fec4b..9c3b9017 100644
--- a/index.js
+++ b/index.js
@@ -19,8 +19,9 @@ const privilegeHelpers = require('./lib/privilege-helpers');
const knex = require('./lib/knex');
const shares = require('./models/shares');
-const trustedPort = config.www.port;
+const trustedPort = config.www.trustedPort;
const sandboxPort = config.www.sandboxPort;
+const publicPort = config.www.publicPort;
const host = config.www.host;
if (config.title) {
@@ -30,8 +31,8 @@ if (config.title) {
log.level = config.log.level;
-function startHTTPServer(trusted, port, callback) {
- const app = appBuilder.createApp(trusted);
+function startHTTPServer(appType, port, callback) {
+ const app = appBuilder.createApp(appType);
app.set('port', port);
const server = http.createServer(app);
@@ -84,23 +85,25 @@ dbcheck(err => { // Check if database needs upgrading before starting the server
executor.spawn(() => {
testServer(() => {
verpServer(() => {
- startHTTPServer(true, trustedPort, () => {
- startHTTPServer(false, sandboxPort, () => {
- privilegeHelpers.dropRootPrivileges();
+ startHTTPServer(AppType.TRUSTED, trustedPort, () => {
+ startHTTPServer(AppType.SANDBOXED, sandboxPort, () => {
+ startHTTPServer(AppType.PUBLIC, publicPort, () => {
+ privilegeHelpers.dropRootPrivileges();
- tzupdate.start();
+ tzupdate.start();
- importer.spawn(() => {
- feedcheck.spawn(() => {
- senders.spawn(() => {
- //triggers(() => {
- //postfixBounceServer(async () => {
+ importer.spawn(() => {
+ feedcheck.spawn(() => {
+ senders.spawn(() => {
+ //triggers(() => {
+ //postfixBounceServer(async () => {
(async () => {
await reportProcessor.init();
log.info('Service', 'All services started');
})();
- //});
- //});
+ //});
+ //});
+ });
});
});
});
diff --git a/lib/client-helpers.js b/lib/client-helpers.js
index 4f8689bb..8453c374 100644
--- a/lib/client-helpers.js
+++ b/lib/client-helpers.js
@@ -5,8 +5,10 @@ const config = require('config');
const forms = require('../models/forms');
const shares = require('../models/shares');
const urls = require('./urls');
+const { AppType } = require('../shared/app');
-async function getAnonymousConfig(context, trusted) {
+
+async function getAnonymousConfig(context, appType) {
return {
authMethod: passport.authMethod,
isAuthMethodLocal: passport.isAuthMethodLocal,
@@ -17,7 +19,9 @@ async function getAnonymousConfig(context, trusted) {
trustedUrlBaseDir: urls.getTrustedUrlBaseDir(),
sandboxUrlBase: urls.getSandboxUrlBase(),
sandboxUrlBaseDir: urls.getSandboxUrlBaseDir(),
- trusted
+ publicUrlBase: urls.getPublicUrlBase(),
+ publicUrlBaseDir: urls.getPublicUrlBaseDir(),
+ appType
}
}
@@ -36,8 +40,7 @@ async function getAuthenticatedConfig(context) {
}
}
-module.exports = {
- getAuthenticatedConfig,
- getAnonymousConfig
-};
+
+module.exports.getAuthenticatedConfig = getAuthenticatedConfig;
+module.exports.getAnonymousConfig = getAnonymousConfig;
diff --git a/lib/helpers.js b/lib/helpers.js
index cfa8b3f6..2976c657 100644
--- a/lib/helpers.js
+++ b/lib/helpers.js
@@ -3,63 +3,11 @@
let _ = require('./translate')._;
module.exports = {
- getDefaultMergeTags,
- getRSSMergeTags,
enforce,
cleanupFromPost,
filterObject
};
-function getDefaultMergeTags() {
- return [{
- key: 'LINK_UNSUBSCRIBE',
- value: _('URL that points to the unsubscribe page')
- }, {
- key: 'LINK_PREFERENCES',
- value: _('URL that points to the preferences page of the subscriber')
- }, {
- key: 'LINK_BROWSER',
- value: _('URL to preview the message in a browser')
- }, {
- key: 'EMAIL',
- value: _('Email address')
- }, {
- key: 'SUBSCRIPTION_ID',
- value: _('Unique ID that identifies the recipient')
- }, {
- key: 'LIST_ID',
- value: _('Unique ID that identifies the list used for this campaign')
- }, {
- key: 'CAMPAIGN_ID',
- value: _('Unique ID that identifies current campaign')
- }];
-}
-
-function getRSSMergeTags() {
- return [{
- key: 'RSS_ENTRY',
- value: _('content from an RSS entry')
- }, {
- key: 'RSS_ENTRY_TITLE',
- value: _('RSS entry title')
- }, {
- key: 'RSS_ENTRY_DATE',
- value: _('RSS entry date')
- }, {
- key: 'RSS_ENTRY_LINK',
- value: _('RSS entry link')
- }, {
- key: 'RSS_ENTRY_CONTENT',
- value: _('content from an RSS entry')
- }, {
- key: 'RSS_ENTRY_SUMMARY',
- value: _('RSS entry summary')
- }, {
- key: 'RSS_ENTRY_IMAGE_URL',
- value: _('RSS entry image URL')
- }];
-}
-
function enforce(condition, message) {
if (!condition) {
throw new Error(message);
diff --git a/lib/mailers.js b/lib/mailers.js
index b0ff259d..f619cd0d 100644
--- a/lib/mailers.js
+++ b/lib/mailers.js
@@ -56,6 +56,28 @@ function invalidateMailer(sendConfigurationId) {
async function _sendMail(transport, mail, template) {
+ let tryCount = 0;
+ const trySend = (callback) => {
+ tryCount++;
+ transport.sendMail(mail, (err, info) => {
+ if (err) {
+ log.error('Mail', err);
+ if (err.responseCode && err.responseCode >= 400 && err.responseCode < 500 && tryCount <= 5) {
+ // temporary error, try again
+ log.verbose('Mail', 'Retrying after %s sec. ...', tryCount);
+ return setTimeout(trySend, tryCount * 1000);
+ }
+ return callback(err);
+ }
+ return callback(null, info);
+ });
+ };
+
+ const trySendAsync = bluebird.promisify(trySend);
+ return await trySendAsync();
+}
+
+async function _sendTransactionalMail(transport, mail, template) {
if (!mail.headers) {
mail.headers = {};
}
@@ -83,28 +105,9 @@ async function _sendMail(transport, mail, template) {
});
}
- let tryCount = 0;
- const trySend = (callback) => {
- tryCount++;
- transport.sendMail(mail, (err, info) => {
- if (err) {
- log.error('Mail', err);
- if (err.responseCode && err.responseCode >= 400 && err.responseCode < 500 && tryCount <= 5) {
- // temporary error, try again
- log.verbose('Mail', 'Retrying after %s sec. ...', tryCount);
- return setTimeout(trySend, tryCount * 1000);
- }
- return callback(err);
- }
- return callback(null, info);
- });
- };
-
- const trySendAsync = bluebird.promisify(trySend);
- return await trySendAsync();
+ return await _sendMail(transport, mail);
}
-
async function _createTransport(sendConfiguration) {
const mailerSettings = sendConfiguration.mailer_settings;
const mailerType = sendConfiguration.mailer_type;
@@ -117,7 +120,7 @@ async function _createTransport(sendConfiguration) {
existingListeners = existingTransport.listeners('idle');
existingTransport.removeAllListeners('idle');
existingTransport.removeAllListeners('stream');
- existingTransport.checkThrottling = null;
+ existingTransport.throttleWait = null;
}
const logFunc = (...args) => {
@@ -190,7 +193,7 @@ async function _createTransport(sendConfiguration) {
existingListeners.forEach(listener => transport.on('idle', listener));
}
- let checkThrottling;
+ let throttleWait;
if (mailerType === sendConfigurations.MailerType.GENERIC_SMTP || mailerType === sendConfigurations.MailerType.ZONE_MTA) {
let throttling = mailerSettings.throttling;
@@ -200,7 +203,7 @@ async function _createTransport(sendConfiguration) {
let lastCheck = Date.now();
- checkThrottling = function (next) {
+ throttleWait = function (next) {
if (!throttling) {
return next();
}
@@ -218,12 +221,13 @@ async function _createTransport(sendConfiguration) {
}
};
} else {
- checkThrottling = next => next();
+ throttleWait = next => next();
}
transport.mailer = {
- checkThrottling,
- sendMail: async (mail, template) => await _sendMail(transport, mail, template)
+ throttleWait: bluebird.promisify(throttleWait),
+ sendTransationalMail: async (mail, template) => await _sendTransactionalMail(transport, mail, template),
+ sendMassMail: async (mail, template) => await _sendMail(transport, mail)
};
transports.set(sendConfiguration.id, transport);
diff --git a/lib/subscription-mail-helpers.js b/lib/subscription-mail-helpers.js
index e8e8e7be..9ba49825 100644
--- a/lib/subscription-mail-helpers.js
+++ b/lib/subscription-mail-helpers.js
@@ -139,7 +139,7 @@ async function _sendMail(list, email, template, subject, relativeUrls, subscript
try {
if (list.send_configuration) {
const mailer = await mailers.getOrCreateMailer(list.send_configuration);
- await mailer.sendMail({
+ await mailer.sendTransactionalMail({
from: {
name: configItems.defaultFrom,
address: configItems.defaultAddress
diff --git a/lib/tools.js b/lib/tools.js
index 97e5facc..16845823 100644
--- a/lib/tools.js
+++ b/lib/tools.js
@@ -4,6 +4,7 @@ const _ = require('./translate')._;
const util = require('util');
const isemail = require('isemail');
const path = require('path');
+const {getPublicUrl} = require('./urls');
const bluebird = require('bluebird');
@@ -11,11 +12,13 @@ const hasher = require('node-object-hash')();
const mjml = require('mjml');
const hbs = require('hbs');
const juice = require('juice');
+let he = require('he');
const fsReadFile = bluebird.promisify(require('fs').readFile);
const jsdomEnv = bluebird.promisify(require('jsdom').env);
+
const templates = new Map();
async function getTemplate(template) {
@@ -102,6 +105,38 @@ function validateEmailGetMessage(result, address) {
}
}
+function formatMessage(campaign, list, subscription, mergeTags, message, filter, isHTML) {
+ filter = typeof filter === 'function' ? filter : (str => str);
+
+ let links = getMessageLinks(campaign, list, subscription);
+
+ let getValue = key => {
+ key = (key || '').toString().toUpperCase().trim();
+ if (links.hasOwnProperty(key)) {
+ return links[key];
+ }
+ if (mergeTags.hasOwnProperty(key)) {
+ let value = (mergeTags[key] || '').toString();
+ let containsHTML = /<[a-z][\s\S]*>/.test(value);
+ return isHTML ? he.encode((containsHTML ? value : value.replace(/(?:\r\n|\r|\n)/g, ' ')), {
+ useNamedReferences: true,
+ allowUnsafeSymbols: true
+ }) : (containsHTML ? htmlToText.fromString(value) : value);
+ }
+ return false;
+ };
+
+ return message.replace(/\[([a-z0-9_]+)(?:\/([^\]]+))?\]/ig, (match, identifier, fallback) => {
+ identifier = identifier.toUpperCase();
+ let value = getValue(identifier);
+ if (value === false) {
+ return match;
+ }
+ value = (value || fallback || '').trim();
+ return filter(value);
+ });
+}
+
async function prepareHtml(html) {
if (!(html || '').toString().trim()) {
return false;
@@ -137,12 +172,23 @@ async function prepareHtml(html) {
return juice(preparedHtml);
}
+function getMessageLinks(campaign, list, subscription) {
+ return {
+ LINK_UNSUBSCRIBE: getPublicUrl('/subscription/' + list.cid + '/unsubscribe/' + subscription.cid + '?c=' + campaign.cid),
+ LINK_PREFERENCES: getPublicUrl('/subscription/' + list.cid + '/manage/' + subscription.cid),
+ LINK_BROWSER: getPublicUrl('/archive/' + campaign.cid + '/' + list.cid + '/' + subscription.cid),
+ CAMPAIGN_ID: campaign.cid,
+ LIST_ID: list.cid,
+ SUBSCRIPTION_ID: subscription.cid
+ };
+}
module.exports = {
validateEmail,
validateEmailGetMessage,
mergeTemplateIntoLayout,
getTemplate,
- prepareHtml
+ prepareHtml,
+ getMessageLinks
};
diff --git a/lib/urls.js b/lib/urls.js
index 53e32da8..207eb87d 100644
--- a/lib/urls.js
+++ b/lib/urls.js
@@ -12,6 +12,10 @@ function getSandboxUrlBase() {
return urllib.resolve(config.www.sandboxUrlBase, '');
}
+function getPublicUrlBase() {
+ return urllib.resolve(config.www.publicUrlBase, '');
+}
+
function getTrustedUrl(path) {
return urllib.resolve(config.www.trustedUrlBase, path || '');
}
@@ -24,21 +28,34 @@ function getSandboxUrl(path, context) {
}
}
+function getPublicUrl(path) {
+ return urllib.resolve(config.www.publicUrlBase, path || '');
+}
+
+
function getTrustedUrlBaseDir() {
- const ivisUrl = urllib.parse(config.www.trustedUrlBase);
- return ivisUrl.pathname;
+ const mailtrainUrl = urllib.parse(config.www.trustedUrlBase);
+ return mailtrainUrl.pathname;
}
function getSandboxUrlBaseDir() {
- const ivisUrl = urllib.parse(config.www.sandboxUrlBase);
- return ivisUrl.pathname;
+ const mailtrainUrl = urllib.parse(config.www.sandboxUrlBase);
+ return mailtrainUrl.pathname;
+}
+
+function getPublicUrlBaseDir() {
+ const mailtrainUrl = urllib.parse(config.www.publicUrlBase);
+ return mailtrainUrl.pathname;
}
module.exports = {
getTrustedUrl,
getSandboxUrl,
+ getPublicUrl,
getTrustedUrlBase,
getSandboxUrlBase,
+ getPublicUrlBase,
getTrustedUrlBaseDir,
- getSandboxUrlBaseDir
+ getSandboxUrlBaseDir,
+ getPublicUrlBaseDir
};
\ No newline at end of file
diff --git a/models/campaigns.js b/models/campaigns.js
index dfc9c764..6a980a43 100644
--- a/models/campaigns.js
+++ b/models/campaigns.js
@@ -255,6 +255,22 @@ async function getById(context, id, withPermissions = true, content = Content.AL
});
}
+async function getByCidTx(tx, context, cid) {
+ const entity = await tx('campaigns').where('cid', cid).first();
+ if (!entity) {
+ shares.throwPermissionDenied();
+ }
+
+ await shares.enforceEntityPermissionTx(tx, context, 'campaign', entity.id, 'view');
+ return entity;
+}
+
+async function getByCid(context, cid) {
+ return await knex.transaction(async tx => {
+ return getByCidTx(tx, context, cid);
+ });
+}
+
async function _validateAndPreprocess(tx, context, entity, isCreate, content) {
if (content === Content.ALL || content === Content.WITHOUT_SOURCE_CUSTOM || content === Content.RSS_ENTRY) {
await namespaceHelpers.validateEntity(tx, entity);
@@ -698,6 +714,8 @@ module.exports.listOthersWhoseListsAreIncludedDTAjax = listOthersWhoseListsAreIn
module.exports.listTestUsersDTAjax = listTestUsersDTAjax;
module.exports.getByIdTx = getByIdTx;
module.exports.getById = getById;
+module.exports.getByCidTx = getByCidTx;
+module.exports.getByCid = getByCid;
module.exports.create = create;
module.exports.createRssTx = createRssTx;
module.exports.updateWithConsistencyCheck = updateWithConsistencyCheck;
diff --git a/models/fields.js b/models/fields.js
index c1a45090..e7f7cd1f 100644
--- a/models/fields.js
+++ b/models/fields.js
@@ -557,8 +557,7 @@ async function removeAllByListIdTx(tx, context, listId) {
}
}
-// Returns an array that can be used for rendering by Handlebars
-async function forHbs(context, listId, subscription) { // assumes grouped subscription
+function forHbsWithFieldsGrouped(fieldsGrouped, subscription) { // assumes grouped subscription
const customFields = [{
name: 'Email Address',
column: 'email',
@@ -569,9 +568,7 @@ async function forHbs(context, listId, subscription) { // assumes grouped subscr
order_manage: -1
}];
- const flds = await listGrouped(context, listId);
-
- for (const fld of flds) {
+ for (const fld of fieldsGrouped) {
const type = fieldTypes[fld.type];
const fldCol = getFieldColumn(fld);
@@ -630,6 +627,13 @@ async function forHbs(context, listId, subscription) { // assumes grouped subscr
}
return customFields;
+
+}
+
+// Returns an array that can be used for rendering by Handlebars
+async function forHbs(context, listId, subscription) { // assumes grouped subscription
+ const flds = await listGrouped(context, listId);
+ return forHbsWithFieldsGrouped(flds, subscription);
}
// Converts subscription data received via (1) POST request from subscription form, (2) via subscribe request to API v1 to subscription structure supported by subscriptions model,
@@ -741,6 +745,7 @@ module.exports.remove = remove;
module.exports.removeAllByListIdTx = removeAllByListIdTx;
module.exports.serverValidate = serverValidate;
module.exports.forHbs = forHbs;
+module.exports.forHbsWithFieldsGrouped = forHbsWithFieldsGrouped;
module.exports.fromPost = fromPost;
module.exports.fromAPI = fromAPI;
module.exports.fromImport = fromImport;
diff --git a/models/files.js b/models/files.js
index 540f00f6..4c93eac4 100644
--- a/models/files.js
+++ b/models/files.js
@@ -50,11 +50,15 @@ async function listDTAjax(context, type, subType, entityId, params) {
);
}
-async function list(context, type, subType, entityId) {
+async function listTx(tx, context, type, subType, entityId) {
enforceTypePermitted(type, subType);
+ await shares.enforceEntityPermissionTx(tx, context, type, entityId, getFilesPermission(type, subType, 'view'));
+ return await tx(getFilesTable(type, subType)).where({entity: entityId}).select(['id', 'originalname', 'filename', 'size', 'created']).orderBy('originalname', 'asc');
+}
+
+async function list(context, type, subType, entityId) {
return await knex.transaction(async tx => {
- await shares.enforceEntityPermissionTx(tx, context, type, entityId, getFilesPermission(type, subType, 'view'));
- return await tx(getFilesTable(type, subType)).where({entity: entityId}).select(['id', 'originalname', 'filename', 'size', 'created']).orderBy('originalname', 'asc');
+ return listTx(tx, context, type, subType, entityId);
});
}
@@ -304,6 +308,7 @@ async function copyAllTx(tx, context, fromType, fromSubType, fromEntityId, toTyp
module.exports.filesDir = filesDir;
module.exports.listDTAjax = listDTAjax;
+module.exports.listTx = listTx;
module.exports.list = list;
module.exports.getFileById = getFileById;
module.exports.getFileByFilename = getFileByFilename;
diff --git a/models/forms.js b/models/forms.js
index 27f3756c..b71d81ce 100644
--- a/models/forms.js
+++ b/models/forms.js
@@ -203,7 +203,7 @@ async function getDefaultCustomFormValues() {
}
form.layout = await getContents('views/subscription/layout.mjml.hbs') || '';
- form.form_input_style = await getContents('public/subscription/form-input-style.css') || '@import url(/subscription/form-input-style.css);';
+ form.form_input_style = await getContents('static/subscription/form-input-style.css') || '@import url(/subscription/form-input-style.css);';
return form;
}
diff --git a/models/links.js b/models/links.js
new file mode 100644
index 00000000..7a06cfe5
--- /dev/null
+++ b/models/links.js
@@ -0,0 +1,175 @@
+'use strict';
+
+const knex = require('../lib/knex');
+const dtHelpers = require('../lib/dt-helpers');
+const shares = require('./shares');
+const tools = require('../lib/tools');
+const campaigns = require('./campaigns');
+const lists = require('./lists');
+const subscriptions = require('./subscriptions');
+const contextHelpers = require('../lib/context-helpers');
+const geoip = require('geoip-ultralight');
+const uaParser = require('device');
+const he = require('he');
+const { enforce } = require('../lib/helpers');
+const { getTrustedUrl } = require('../lib/urls');
+
+const LinkId = {
+ OPEN: -1,
+ GENERAL_CLICK: 0
+};
+
+async function resolve(linkCid) {
+ return await knex('links').where('cid', linkCid).select(['id', 'url']).first();
+}
+
+async function countLink(remoteIp, userAgent, campaignCid, listCid, subscriptionCid, linkId) {
+ await knex.transaction(async tx => {
+ const list = await lists.getByCidTx(tx, contextHelpers.getAdminContext(), listCid);
+ const campaign = await campaigns.getByCidTx(tx, contextHelpers.getAdminContext(), campaignCid);
+ const subscription = await subscriptions.getByCidTx(tx, contextHelpers.getAdminContext(), subscriptionCid);
+
+ const country = geoip.lookupCountry(remoteIp) || null;
+ const device = uaParser(userAgent, { unknownUserAgentDeviceType: 'desktop', emptyUserAgentDeviceType: 'desktop' });
+ const now = new Date();
+
+ const _countLink = async (clickLinkId, incrementOnDup) => {
+ try {
+ const campaignLinksQry = knex('campaign_links')
+ .insert({
+ campaign: campaign.id,
+ list: list.id,
+ subscription: subscription.id,
+ link: linkId,
+ ip: remoteIp,
+ device_type: device.type,
+ country
+ }).toSQL();
+
+ const campaignLinksQryResult = await tx.raw(campaignLinksQry.sql + (incrementOnDup ? ' ON DUPLICATE KEY UPDATE `count`=`count`+1' : ''), campaignLinksQry.bindings);
+
+ if (campaignLinksQryResult.affectedRows > 1) { // When using DUPLICATE KEY UPDATE, this means that the entry was already there
+ return false;
+ }
+
+ return true;
+
+ } catch (err) {
+ if (err.code === 'ER_DUP_ENTRY') {
+ return false;
+ }
+
+ throw err;
+ }
+ };
+
+
+
+ // Update opened and click timestamps
+ const latestUpdates = {};
+
+ if (!campaign.click_tracking_disabled && linkId > LinkId.GENERAL_CLICK) {
+ latestUpdates.latest_click = now;
+ }
+
+ if (!campaign.open_tracking_disabled) {
+ latestUpdates.latest_open = now;
+ }
+
+ if (latestUpdates.latest_click || latestUpdates.latest_open) {
+ await tx(subscriptions.getSubscriptionTableName(list.id)).update(latestUpdates).where('id', subscription.id);
+ }
+
+
+ // Update clicks
+ if (linkId > LinkId.GENERAL_CLICK && !campaign.click_tracking_disabled) {
+ if (await _countLink(linkId, true)) {
+ if (await _countLink(LinkId.GENERAL_CLICK, false)) {
+ await tx('campaigns').increment('clicks').where('id', campaign.id);
+ }
+ }
+ }
+
+
+ // Update opens. We count a click as an open too.
+ if (!campaign.open_tracking_disabled) {
+ if (await _countLink(LinkId.OPEN, true)) {
+ await tx('campaigns').increment('opened').where('id', campaign.id);
+ }
+ }
+ });
+}
+
+async function addOrGet(campaignId, url) {
+ return await knex.transaction(async tx => {
+ const link = tx('links').select(['id', 'cid']).where({
+ campaign: campaignId,
+ url
+ }).first();
+
+ if (!link) {
+ let cid = shortid.generate();
+
+ const ids = tx('links').insert({
+ campaign: campaignId,
+ cid,
+ url
+ });
+
+ return {
+ id: ids[0],
+ cid
+ };
+ }
+ });
+}
+
+async function updateLinks(campaign, list, subscription, message) {
+ if ((campaign.open_tracking_disabled && campaign.click_tracking_disabled) || !message || !message.trim()) {
+ // tracking is disabled, do not modify the message
+ return message;
+ }
+
+ // insert tracking image
+ if (!campaign.open_tracking_disabled) {
+ let inserted = false;
+ const imgUrl = getTrustedUrl(`/links/${campaign.cid}/${list.cid}/${subscription.cid}`);
+ const img = ' ';
+ message = message.replace(/<\/body\b/i, match => {
+ inserted = true;
+ return img + match;
+ });
+ if (!inserted) {
+ message = message + img;
+ }
+ }
+
+ if (!campaign.click_tracking_disabled) {
+ const re = /(]* href\s*=[\s"']*)(http[^"'>\s]+)/gi;
+
+ const urlsToBeReplaced = new Set();
+
+ message.replace(re, (match, prefix, encodedUrl) => {
+ const url = he.decode(encodedUrl, {isAttributeValue: true});
+ urlsToBeReplaced.add(url);
+ });
+
+ const urls = new Map(); // url -> {id, cid} (as returned by add)
+ for (const url of urlsToBeReplaced) {
+ const link = await addOrGet(campaign.id, url);
+ urls.set(url, link);
+ }
+
+ message.replace(re, (match, prefix, encodedUrl) => {
+ const url = he.decode(encodedUrl, {isAttributeValue: true});
+ const link = urls.get(url);
+ return getTrustedUrl(`/links/${campaign.cid}/${list.cid}/${subscription.cid}/${link.cid}`);
+ });
+ }
+}
+
+
+module.exports.resolve = resolve;
+module.exports.countLink = countLink;
+module.exports.addOrGet = addOrGet;
+module.exports.updateLinks = updateLinks;
\ No newline at end of file
diff --git a/models/lists.js b/models/lists.js
index c748295e..3decf455 100644
--- a/models/lists.js
+++ b/models/lists.js
@@ -14,7 +14,7 @@ const entitySettings = require('../lib/entity-settings');
const UnsubscriptionMode = require('../shared/lists').UnsubscriptionMode;
-const allowedKeys = new Set(['name', 'description', 'default_form', 'public_subscribe', 'unsubscription_mode', 'contact_email', 'homepage', 'namespace']);
+const allowedKeys = new Set(['name', 'description', 'default_form', 'public_subscribe', 'unsubscription_mode', 'contact_email', 'homepage', 'namespace', 'to_name']);
function hash(entity) {
return hasher.hash(filterObject(entity, allowedKeys));
@@ -61,7 +61,7 @@ async function listWithSegmentByCampaignDTAjax(context, campaignId, params) {
);
}
-async function _getByIdTx(tx, context, id) {
+async function getByIdTx(tx, context, id) {
await shares.enforceEntityPermissionTx(tx, context, 'list', id, 'view');
const entity = await tx('lists').where('id', id).first();
return entity;
@@ -70,28 +70,32 @@ async function _getByIdTx(tx, context, id) {
async function getById(context, id) {
return await knex.transaction(async tx => {
// note that permissions are not obtained here as this methods is used only with synthetic admin context
- return await _getByIdTx(tx, context, id);
+ return await getByIdTx(tx, context, id);
});
}
async function getByIdWithListFields(context, id) {
return await knex.transaction(async tx => {
- const entity = await _getByIdTx(tx, context, id);
+ const entity = await getByIdTx(tx, context, id);
entity.permissions = await shares.getPermissionsTx(tx, context, 'list', id);
entity.listFields = await fields.listByOrderListTx(tx, id);
return entity;
});
}
+async function getByCidTx(tx, context, cid) {
+ const entity = await tx('lists').where('cid', cid).first();
+ if (!entity) {
+ shares.throwPermissionDenied();
+ }
+
+ await shares.enforceEntityPermissionTx(tx, context, 'list', entity.id, 'view');
+ return entity;
+}
+
async function getByCid(context, cid) {
return await knex.transaction(async tx => {
- const entity = await tx('lists').where('cid', cid).first();
- if (!entity) {
- shares.throwPermissionDenied();
- }
-
- await shares.enforceEntityPermissionTx(tx, context, 'list', entity.id, 'view');
- return entity;
+ return getByCidTx(tx, context, cid);
});
}
@@ -212,8 +216,10 @@ module.exports.UnsubscriptionMode = UnsubscriptionMode;
module.exports.hash = hash;
module.exports.listDTAjax = listDTAjax;
module.exports.listWithSegmentByCampaignDTAjax = listWithSegmentByCampaignDTAjax;
+module.exports.getByIdTx = getByIdTx;
module.exports.getById = getById;
module.exports.getByIdWithListFields = getByIdWithListFields;
+module.exports.getByCidTx = getByCidTx;
module.exports.getByCid = getByCid;
module.exports.create = create;
module.exports.updateWithConsistencyCheck = updateWithConsistencyCheck;
diff --git a/models/subscriptions.js b/models/subscriptions.js
index d9cc620c..7248481a 100644
--- a/models/subscriptions.js
+++ b/models/subscriptions.js
@@ -194,28 +194,30 @@ async function hashByList(listId, entity) {
});
}
+async function _getByTx(tx, context, listId, key, value, grouped) {
+ await shares.enforceEntityPermissionTx(tx, context, 'list', listId, 'viewSubscriptions');
+
+ const entity = await tx(getSubscriptionTableName(listId)).where(key, value).first();
+
+ if (!entity) {
+ throw new interoperableErrors.NotFoundError('Subscription not found in this list');
+ }
+
+ const groupedFieldsMap = await getGroupedFieldsMap(tx, listId);
+
+ if (grouped) {
+ groupSubscription(groupedFieldsMap, entity);
+ }
+
+ return entity;
+}
async function _getBy(context, listId, key, value, grouped) {
return await knex.transaction(async tx => {
- await shares.enforceEntityPermissionTx(tx, context, 'list', listId, 'viewSubscriptions');
-
- const entity = await tx(getSubscriptionTableName(listId)).where(key, value).first();
-
- if (!entity) {
- throw new interoperableErrors.NotFoundError('Subscription not found in this list');
- }
-
- const groupedFieldsMap = await getGroupedFieldsMap(tx, listId);
-
- if (grouped) {
- groupSubscription(groupedFieldsMap, entity);
- }
-
- return entity;
+ return _getByTx(tx, context, listId, key, value, grouped);
});
}
-
async function getById(context, listId, id, grouped = true) {
return await _getBy(context, listId, 'id', id, grouped);
}
@@ -228,6 +230,10 @@ async function getByCid(context, listId, cid, grouped = true) {
return await _getBy(context, listId, 'cid', cid, grouped);
}
+async function getByCidTx(tx, context, listId, cid, grouped = true) {
+ return await _getByTx(tx, context, listId, 'cid', cid, grouped);
+}
+
async function listDTAjax(context, listId, segmentId, params) {
return await knex.transaction(async tx => {
await shares.enforceEntityPermissionTx(tx, context, 'list', listId, 'viewSubscriptions');
@@ -725,6 +731,7 @@ async function getListsWithEmail(context, email) {
module.exports.getSubscriptionTableName = getSubscriptionTableName;
module.exports.hashByList = hashByList;
module.exports.getById = getById;
+module.exports.getByCidTx = getByCidTx;
module.exports.getByCid = getByCid;
module.exports.getByEmail = getByEmail;
module.exports.list = list;
diff --git a/models/users.js b/models/users.js
index 91605199..20f4c234 100644
--- a/models/users.js
+++ b/models/users.js
@@ -303,7 +303,7 @@ async function sendPasswordReset(usernameOrEmail) {
const { adminEmail } = await settings.get(contextHelpers.getAdminContext(), ['adminEmail']);
const mailer = await mailers.getOrCreateMailer();
- await mailer.sendMail({
+ await mailer.sendTransactionalMail({
from: {
address: adminEmail
},
diff --git a/obsolete/lib/models/forms.js b/obsolete/lib/models/forms.js
index 9550077b..f5d65e04 100644
--- a/obsolete/lib/models/forms.js
+++ b/obsolete/lib/models/forms.js
@@ -339,7 +339,7 @@ function setDefaultValues(form) {
});
form.layout = getContents('views/subscription/layout.mjml.hbs') || '';
- form.formInputStyle = getContents('public/subscription/form-input-style.css') || '@import url(/subscription/form-input-style.css);';
+ form.formInputStyle = getContents('static/subscription/form-input-style.css') || '@import url(/subscription/form-input-style.css);';
return form;
}
diff --git a/routes/index.js b/routes/index.js
index 9583e4bb..8166f852 100644
--- a/routes/index.js
+++ b/routes/index.js
@@ -4,15 +4,16 @@ const passport = require('../lib/passport');
const _ = require('../lib/translate')._;
const clientHelpers = require('../lib/client-helpers');
const { getTrustedUrl } = require('../lib/urls');
+const { AppType } = require('../shared/app');
const routerFactory = require('../lib/router-async');
-function getRouter(trusted) {
+function getRouter(appType) {
const router = routerFactory.create();
- if (trusted) {
+ if (appType === AppType.TRUSTED) {
router.getAsync('/*', passport.csrfProtection, async (req, res) => {
- const mailtrainConfig = await clientHelpers.getAnonymousConfig(req.context, trusted);
+ const mailtrainConfig = await clientHelpers.getAnonymousConfig(req.context, appType);
if (req.user) {
Object.assign(mailtrainConfig, await clientHelpers.getAuthenticatedConfig(req.context));
}
@@ -32,6 +33,4 @@ function getRouter(trusted) {
}
-module.exports = {
- getRouter
-};
\ No newline at end of file
+module.exports.getRouter = getRouter;
diff --git a/routes/mosaico.js b/routes/mosaico.js
index 07bc4980..e3ccae72 100644
--- a/routes/mosaico.js
+++ b/routes/mosaico.js
@@ -25,6 +25,7 @@ const interoperableErrors = require('../shared/interoperable-errors');
const { getTrustedUrl, getSandboxUrl } = require('../lib/urls');
const { base } = require('../shared/templates');
+const { AppType } = require('../shared/app');
users.registerRestrictedAccessTokenMethod('mosaico', async ({entityTypeId, entityId}) => {
@@ -123,10 +124,10 @@ function sanitizeSize(val, min, max, defaultVal, allowNull) {
-function getRouter(trusted) {
+function getRouter(appType) {
const router = routerFactory.create();
- if (!trusted) {
+ if (appType === AppType.SANDBOXED) {
router.getAsync('/templates/:mosaicoTemplateId/index.html', passport.loggedIn, async (req, res) => {
const tmpl = await mosaicoTemplates.getById(req.context, req.params.mosaicoTemplateId);
@@ -174,7 +175,7 @@ function getRouter(trusted) {
});
router.getAsync('/editor', passport.csrfProtection, async (req, res) => {
- const mailtrainConfig = await clientHelpers.getAnonymousConfig(req.context, trusted);
+ const mailtrainConfig = await clientHelpers.getAnonymousConfig(req.context, appType);
let languageStrings = null;
if (config.language && config.language !== 'en') {
@@ -196,7 +197,7 @@ function getRouter(trusted) {
getSandboxUrl('mailtrain/common.js'),
getSandboxUrl('mailtrain/mosaico.js')
],
- mosaicoPublicPath: getSandboxUrl('public/mosaico')
+ mosaicoPublicPath: getSandboxUrl('static/mosaico')
});
});
@@ -240,6 +241,4 @@ function getRouter(trusted) {
return router;
}
-module.exports = {
- getRouter
-};
+module.exports.getRouter = getRouter;
diff --git a/routes/subscription.js b/routes/subscription.js
index 17465bef..12309ac5 100644
--- a/routes/subscription.js
+++ b/routes/subscription.js
@@ -78,7 +78,7 @@ async function injectCustomFormData(customFormId, viewKey, data) {
}
if (!customFormId) {
- data.formInputStyle = '@import url(/public/subscription/form-input-style.css);';
+ data.formInputStyle = '@import url(/static/subscription/form-input-style.css);';
return;
}
@@ -86,7 +86,7 @@ async function injectCustomFormData(customFormId, viewKey, data) {
data.template.template = form[viewKey] || data.template.template;
data.template.layout = form.layout || data.template.layout;
- data.formInputStyle = form.formInputStyle || '@import url(/public/subscription/form-input-style.css);';
+ data.formInputStyle = form.formInputStyle || '@import url(/static/subscription/form-input-style.css);';
const configItems = await settings.get(contextHelpers.getAdminContext(), ['uaCode']);
diff --git a/services/sender-master.js b/services/sender-master.js
index cb7ad19d..c17a959e 100644
--- a/services/sender-master.js
+++ b/services/sender-master.js
@@ -8,9 +8,6 @@ const knex = require('../lib/knex');
const {CampaignStatus, CampaignType} = require('../shared/campaigns');
const { enforce } = require('../lib/helpers');
const campaigns = require('../models/campaigns');
-const subscriptions = require('../models/subscriptions');
-const { SubscriptionStatus } = require('../shared/lists');
-const segments = require('../models/segments');
let messageTid = 0;
const workerProcesses = new Map();
@@ -72,14 +69,17 @@ async function scheduleWorkers() {
const queue = messageQueue.get(campaignId);
if (queue.length > 0) {
- const msgs = queue.splice(0, workerBatchSize);
+ const subscribers = queue.splice(0, workerBatchSize);
if (queue.length === 0 && messageQueueCont.has(campaignId)) {
const scheduleMessages = messageQueueCont.get(campaignId);
setImmediate(scheduleMessages);
}
- sendToWorker(workerId, 'process-messages', msgs);
+ sendToWorker(workerId, 'process-messages', {
+ campaignId,
+ subscribers
+ });
workerId = await getAvailableWorker();
keepLooping = true;
diff --git a/services/sender-worker.js b/services/sender-worker.js
index 08e422ff..49d3dff0 100644
--- a/services/sender-worker.js
+++ b/services/sender-worker.js
@@ -1,7 +1,26 @@
'use strict';
+const config = require('config');
const log = require('npmlog');
const mailers = require('../lib/mailers');
+const knex = require('../lib/knex');
+const subscriptions = require('../models/subscriptions');
+const contextHelpers = require('../lib/context-helpers');
+const campaigns = require('../models/campaigns');
+const templates = require('../models/templates');
+const lists = require('../models/lists');
+const fields = require('../models/fields');
+const sendConfigurations = require('../models/send-configurations');
+const links = require('../models/links');
+const {CampaignSource} = require('../shared/campaigns');
+const {SubscriptionStatus} = require('../shared/lists');
+const tools = require('../lib/tools');
+const request = require('request-promise');
+const files = require('../models/files');
+const htmlToText = require('html-to-text');
+const {getPublicUrl} = require('../lib/urls');
+const blacklist = require('../models/blacklist');
+let libmime = require('libmime');
const workerId = Number.parseInt(process.argv[2]);
let running = false;
@@ -14,16 +33,232 @@ const fsExtra = require('fs-extra-promise');
const {ImportSource, MappingType, ImportStatus, RunStatus} = require('../shared/imports');
const imports = require('../models/imports');
const fields = require('../models/fields');
-const subscriptions = require('../models/subscriptions');
const { Writable } = require('stream');
const { cleanupFromPost, enforce } = require('../lib/helpers');
-const contextHelpers = require('../lib/context-helpers');
const tools = require('../lib/tools');
const shares = require('../models/shares');
const _ = require('../lib/translate')._;
*/
-async function processMessages(msgs) {
+class CampaignSender {
+ constructor(campaignId) {
+ this.campaignId = campaignId;
+ }
+
+ async init() {
+ this.lists = Map(); // listId -> list
+ this.listsFieldsGrouped = Map(); // listId -> fieldsGrouped
+ this.attachments = [];
+
+ await knex.transaction(async tx => {
+ this.campaign = await campaigns.getByIdTx(tx, contextHelpers.getAdminContext(), this.campaignId, false, campaigns.Content.ALL)
+ this.sendConfiguration = await sendConfigurations.getByIdTx(tx, contextHelpers.getAdminContext(), campaign.send_configuration);
+
+ for (const listSpec of campaign.lists) {
+ this.lists.set(listSpec.list) = await lists.getByIdTx(tx, contextHelpers.getAdminContext(), listSpec.list);
+ this.listsFieldsGrouped.set(listSpec.list) = await fields.listGroupedTx(tx, listSpec.list);
+ }
+
+ if (campaign.source === CampaignSource.TEMPLATE) {
+ this.template = templates.getByIdTx(tx, contextHelpers.getAdminContext(), this.campaign.data.sourceTemplate, false);
+ }
+
+ const attachments = await files.listTx(tx, contextHelpers.getAdminContext(), 'campaign', 'attachment', this.campaignId);
+ for (const attachment of attachments) {
+ this.attachments.push({
+ filename: attachment.originalname,
+ path: files.getFilePath('campaign', 'attachment', this.campaignId, attachment.filename)
+ });
+ }
+
+ });
+
+ this.useVerp = config.verp.enabled && sendConfiguration.verp_hostname;
+ this.useVerpSenderHeader = useVerp && config.verp.disablesenderheader !== true;
+ }
+
+ async sendMessage(listId, email) {
+ if (await blacklist.isBlacklisted(email)) {
+ return;
+ }
+
+ const subscriptionGrouped = await subscriptions.getByEmail(contextHelpers.getAdminContext(), listId, email);
+ console.log(subscriptionGrouped);
+
+ const flds = this.listsFieldsGrouped.get(listId);
+ const campaign = this.campaign;
+ const list = this.lists.get(listId);
+ const sendConfiguration = this.sendConfiguration;
+
+ const encryptionKeys = [];
+ const mergeTags = fields.forHbsWithFieldsGrouped(flds, subscriptionGrouped);
+ for (const fld of flds) {
+ if (fld.type === 'gpg' && mergeTags[fld.key]) {
+ encryptionKeys.push(mergeTags[fld.key].trim());
+ }
+ }
+
+ let html = '';
+ let text = '';
+ let renderTags = false;
+
+ if (campaign.source === CampaignSource.URL) {
+ const form = tools.getMessageLinks(campaign, list, subscriptionGrouped);
+ for (const key in mergeTags) {
+ form[key] = mergeTags[key];
+ }
+
+ const response = await request.post({
+ uri: campaign.sourceUrl,
+ form,
+ resolveWithFullResponse: true
+ });
+
+ if (response.statusCode !== 200) {
+ throw new Error(`Received status code ${httpResponse.statusCode} from ${campaign.sourceUrl}`);
+ }
+
+ html = response.body;
+ text = '';
+ renderTags = false;
+
+ } else if (campaign.source === CampaignSource.CUSTOM || campaign.source === CampaignSource.CUSTOM_FROM_CAMPAIGN || campaign.source === CampaignSource.CUSTOM_FROM_TEMPLATE) {
+ html = campaign.data.sourceCustom.html;
+ text = campaign.data.sourceCustom.text;
+ renderTags = true;
+
+ } else if (campaign.source === CampaignSource.TEMPLATE) {
+ const template = this.template;
+ html = template.html;
+ text = template.text;
+ renderTags = true;
+ }
+
+ html = await links.updateLinks(campaign, list, subscriptionGrouped, html);
+
+ const attachments = this.attachments.slice();
+ // replace data: images with embedded attachments
+ html = html.replace(/( ]* src\s*=[\s"']*)(data:[^"'>\s]+)/gi, (match, prefix, dataUri) => {
+ let cid = shortid.generate() + '-attachments@' + campaign.address.split('@').pop();
+ attachments.push({
+ path: dataUri,
+ cid
+ });
+ return prefix + 'cid:' + cid;
+ });
+
+ const campaignAddress = [campaign.cid, list.cid, subscriptionGrouped.cid].join('.');
+
+ const renderedHtml = renderTags ? tools.formatMessage(campaign, list, subscriptionGrouped, mergeTags, html, false, true) : html;
+
+ const renderedText = (text || '').trim()
+ ? (renderTags ? tools.formatMessage(campaign, list, subscriptionGrouped, mergeTags, text) : text)
+ : htmlToText.fromString(renderedHtml, {wordwrap: 130});
+
+ let listUnsubscribe = null;
+ if (!list.listunsubscribe_disabled) {
+ listUnsubscribe = campaign.unsubscribe_url
+ ? tools.formatMessage(campaign, list, subscriptionGrouped, mergeTags, campaign.unsubscribe_url)
+ : getPublicUrl('/subscription/' + list.cid + '/unsubscribe/' + subscriptionGrouped.subscription.cid);
+ }
+
+ const mailer = await mailers.getOrCreateMailer(sendConfiguration.id);
+
+ await mailer.throttleWait();
+
+ const getOverridable = key => {
+ if (sendConfiguration[key + '_overridable'] && this.campaign[key + '_override'] !== null) {
+ return campaign[key + '_override'];
+ } else {
+ return sendConfiguration[key];
+ }
+ }
+
+ const mail = {
+ from: {
+ name: getOverridable('from_name'),
+ address: getOverridable('from_email')
+ },
+ replyTo: getOverridable('reply_to'),
+ xMailer: sendConfiguration.x_mailer ? sendConfiguration.x_mailer : false,
+ to: {
+ name: tools.formatMessage(campaign, list, subscriptionGrouped, mergeTags, list.to_name, false, false),
+ address: subscriptionGrouped.email
+ },
+ sender: this.useVerpSenderHeader ? campaignAddress + '@' + sendConfiguration.verp_hostname : false,
+
+ envelope: this.useVerp ? {
+ from: campaignAddress + '@' + sendConfiguration.verp_hostname,
+ to: subscriptionGrouped.email
+ } : false,
+
+ headers: {
+ 'x-fbl': campaignAddress,
+ // custom header for SparkPost
+ 'x-msys-api': JSON.stringify({
+ campaign_id: campaignAddress
+ }),
+ // custom header for SendGrid
+ 'x-smtpapi': JSON.stringify({
+ unique_args: {
+ campaign_id: campaignAddress
+ }
+ }),
+ // custom header for Mailgun
+ 'x-mailgun-variables': JSON.stringify({
+ campaign_id: campaignAddress
+ }),
+ 'List-ID': {
+ prepared: true,
+ value: libmime.encodeWords(list.name) + ' <' + list.cid + '.' + getPublicUrl() + '>'
+ }
+ },
+ list: {
+ unsubscribe: listUnsubscribe
+ },
+ subject: tools.formatMessage(campaign, list, subscriptionGrouped, mergeTags, getOverridable('subject'), false, false),
+ html: renderedHtml,
+ text: renderedText,
+
+ attachments,
+ encryptionKeys
+ };
+
+
+ let status;
+ let response;
+ try {
+ const info = await mailer.sendMassMail(mail);
+ status = SubscriptionStatus.SUBSCRIBED;
+ response = info.response || info.messageId;
+
+ await knex('campaigns').where('id', campaign.id).increment('delivered');
+ } catch (err) {
+ status = SubscriptionStatus.BOUNCED;
+ response = err.response || err.message;
+ await knex('campaigns').where('id', campaign.id).increment('delivered').increment('bounced');
+ }
+
+ const responseId = response.split(/\s+/).pop();
+
+ const now = new Date();
+ await knex('campaign_messages').insert({
+ campaign: this.campaignId,
+ list: listId,
+ subscriptions: subscriptionGrouped.id,
+ send_configuration: sendConfiguration.id,
+ status,
+ response,
+ response_id: responseId,
+ updated: now
+ });
+
+ log.verbose('Senders', 'Message sent and status updated for %s', subscriptionGrouped.cid);
+ }
+}
+
+
+async function processMessages(campaignId, subscribers) {
if (running) {
log.error('Senders', `Worker ${workerId} assigned work while working`);
return;
@@ -31,8 +266,16 @@ async function processMessages(msgs) {
running = true;
- console.log(msgs);
- // FIXME
+ const cs = new CampaignSender(campaignId);
+ await cs.init()
+
+ for (const subData of subscribers) {
+ try {
+ await cs.sendMessage(subData.listId, subData.email);
+ } catch (err) {
+ log.error('Senders', `Sending message to ${subData.listId}:${subData.email} failed with error: ${err.message}`)
+ }
+ }
running = false;
@@ -54,7 +297,7 @@ process.on('message', msg => {
} else if (type === 'process-messages') {
// noinspection JSIgnoredPromiseFromCall
- processMessages(msg.data)
+ processMessages(msg.data.campaignId, msg.data.subscribers)
}
}
diff --git a/setup/knex/migrations/20170506102634_v1_to_v2.js b/setup/knex/migrations/20170506102634_v1_to_v2.js
index ed459b0b..fbc2af07 100644
--- a/setup/knex/migrations/20170506102634_v1_to_v2.js
+++ b/setup/knex/migrations/20170506102634_v1_to_v2.js
@@ -670,6 +670,7 @@ async function migrateSettings(knex) {
await knex.schema.table('lists', table => {
table.string('contact_email');
table.string('homepage');
+ table.string('to_name');
table.integer('send_configuration').unsigned().references(`send_configurations.id`);
});
@@ -679,8 +680,11 @@ async function migrateSettings(knex) {
settings[row.key] = row.value;
}
- await knex('lists').update({contact_email: settings.defaultAddress});
- await knex('lists').update({homepage: settings.defaultHomepage});
+ await knex('lists').update({
+ contact_email: settings.defaultAddress,
+ homepage: settings.defaultHomepage,
+ to_name: '[FIRST_NAME] [LAST_NAME]'
+ });
let mailer_settings;
let mailer_type;
@@ -971,7 +975,6 @@ async function migrateCampaigns(knex) {
data: editorData,
html: campaign.html,
text: campaign.text,
- htmlPrepared: campaign.html_prepared
};
data.sourceTemplate = campaign.template;
diff --git a/shared/app.js b/shared/app.js
new file mode 100644
index 00000000..4044bf80
--- /dev/null
+++ b/shared/app.js
@@ -0,0 +1,9 @@
+'use strict';
+
+const AppType = {
+ TRUSTED: 0,
+ SANDBOXED: 1,
+ PUBLIC: 2
+};
+
+module.exports.AppType = AppType;
diff --git a/shared/mosaico-templates.js b/shared/mosaico-templates.js
index 722a8df2..41e0df7b 100644
--- a/shared/mosaico-templates.js
+++ b/shared/mosaico-templates.js
@@ -1397,78 +1397,78 @@ const versafix = '\n' +
' \n' +
' \n' +
- ' \n' +
+ ' \n' +
' \n' +
' \n' +
' \n' +
- ' \n' +
+ ' \n' +
' \n' +
' \n' +
' \n' +
- ' \n' +
+ ' \n' +
' \n' +
' \n' +
' \n' +
- ' \n' +
+ ' \n' +
' \n' +
' \n' +
' \n' +
- ' \n' +
+ ' \n' +
' \n' +
' \n' +
' \n' +
- ' \n' +
+ ' \n' +
' \n' +
' \n' +
' \n' +
- ' \n' +
+ ' \n' +
' \n' +
' \n' +
' \n' +
- ' \n' +
+ ' \n' +
' \n' +
' \n' +
' \n' +
- ' \n' +
+ ' \n' +
' \n' +
' \n' +
' \n' +
' \n' +
' \n' +
- ' \n' +
+ ' \n' +
' \n' +
' \n' +
' \n' +
- ' \n' +
+ ' \n' +
' \n' +
' \n' +
' \n' +
- ' \n' +
+ ' \n' +
' \n' +
' \n' +
' \n' +
- ' \n' +
+ ' \n' +
' \n' +
' \n' +
' \n' +
- ' \n' +
+ ' \n' +
' \n' +
' \n' +
' \n' +
- ' \n' +
+ ' \n' +
' \n' +
' \n' +
' \n' +
- ' \n' +
+ ' \n' +
' \n' +
' \n' +
' \n' +
- ' \n' +
+ ' \n' +
' \n' +
' \n' +
' \n' +
- ' \n' +
+ ' \n' +
' \n' +
' | \n' +
' |
\n' +
@@ -1516,7 +1516,7 @@ const versafix = '\n' +
'
\n' +
- '  \n' +
' | \n' +
' \n' +
diff --git a/shared/templates.js b/shared/templates.js
index 069d60fe..181fbf7d 100644
--- a/shared/templates.js
+++ b/shared/templates.js
@@ -1,7 +1,5 @@
'use strict';
-// FIXME - process also urlencoded strings - this is for the mosaico/img/template, which passes the file in src parameter
-
function base(text, trustedBaseUrl, sandboxBaseUrl) {
if (trustedBaseUrl.endsWith('/')) {
trustedBaseUrl = trustedBaseUrl.substring(0, trustedBaseUrl.length - 1);
diff --git a/test/e2e/lib/config.js b/test/e2e/lib/config.js
index 2fa99a3e..32f07a16 100644
--- a/test/e2e/lib/config.js
+++ b/test/e2e/lib/config.js
@@ -71,7 +71,7 @@ module.exports = {
}
},
settings: {
- 'service-url': 'http://localhost:' + config.www.port + '/',
+ 'service-url': 'http://localhost:' + config.www.publicPort + '/',
'admin-email': 'keep.admin@mailtrain.org',
'default-homepage': 'https://mailtrain.org',
'smtp-hostname': config.testserver.host,
diff --git a/views/layout.hbs b/views/layout.hbs
index 57a9005a..9ab2ce9d 100644
--- a/views/layout.hbs
+++ b/views/layout.hbs
@@ -7,17 +7,17 @@
-
+
Mailtrain
{{#if title}} | {{title}}{{/if}}
-
-
-
-
-
+
+
+
+
+
{{#if mailtrainConfig}}