Support for detecting MTA by its response. Message IDs are reconstructed based on detected MTA.
Bugfixes for AWS. AWS now seems to work.
This commit is contained in:
Tomas Bures 2019-05-20 00:21:03 +02:00
parent bbbe671d59
commit 3c72e778d9
6 changed files with 83 additions and 45 deletions

View file

@ -93,6 +93,14 @@ export default class CUD extends Component {
} }
submitFormValuesMutator(data) { submitFormValuesMutator(data) {
data.seconds = Number.parseInt(data.daysAfter) * 3600 * 24;
if (data.entity === Entity.SUBSCRIPTION) {
data.event = data.subscriptionEvent;
} else if (data.entity === Entity.CAMPAIGN) {
data.event = data.campaignEvent;
}
return filterData(data, ['name', 'description', 'entity', 'event', 'seconds', 'enabled', 'source_campaign']); return filterData(data, ['name', 'description', 'entity', 'event', 'seconds', 'enabled', 'source_campaign']);
} }
@ -157,22 +165,14 @@ export default class CUD extends Component {
this.disableForm(); this.disableForm();
this.setFormStatusMessage('info', t('saving')); this.setFormStatusMessage('info', t('saving'));
const submitResult = await this.validateAndSendFormValuesToURL(sendMethod, url, data => { const submitResult = await this.validateAndSendFormValuesToURL(sendMethod, url);
data.seconds = Number.parseInt(data.daysAfter) * 3600 * 24;
if (data.entity === Entity.SUBSCRIPTION) {
data.event = data.subscriptionEvent;
} else if (data.entity === Entity.CAMPAIGN) {
data.event = data.campaignEvent;
}
});
if (submitResult) { if (submitResult) {
if (this.props.entity) { if (this.props.entity) {
if (submitAndLeave) { if (submitAndLeave) {
this.navigateToWithFlashMessage(`/campaigns/${this.props.campaign.id}/triggers`, 'success', t('triggerUpdated')); this.navigateToWithFlashMessage(`/campaigns/${this.props.campaign.id}/triggers`, 'success', t('triggerUpdated'));
} else { } else {
await this.getFormValuesFromURL(`rest/triggers/${this.props.campaign.id}/${this.props.entity.id}`, ::this.getFormValuesMutator); await this.getFormValuesFromURL(`rest/triggers/${this.props.campaign.id}/${this.props.entity.id}`);
this.enableForm(); this.enableForm();
this.setFormStatusMessage('success', t('triggerUpdated')); this.setFormStatusMessage('success', t('triggerUpdated'));
} }

View file

@ -57,18 +57,21 @@ export default class List extends Component {
segmentId: PropTypes.string segmentId: PropTypes.string
} }
updateSegmentSelection(props) { componentDidMount() {
this.populateFormValues({ this.populateFormValues({
segment: props.segmentId || '' segment: this.props.segmentId || ''
}); });
} }
componentDidMount() {
this.updateSegmentSelection(this.props);
}
componentDidUpdate() { componentDidUpdate() {
this.updateSegmentSelection(this.props); const segmentId = this.props.segmentId || '';
if (this.getFormValue('segment') !== segmentId) {
// Populate is used here because it does not invoke onChange
this.populateFormValues({
segment: segmentId
});
}
} }
render() { render() {

View file

@ -4,7 +4,16 @@ import React, {Component} from 'react';
import {withTranslation} from '../lib/i18n'; import {withTranslation} from '../lib/i18n';
import {Title, withPageHelpers} from '../lib/page' import {Title, withPageHelpers} from '../lib/page'
import {Link} from 'react-router-dom' import {Link} from 'react-router-dom'
import {Button, ButtonRow, Form, FormSendMethod, InputField, withForm, withFormErrorHandlers} from '../lib/form'; import {
Button,
ButtonRow,
filterData,
Form,
FormSendMethod,
InputField,
withForm,
withFormErrorHandlers
} from '../lib/form';
import {withAsyncErrorHandler, withErrorHandling} from '../lib/error-handling'; import {withAsyncErrorHandler, withErrorHandling} from '../lib/error-handling';
import passwordValidator from '../../../shared/password-validator'; import passwordValidator from '../../../shared/password-validator';
import axios from '../lib/axios'; import axios from '../lib/axios';
@ -39,6 +48,10 @@ export default class Account extends Component {
}); });
} }
submitFormValuesMutator(data) {
return filterData(data, ['password']);
}
@withAsyncErrorHandler @withAsyncErrorHandler
async validateResetToken() { async validateResetToken() {
const params = this.props.match.params; const params = this.props.match.params;
@ -96,9 +109,7 @@ export default class Account extends Component {
this.disableForm(); this.disableForm();
this.setFormStatusMessage('info', t('resettingPassword')); this.setFormStatusMessage('info', t('resettingPassword'));
const submitSuccessful = await this.validateAndSendFormValuesToURL(FormSendMethod.POST, 'rest/password-reset', data => { const submitSuccessful = await this.validateAndSendFormValuesToURL(FormSendMethod.POST, 'rest/password-reset');
delete data.password2;
});
if (submitSuccessful) { if (submitSuccessful) {
this.navigateToWithFlashMessage('/login', 'success', t('passwordReset-1')); this.navigateToWithFlashMessage('/login', 'success', t('passwordReset-1'));

View file

@ -78,6 +78,12 @@ export default class CUD extends Component {
} }
submitFormValuesMutator(data) { submitFormValuesMutator(data) {
this.mailerTypes[data.mailer_type].beforeSave(data);
if (!data.verpEnabled) {
data.verp_hostname = null;
data.verp_disable_sender_header = false;
}
return filterData(data, ['name', 'description', 'from_email', 'from_email_overridable', 'from_name', return filterData(data, ['name', 'description', 'from_email', 'from_email_overridable', 'from_name',
'from_name_overridable', 'reply_to', 'reply_to_overridable', 'subject', 'subject_overridable', 'x_mailer', 'from_name_overridable', 'reply_to', 'reply_to_overridable', 'subject', 'subject_overridable', 'x_mailer',
'verp_hostname', 'verp_disable_sender_header', 'mailer_type', 'mailer_settings', 'namespace']); 'verp_hostname', 'verp_disable_sender_header', 'mailer_type', 'mailer_settings', 'namespace']);
@ -155,13 +161,7 @@ export default class CUD extends Component {
this.disableForm(); this.disableForm();
this.setFormStatusMessage('info', t('saving')); this.setFormStatusMessage('info', t('saving'));
const submitResult = await this.validateAndSendFormValuesToURL(sendMethod, url, data => { const submitResult = await this.validateAndSendFormValuesToURL(sendMethod, url);
this.mailerTypes[data.mailer_type].beforeSave(data);
if (!data.verpEnabled) {
data.verp_hostname = null;
data.verp_disable_sender_header = false;
}
});
if (submitResult) { if (submitResult) {
if (this.props.entity) { if (this.props.entity) {

View file

@ -1,6 +1,7 @@
'use strict'; 'use strict';
const config = require('config'); const config = require('config');
const log = require('./log');
const mailers = require('./mailers'); const mailers = require('./mailers');
const knex = require('./knex'); const knex = require('./knex');
const subscriptions = require('../models/subscriptions'); const subscriptions = require('../models/subscriptions');
@ -20,8 +21,7 @@ const htmlToText = require('html-to-text');
const {getPublicUrl} = require('./urls'); const {getPublicUrl} = require('./urls');
const blacklist = require('../models/blacklist'); const blacklist = require('../models/blacklist');
const libmime = require('libmime'); const libmime = require('libmime');
const shares = require('../models/shares') const shares = require('../models/shares');
class CampaignSender { class CampaignSender {
constructor() { constructor() {
@ -83,7 +83,7 @@ class CampaignSender {
const getOverridable = key => { const getOverridable = key => {
return sendConfiguration[key]; return sendConfiguration[key];
} };
const campaignAddress = [campaign.cid, list.cid, subscriptionGrouped.cid].join('.'); const campaignAddress = [campaign.cid, list.cid, subscriptionGrouped.cid].join('.');
@ -95,7 +95,7 @@ class CampaignSender {
replyTo: getOverridable('reply_to'), replyTo: getOverridable('reply_to'),
xMailer: sendConfiguration.x_mailer ? sendConfiguration.x_mailer : false, xMailer: sendConfiguration.x_mailer ? sendConfiguration.x_mailer : false,
to: { to: {
name: tools.formatMessage(campaign, list, subscriptionGrouped, mergeTags, list.to_name, false), name: list.to_name === null ? undefined : tools.formatMessage(campaign, list, subscriptionGrouped, mergeTags, list.to_name, false),
address: subscriptionGrouped.email address: subscriptionGrouped.email
}, },
sender: useVerpSenderHeader ? campaignAddress + '@' + sendConfiguration.verp_hostname : false, sender: useVerpSenderHeader ? campaignAddress + '@' + sendConfiguration.verp_hostname : false,
@ -336,7 +336,7 @@ class CampaignSender {
} else { } else {
return sendConfiguration[key] || ''; return sendConfiguration[key] || '';
} }
} };
const mail = { const mail = {
from: { from: {
@ -346,7 +346,7 @@ class CampaignSender {
replyTo: getOverridable('reply_to'), replyTo: getOverridable('reply_to'),
xMailer: sendConfiguration.x_mailer ? sendConfiguration.x_mailer : false, xMailer: sendConfiguration.x_mailer ? sendConfiguration.x_mailer : false,
to: { to: {
name: tools.formatMessage(campaign, list, subscriptionGrouped, mergeTags, list.to_name, false), name: list.to_name === null ? undefined : tools.formatMessage(campaign, list, subscriptionGrouped, mergeTags, list.to_name, false),
address: subscriptionGrouped.email address: subscriptionGrouped.email
}, },
sender: this.useVerpSenderHeader ? campaignAddress + '@' + sendConfiguration.verp_hostname : false, sender: this.useVerpSenderHeader ? campaignAddress + '@' + sendConfiguration.verp_hostname : false,
@ -391,24 +391,49 @@ class CampaignSender {
let status; let status;
let response; let response;
let responseId; let responseId = null;
try { try {
const info = await mailer.sendMassMail(mail); const info = await mailer.sendMassMail(mail);
status = SubscriptionStatus.SUBSCRIBED; status = SubscriptionStatus.SUBSCRIBED;
/* log.verbose('CampaignSender', `response: ${info.response} messageId: ${info.messageId}`);
ZoneMTA
let match;
if ((match = info.response.match(/^250 Message queued as ([0-9a-f]+)$/))) {
/*
ZoneMTA
info.response: 250 Message queued as 1691ad7f7ae00080fd info.response: 250 Message queued as 1691ad7f7ae00080fd
info.messageId: <e65c9386-e899-7d01-b21e-ec03c3a9d9b4@sathyasai.org> info.messageId: <e65c9386-e899-7d01-b21e-ec03c3a9d9b4@sathyasai.org>
*/
response = info.response;
responseId = match[1];
Postal Mail Server } else if ((match = info.messageId.match(/^<([^>@]*)@.*amazonses\.com>$/))) {
/*
AWS SES
info.response: 0102016ad2244c0a-955492f2-9194-4cd1-bef9-70a45906a5a7-000000
info.messageId: <0102016ad2244c0a-955492f2-9194-4cd1-bef9-70a45906a5a7-000000@eu-west-1.amazonses.com>
*/
response = info.response;
responseId = match[1];
} else if (info.response.match(/^250 OK$/) && (match = info.messageId.match(/^<([^>]*)>$/))) {
/*
Postal Mail Server
info.response: 250 OK info.response: 250 OK
info.messageId: <xxxxxxxxx@xxx.xx> (postal messageId) info.messageId: <xxxxxxxxx@xxx.xx> (postal messageId)
*/ */
response = info.response;
responseId = match[1];
} else {
/*
Fallback - Mailtrain v1 behavior
*/
response = info.response || info.messageId;
responseId = response.split(/\s+/).pop();
}
console.log(`response: ${info.response} messageId: ${info.messageId}`);
response = info.response || info.messageId;
responseId = info.messageId.replace(/(^<|>$)/g, "") || response.split(/\s+/).pop();
await knex('campaigns').where('id', campaign.id).increment('delivered'); await knex('campaigns').where('id', campaign.id).increment('delivered');
} catch (err) { } catch (err) {
@ -417,6 +442,7 @@ class CampaignSender {
await knex('campaigns').where('id', campaign.id).increment('delivered').increment('bounced'); await knex('campaigns').where('id', campaign.id).increment('delivered').increment('bounced');
} }
const now = new Date(); const now = new Date();
if (campaign.type === CampaignType.REGULAR || campaign.type === CampaignType.RSS_ENTRY) { if (campaign.type === CampaignType.REGULAR || campaign.type === CampaignType.RSS_ENTRY) {

View file

@ -13,8 +13,6 @@ const uploads = multer();
router.postAsync('/aws', async (req, res) => { router.postAsync('/aws', async (req, res) => {
console.log(req.body);
if (typeof req.body === 'string') { if (typeof req.body === 'string') {
req.body = JSON.parse(req.body); req.body = JSON.parse(req.body);
} }