From 30b361290b92c09d7c258345a5042faf5e2a015a Mon Sep 17 00:00:00 2001 From: Tomas Bures Date: Tue, 25 Jun 2019 07:18:06 +0200 Subject: [PATCH] - Refactoring of the mail sending part. Mail queue (table 'queued') is now used also for all test emails. - More options how to send test emails. - Fixed problems with pausing a campaign (#593) - Started rework of transactional sender of templates (#606), however this contains functionality regression at the moment because it does not interpret templates as HBS. It needs HBS option for templates as described in https://github.com/Mailtrain-org/mailtrain/issues/611#issuecomment-502345227 TODO: - detect sending errors connected to not able to contact the mailer and pause/retry campaing and queued sending - don't mark the recipients as BOUNCED - add FAILED campaign state and fall into it if sending to campaign consistently fails (i.e. the error with sending is not temporary) - if the same happends for queued email, delete the message --- client/src/account/API.js | 17 +- client/src/campaigns/CUD.js | 20 +- client/src/campaigns/Content.js | 7 +- client/src/campaigns/Status.js | 105 ++-- client/src/campaigns/TestSendModalDialog.js | 283 +++++++++-- client/src/campaigns/helpers.js | 3 +- client/src/lib/form.js | 7 + client/src/send-configurations/CUD.js | 6 +- client/src/send-configurations/helpers.js | 2 +- client/src/templates/CUD.js | 3 +- client/src/templates/TestSendModalDialog.js | 149 ------ locales/en-US/common.json | 4 +- server/config/default.yaml | 22 +- server/lib/campaign-sender.js | 448 ++++++++++-------- server/lib/dependency-helpers.js | 3 +- server/lib/entity-settings.js | 1 + server/lib/helpers.js | 4 +- server/lib/mailers.js | 15 +- server/lib/subscription-mail-helpers.js | 2 +- server/lib/template-sender.js | 87 ---- server/models/blacklist.js | 17 +- server/models/campaigns.js | 145 +++++- server/models/files.js | 59 ++- server/models/links.js | 34 +- server/models/send-configurations.js | 4 +- server/models/subscriptions.js | 29 ++ server/models/templates.js | 68 ++- server/models/triggers.js | 2 +- server/models/users.js | 2 +- server/routes/api.js | 54 +-- server/routes/archive.js | 4 +- server/routes/rest/campaigns.js | 6 + server/routes/rest/templates.js | 7 - server/services/feedcheck.js | 2 +- server/services/sender-master.js | 362 +++++++++----- server/services/sender-worker.js | 57 ++- server/services/triggers.js | 14 +- .../migrations/20170506102634_v1_to_v2.js | 12 +- ...neralization_of_queued_and_file_locking.js | 63 +++ ...000_drop_subject_in_send_configurations.js | 15 + shared/activity-log.js | 3 +- shared/campaigns.js | 5 +- 42 files changed, 1366 insertions(+), 786 deletions(-) delete mode 100644 client/src/templates/TestSendModalDialog.js delete mode 100644 server/lib/template-sender.js create mode 100644 server/setup/knex/migrations/20190615000000_generalization_of_queued_and_file_locking.js create mode 100644 server/setup/knex/migrations/20190616000000_drop_subject_in_send_configurations.js diff --git a/client/src/account/API.js b/client/src/account/API.js index beb3f2bc..d3a07a4b 100644 --- a/client/src/account/API.js +++ b/client/src/account/API.js @@ -138,7 +138,7 @@ export default class API extends Component { {t('example')}

-
curl -XPOST '{getUrl(`api/subscribe/B16uVTdW?access_token=${accessToken}`)}' \
+                
curl -XPOST '{getUrl(`api/subscribe/B16uVTdW?access_token=${accessToken}`)}' \
--data 'EMAIL=test@example.com&MERGE_CHECKBOX=yes&REQUIRE_CONFIRMATION=yes'

POST /api/unsubscribe/:listId – {t('removeSubscription')}

@@ -165,7 +165,7 @@ export default class API extends Component { {t('example')}

-
curl -XPOST '{getUrl(`api/unsubscribe/B16uVTdW?access_token=${accessToken}`)}' \
+                
curl -XPOST '{getUrl(`api/unsubscribe/B16uVTdW?access_token=${accessToken}`)}' \
--data 'EMAIL=test@example.com'

POST /api/delete/:listId – {t('deleteSubscription')}

@@ -192,7 +192,7 @@ export default class API extends Component { {t('example')}

-
curl -XPOST '{getUrl(`api/delete/B16uVTdW?access_token=${accessToken}`)}' \
+                
curl -XPOST '{getUrl(`api/delete/B16uVTdW?access_token=${accessToken}`)}' \
--data 'EMAIL=test@example.com'

POST /api/field/:listId – {t('addNewCustomField')}

@@ -240,7 +240,7 @@ export default class API extends Component { {t('example')}

-
curl -XPOST '{getUrl(`api/field/B16uVTdW?access_token=${accessToken}`)}' \
+                
curl -XPOST '{getUrl(`api/field/B16uVTdW?access_token=${accessToken}`)}' \
--data 'NAME=Birthday&TYPE=birthday-us&VISIBLE=yes'

GET /api/blacklist/get – {t('getListOfBlacklistedEmails')}

@@ -292,7 +292,7 @@ export default class API extends Component { {t('example')}

-
curl -XPOST '{getUrl(`api/blacklist/add?access_token={accessToken}`)}' \
+                
curl -XPOST '{getUrl(`api/blacklist/add?access_token={accessToken}`)}' \
--data 'EMAIL=test@example.com&'

POST /api/blacklist/delete – {t('deleteEmailFromBlacklist')}

@@ -319,7 +319,7 @@ export default class API extends Component { {t('example')}

-
curl -XPOST '{getUrl(`api/blacklist/delete?access_token=${accessToken}`)}' \
+                
curl -XPOST '{getUrl(`api/blacklist/delete?access_token=${accessToken}`)}' \
--data 'EMAIL=test@example.com&'

GET /api/lists/:email – {t('getTheListsAUserHasSubscribedTo')}

@@ -381,15 +381,14 @@ export default class API extends Component {
  • EMAIL – {t('emailAddress')} ({t('required')})
  • SEND_CONFIGURATION_ID – {t('idOfConfigurationUsedToCreateMailer')}
  • SUBJECT – {t('subject')}
  • -
  • DATA – {t('dataPassedToTemplateWhenCompilingWith')}: {'{'} "any": ["type", {'{'}"of": "data"{'}'}] {'}'}
  • -
  • VARIABLES – {t('mapOfTemplatesubjectVariablesToReplace')}: {'{'} "FOO": "bar" {'}'}
  • +
  • VARIABLES – {t('mapOfTemplatesubjectVariablesToReplace')}
  • {t('example')}

    -
    curl -XPOST '{getUrl(`api/templates/1/send?access_token={accessToken}`)}' \
    +                
    curl -XPOST '{getUrl(`api/templates/1/send?access_token=${accessToken}`)}' \
    --data 'EMAIL=test@example.com&SUBJECT=Test&VARIABLES[FOO]=bar&VARIABLES[TEST]=example'
    ); diff --git a/client/src/campaigns/CUD.js b/client/src/campaigns/CUD.js index 74585082..37a27284 100644 --- a/client/src/campaigns/CUD.js +++ b/client/src/campaigns/CUD.js @@ -244,7 +244,7 @@ export default class CUD extends Component { return filterData(data, [ 'name', 'description', 'segment', 'namespace', 'send_configuration', - 'from_name_override', 'from_email_override', 'reply_to_override', 'subject_override', + 'from_name_override', 'from_email_override', 'reply_to_override', 'data', 'click_tracking_disabled', 'open_tracking_disabled', 'unsubscribe_url', 'type', 'source', 'parent', 'lists' ]); @@ -284,6 +284,8 @@ export default class CUD extends Component { send_configuration: null, namespace: mailtrainConfig.user.namespace, + subject: '', + click_tracking_disabled: false, open_tracking_disabled: false, @@ -326,6 +328,10 @@ export default class CUD extends Component { state.setIn(['name', 'error'], t('nameMustNotBeEmpty')); } + if (!state.getIn(['subject', 'value'])) { + state.setIn(['subject', 'error'], t('"Subject" line must not be empty"')); + } + if (!state.getIn(['send_configuration', 'value'])) { state.setIn(['send_configuration', 'error'], t('sendConfigurationMustBeSelected')); } @@ -592,7 +598,6 @@ export default class CUD extends Component { { data: 2, title: t('id'), render: data => {data} }, { data: 3, title: t('description') }, { data: 4, title: t('type'), render: data => this.mailerTypes[data].typeName }, - { data: 5, title: t('created'), render: data => moment(data).fromNow() }, { data: 6, title: t('namespace') } ]; @@ -604,10 +609,10 @@ export default class CUD extends Component { const addOverridable = (id, label) => { if(this.state.sendConfiguration[id + '_overridable']){ if (this.getFormValue(id + '_overriden')) { - sendSettings.push(); + sendSettings.push(); } else { sendSettings.push( - + {this.state.sendConfiguration[id]} ); @@ -616,7 +621,7 @@ export default class CUD extends Component { } else{ sendSettings.push( - + {this.state.sendConfiguration[id]} ); @@ -626,7 +631,8 @@ export default class CUD extends Component { addOverridable('from_name', t('fromName')); addOverridable('from_email', t('fromEmailAddress')); addOverridable('reply_to', t('replytoEmailAddress')); - addOverridable('subject', t('subjectLine')); + + sendSettings.push(); } else { sendSettings = {t('loadingSendConfiguration')} } @@ -760,8 +766,6 @@ export default class CUD extends Component { } - - {templateEdit} diff --git a/client/src/campaigns/Content.js b/client/src/campaigns/Content.js index c1e42816..0a2da44c 100644 --- a/client/src/campaigns/Content.js +++ b/client/src/campaigns/Content.js @@ -20,7 +20,7 @@ import {getEditForm, getTemplateTypes, getTypeForm, ResourceType} from '../templ import axios from '../lib/axios'; import styles from "../lib/styles.scss"; import {getUrl} from "../lib/urls"; -import {TestSendModalDialog} from "./TestSendModalDialog"; +import {TestSendModalDialog, TestSendModalDialogMode} from "./TestSendModalDialog"; import {withComponentMixins} from "../lib/decorator-helpers"; import {ContentModalDialog} from "../lib/modals"; @@ -234,10 +234,11 @@ export default class CustomContent extends Component { return (
    this.setState({showTestSendModal: false})} getDataAsync={this.sendModalGetDataHandler} - entity={this.props.entity} + campaign={this.props.entity} />
    diff --git a/client/src/campaigns/Status.js b/client/src/campaigns/Status.js index 10d3bf34..5f70fc7f 100644 --- a/client/src/campaigns/Status.js +++ b/client/src/campaigns/Status.js @@ -16,6 +16,7 @@ import {CampaignStatus, CampaignType} from "../../../shared/campaigns"; import moment from 'moment'; import campaignsStyles from "./styles.scss"; import {withComponentMixins} from "../lib/decorator-helpers"; +import {TestSendModalDialog, TestSendModalDialogMode} from "./TestSendModalDialog"; @withComponentMixins([ @@ -25,7 +26,7 @@ import {withComponentMixins} from "../lib/decorator-helpers"; withPageHelpers, requiresAuthenticatedUser ]) -class TestUser extends Component { +class PreviewForTestUserModalDialog extends Component { constructor(props) { super(props); this.initForm({ @@ -34,7 +35,9 @@ class TestUser extends Component { } static propTypes = { - entity: PropTypes.object.isRequired + visible: PropTypes.bool.isRequired, + onHide: PropTypes.func.isRequired, + entity: PropTypes.object.isRequired, } localValidateFormValues(state) { @@ -64,6 +67,10 @@ class TestUser extends Component { } } + async hideModal() { + this.props.onHide(); + } + render() { const t = this.props.t; @@ -76,12 +83,14 @@ class TestUser extends Component { ]; return ( -
    - - -