Built-in Zone MTA
Plugin for ZoneMTA for per-message DKIM keys.
This commit is contained in:
parent
d103a2cc79
commit
77c64f487d
18 changed files with 231 additions and 110 deletions
|
@ -142,14 +142,16 @@ export default class CUD extends Component {
|
||||||
|
|
||||||
@withAsyncErrorHandler
|
@withAsyncErrorHandler
|
||||||
async fetchSendConfiguration(sendConfigurationId) {
|
async fetchSendConfiguration(sendConfigurationId) {
|
||||||
this.fetchSendConfigurationId = sendConfigurationId;
|
if (sendConfigurationId) {
|
||||||
|
this.fetchSendConfigurationId = sendConfigurationId;
|
||||||
|
|
||||||
const result = await axios.get(getUrl(`rest/send-configurations-public/${sendConfigurationId}`));
|
const result = await axios.get(getUrl(`rest/send-configurations-public/${sendConfigurationId}`));
|
||||||
|
|
||||||
if (sendConfigurationId === this.fetchSendConfigurationId) {
|
if (sendConfigurationId === this.fetchSendConfigurationId) {
|
||||||
this.setState({
|
this.setState({
|
||||||
sendConfiguration: result.data
|
sendConfiguration: result.data
|
||||||
});
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -442,7 +442,8 @@ class TextArea extends Component {
|
||||||
label: PropTypes.string.isRequired,
|
label: PropTypes.string.isRequired,
|
||||||
placeholder: PropTypes.string,
|
placeholder: PropTypes.string,
|
||||||
help: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
|
help: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
|
||||||
format: PropTypes.string
|
format: PropTypes.string,
|
||||||
|
className: PropTypes.string
|
||||||
}
|
}
|
||||||
|
|
||||||
static contextTypes = {
|
static contextTypes = {
|
||||||
|
@ -452,11 +453,12 @@ class TextArea extends Component {
|
||||||
render() {
|
render() {
|
||||||
const props = this.props;
|
const props = this.props;
|
||||||
const owner = this.context.formStateOwner;
|
const owner = this.context.formStateOwner;
|
||||||
const id = this.props.id;
|
const id = props.id;
|
||||||
const htmlId = 'form_' + id;
|
const htmlId = 'form_' + id;
|
||||||
|
const className = props.className || ''
|
||||||
|
|
||||||
return wrapInput(id, htmlId, owner, props.format, '', props.label, props.help,
|
return wrapInput(id, htmlId, owner, props.format, '', props.label, props.help,
|
||||||
<textarea id={htmlId} placeholder={props.placeholder} value={owner.getFormValue(id) || ''} className="form-control" aria-describedby={htmlId + '_help'} onChange={evt => owner.updateFormValue(id, evt.target.value)}></textarea>
|
<textarea id={htmlId} placeholder={props.placeholder} value={owner.getFormValue(id) || ''} className={`form-control ${className}`} aria-describedby={htmlId + '_help'} onChange={evt => owner.updateFormValue(id, evt.target.value)}></textarea>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -82,6 +82,7 @@ export default class CUD extends Component {
|
||||||
this.getFormValuesFromEntity(this.props.entity, data => {
|
this.getFormValuesFromEntity(this.props.entity, data => {
|
||||||
this.mailerTypes[data.mailer_type].afterLoad(data);
|
this.mailerTypes[data.mailer_type].afterLoad(data);
|
||||||
data.verpEnabled = !!data.verp_hostname;
|
data.verpEnabled = !!data.verp_hostname;
|
||||||
|
data.verp_hostname = data.verp_hostname || '';
|
||||||
});
|
});
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
@ -89,9 +90,13 @@ export default class CUD extends Component {
|
||||||
name: '',
|
name: '',
|
||||||
description: '',
|
description: '',
|
||||||
namespace: mailtrainConfig.user.namespace,
|
namespace: mailtrainConfig.user.namespace,
|
||||||
|
from_email: '',
|
||||||
from_email_overridable: false,
|
from_email_overridable: false,
|
||||||
|
from_name: '',
|
||||||
from_name_overridable: false,
|
from_name_overridable: false,
|
||||||
|
reply_to: '',
|
||||||
reply_to_overridable: false,
|
reply_to_overridable: false,
|
||||||
|
subject: '',
|
||||||
subject_overridable: false,
|
subject_overridable: false,
|
||||||
verpEnabled: false,
|
verpEnabled: false,
|
||||||
verp_hostname: '',
|
verp_hostname: '',
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
import {MailerType} from "../../../shared/send-configurations";
|
import {MailerType, ZoneMTAType} from "../../../shared/send-configurations";
|
||||||
import {
|
import {
|
||||||
CheckBox,
|
CheckBox,
|
||||||
Dropdown,
|
Dropdown,
|
||||||
|
@ -11,6 +11,7 @@ import {
|
||||||
TextArea
|
TextArea
|
||||||
} from "../lib/form";
|
} from "../lib/form";
|
||||||
import {Trans} from "react-i18next";
|
import {Trans} from "react-i18next";
|
||||||
|
import styles from "./styles.scss";
|
||||||
|
|
||||||
export const mailerTypesOrder = [
|
export const mailerTypesOrder = [
|
||||||
MailerType.ZONE_MTA,
|
MailerType.ZONE_MTA,
|
||||||
|
@ -139,6 +140,12 @@ export function getMailerTypes(t) {
|
||||||
{ key: 'eu-west-1', label: t('euwest1')}
|
{ key: 'eu-west-1', label: t('euwest1')}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const zoneMtaTypeOptions = [
|
||||||
|
{ key: ZoneMTAType.WITH_MAILTRAIN_HEADER_CONF, label: t('Dynamic configuration of DKIM keys via ZoneMTA\'s Mailtrain plugin (use this option for builtin ZoneMTA)')},
|
||||||
|
{ key: ZoneMTAType.WITH_HTTP_CONF, label: t('Dynamic configuration of DKIM keys via ZoneMTA\'s HTTP config plugin')},
|
||||||
|
{ key: ZoneMTAType.REGULAR, label: t('No dynamic configuration of DKIM keys')}
|
||||||
|
]
|
||||||
|
|
||||||
mailerTypes[MailerType.GENERIC_SMTP] = {
|
mailerTypes[MailerType.GENERIC_SMTP] = {
|
||||||
getForm: owner =>
|
getForm: owner =>
|
||||||
<div>
|
<div>
|
||||||
|
@ -182,39 +189,49 @@ export function getMailerTypes(t) {
|
||||||
};
|
};
|
||||||
|
|
||||||
mailerTypes[MailerType.ZONE_MTA] = {
|
mailerTypes[MailerType.ZONE_MTA] = {
|
||||||
getForm: owner =>
|
getForm: owner => {
|
||||||
<div>
|
const zoneMtaType = Number.parseInt(owner.getFormValue('zoneMtaType'));
|
||||||
<Fieldset label={t('mailerSettings')}>
|
return (
|
||||||
<Dropdown id="mailer_type" label={t('mailerType')} options={typeOptions}/>
|
<div>
|
||||||
<InputField id="smtpHostname" label={t('hostname')} placeholder={t('hostnameEgSmtpexamplecom')}/>
|
<Fieldset label={t('mailerSettings')}>
|
||||||
<InputField id="smtpPort" label={t('port')} placeholder={t('portEg465AutodetectedIfLeftBlank')}/>
|
<Dropdown id="mailer_type" label={t('mailerType')} options={typeOptions}/>
|
||||||
<Dropdown id="smtpEncryption" label={t('encryption')} options={smtpEncryptionOptions}/>
|
<Dropdown id="zoneMtaType" label={t('Dynamic configuration')} options={zoneMtaTypeOptions}/>
|
||||||
<CheckBox id="smtpUseAuth" text={t('enableSmtpAuthentication')}/>
|
<InputField id="smtpHostname" label={t('hostname')} placeholder={t('hostnameEgSmtpexamplecom')}/>
|
||||||
{ owner.getFormValue('smtpUseAuth') &&
|
<InputField id="smtpPort" label={t('port')} placeholder={t('portEg465AutodetectedIfLeftBlank')}/>
|
||||||
<div>
|
<Dropdown id="smtpEncryption" label={t('encryption')} options={smtpEncryptionOptions}/>
|
||||||
<InputField id="smtpUser" label={t('username')} placeholder={t('usernameEgMyaccount@examplecom')}/>
|
<CheckBox id="smtpUseAuth" text={t('enableSmtpAuthentication')}/>
|
||||||
<InputField id="smtpPassword" label={t('password')} placeholder={t('usernameEgMyaccount@examplecom')}/>
|
{ owner.getFormValue('smtpUseAuth') &&
|
||||||
</div>
|
<div>
|
||||||
|
<InputField id="smtpUser" label={t('username')} placeholder={t('usernameEgMyaccount@examplecom')}/>
|
||||||
|
<InputField id="smtpPassword" label={t('password')} placeholder={t('usernameEgMyaccount@examplecom')}/>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</Fieldset>
|
||||||
|
{(zoneMtaType === ZoneMTAType.WITH_MAILTRAIN_HEADER_CONF || zoneMtaType === ZoneMTAType.WITH_HTTP_CONF) &&
|
||||||
|
<Fieldset label={t('dkimSigning')}>
|
||||||
|
<Trans i18nKey="ifYouAreUsingZoneMtaThenMailtrainCan"><p>If you are using ZoneMTA then Mailtrain can provide a DKIM key for signing all outgoing messages.</p></Trans>
|
||||||
|
<Trans i18nKey="doNotUseSensitiveKeysHereThePrivateKeyIs"><p className="text-warning">Do not use sensitive keys here. The private key is not encrypted in the database.</p></Trans>
|
||||||
|
{zoneMtaType === ZoneMTAType.WITH_HTTP_CONF &&
|
||||||
|
<InputField id="dkimApiKey" label={t('zoneMtaDkimApiKey')} help={t('secretValueKnownToZoneMtaForRequesting')}/>
|
||||||
|
}
|
||||||
|
<InputField id="dkimDomain" label={t('dkimDomain')} help={t('leaveBlankToUseTheSenderEmailAddress')}/>
|
||||||
|
<InputField id="dkimSelector" label={t('dkimKeySelector')} help={t('signingIsDisabledWithoutAValidSelector')}/>
|
||||||
|
<TextArea id="dkimPrivateKey" className={styles.dkimPrivateKey} label={t('dkimPrivateKey')} placeholder={t('beginsWithBeginRsaPrivateKey')} help={t('signingIsDisabledWithoutAValidPrivateKey')}/>
|
||||||
|
</Fieldset>
|
||||||
}
|
}
|
||||||
</Fieldset>
|
<Fieldset label={t('advancedMailerSettings')}>
|
||||||
<Fieldset label={t('dkimSigning')}>
|
<CheckBox id="logTransactions" text={t('logSmtpTransactions')}/>
|
||||||
<Trans i18nKey="ifYouAreUsingZoneMtaThenMailtrainCan"><p>If you are using ZoneMTA then Mailtrain can provide a DKIM key for signing all outgoing messages. Other services usually provide their own means to DKIM sign your messages.</p></Trans>
|
<CheckBox id="smtpAllowSelfSigned" text={t('allowSelfsignedCertificates')}/>
|
||||||
<Trans i18nKey="doNotUseSensitiveKeysHereThePrivateKeyIs"><p className="text-warning">Do not use sensitive keys here. The private key is not encrypted in the database.</p></Trans>
|
<InputField id="maxConnections" label={t('maxConnections')} placeholder={t('theCountOfMaxConnectionsEg10')} help={t('theCountOfMaximumSimultaneousConnections')}/>
|
||||||
<InputField id="dkimApiKey" label={t('zoneMtaDkimApiKey')} help={t('secretValueKnownToZoneMtaForRequesting')}/>
|
<InputField id="smtpMaxMessages" label={t('maxMessages')} placeholder={t('theCountOfMaxMessagesEg100')} help={t('theNumberOfMessagesToSendThroughASingle')}/>
|
||||||
<InputField id="dkimDomain" label={t('dkimDomain')} help={t('leaveBlankToUseTheSenderEmailAddress')}/>
|
<InputField id="throttling" label={t('throttling')} placeholder={t('messagesPerHourEg1000')} help={t('maximumNumberOfMessagesToSendInAnHour')}/>
|
||||||
<InputField id="dkimSelector" label={t('dkimKeySelector')} help={t('signingIsDisabledWithoutAValidSelector')}/>
|
</Fieldset>
|
||||||
<TextArea id="dkimPrivateKey" label={t('dkimPrivateKey')} placeholder={t('beginsWithBeginRsaPrivateKey')} help={t('signingIsDisabledWithoutAValidPrivateKey')}/>
|
</div>
|
||||||
</Fieldset>
|
);
|
||||||
<Fieldset label={t('advancedMailerSettings')}>
|
},
|
||||||
<CheckBox id="logTransactions" text={t('logSmtpTransactions')}/>
|
|
||||||
<CheckBox id="smtpAllowSelfSigned" text={t('allowSelfsignedCertificates')}/>
|
|
||||||
<InputField id="maxConnections" label={t('maxConnections')} placeholder={t('theCountOfMaxConnectionsEg10')} help={t('theCountOfMaximumSimultaneousConnections')}/>
|
|
||||||
<InputField id="smtpMaxMessages" label={t('maxMessages')} placeholder={t('theCountOfMaxMessagesEg100')} help={t('theNumberOfMessagesToSendThroughASingle')}/>
|
|
||||||
<InputField id="throttling" label={t('throttling')} placeholder={t('messagesPerHourEg1000')} help={t('maximumNumberOfMessagesToSendInAnHour')}/>
|
|
||||||
</Fieldset>
|
|
||||||
</div>,
|
|
||||||
initData: () => ({
|
initData: () => ({
|
||||||
...getInitGenericSMTP(),
|
...getInitGenericSMTP(),
|
||||||
|
zoneMtaType: ZoneMTAType.REGULAR,
|
||||||
dkimApiKey: '',
|
dkimApiKey: '',
|
||||||
dkimDomain: '',
|
dkimDomain: '',
|
||||||
dkimSelector: '',
|
dkimSelector: '',
|
||||||
|
@ -222,6 +239,7 @@ export function getMailerTypes(t) {
|
||||||
}),
|
}),
|
||||||
afterLoad: data => {
|
afterLoad: data => {
|
||||||
afterLoadGenericSMTP(data);
|
afterLoadGenericSMTP(data);
|
||||||
|
data.zoneMtaType = data.mailer_settings.zoneMtaType;
|
||||||
data.dkimApiKey = data.mailer_settings.dkimApiKey;
|
data.dkimApiKey = data.mailer_settings.dkimApiKey;
|
||||||
data.dkimDomain = data.mailer_settings.dkimDomain;
|
data.dkimDomain = data.mailer_settings.dkimDomain;
|
||||||
data.dkimSelector = data.mailer_settings.dkimSelector;
|
data.dkimSelector = data.mailer_settings.dkimSelector;
|
||||||
|
@ -229,10 +247,17 @@ export function getMailerTypes(t) {
|
||||||
},
|
},
|
||||||
beforeSave: data => {
|
beforeSave: data => {
|
||||||
beforeSaveGenericSMTP(data);
|
beforeSaveGenericSMTP(data);
|
||||||
data.mailer_settings.dkimApiKey = data.dkimApiKey;
|
const zoneMtaType = Number.parseInt(data.zoneMtaType);
|
||||||
data.mailer_settings.dkimDomain = data.dkimDomain;
|
data.mailer_settings.zoneMtaType = zoneMtaType;
|
||||||
data.mailer_settings.dkimSelector = data.dkimSelector;
|
if (zoneMtaType === ZoneMTAType.WITH_HTTP_CONF || zoneMtaType === ZoneMTAType.WITH_MAILTRAIN_HEADER_CONF) {
|
||||||
data.mailer_settings.dkimPrivateKey = data.dkimPrivateKey;
|
data.mailer_settings.dkimDomain = data.dkimDomain;
|
||||||
|
data.mailer_settings.dkimSelector = data.dkimSelector;
|
||||||
|
data.mailer_settings.dkimPrivateKey = data.dkimPrivateKey;
|
||||||
|
}
|
||||||
|
if (zoneMtaType === ZoneMTAType.WITH_HTTP_CONF) {
|
||||||
|
data.mailer_settings.dkimApiKey = data.dkimApiKey;
|
||||||
|
}
|
||||||
|
|
||||||
clearBeforeSave(data);
|
clearBeforeSave(data);
|
||||||
},
|
},
|
||||||
afterTypeChange: mutState => {
|
afterTypeChange: mutState => {
|
||||||
|
|
3
client/src/send-configurations/styles.scss
Normal file
3
client/src/send-configurations/styles.scss
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
textarea.dkimPrivateKey {
|
||||||
|
height: 200px;
|
||||||
|
}
|
|
@ -215,6 +215,9 @@ testServer:
|
||||||
password: testpass
|
password: testpass
|
||||||
logger: false
|
logger: false
|
||||||
|
|
||||||
|
builtinZoneMTA:
|
||||||
|
enabled: true
|
||||||
|
|
||||||
seleniumWebDriver:
|
seleniumWebDriver:
|
||||||
browser: phantomjs
|
browser: phantomjs
|
||||||
|
|
||||||
|
|
|
@ -23,4 +23,6 @@ passwordresetlink="xxx"
|
||||||
[reports]
|
[reports]
|
||||||
enabled=true
|
enabled=true
|
||||||
[redis]
|
[redis]
|
||||||
enabled=true
|
enabled=true
|
||||||
|
[log]
|
||||||
|
level="verbose"
|
|
@ -21,6 +21,7 @@ const privilegeHelpers = require('./lib/privilege-helpers');
|
||||||
const knex = require('./lib/knex');
|
const knex = require('./lib/knex');
|
||||||
const shares = require('./models/shares');
|
const shares = require('./models/shares');
|
||||||
const { AppType } = require('../shared/app');
|
const { AppType } = require('../shared/app');
|
||||||
|
const builtinZoneMta = require('./lib/builtin-zone-mta');
|
||||||
|
|
||||||
const trustedPort = config.www.trustedPort;
|
const trustedPort = config.www.trustedPort;
|
||||||
const sandboxPort = config.www.sandboxPort;
|
const sandboxPort = config.www.sandboxPort;
|
||||||
|
@ -94,29 +95,31 @@ dbcheck(err => { // Check if database needs upgrading before starting the server
|
||||||
.then(() =>
|
.then(() =>
|
||||||
executor.spawn(() =>
|
executor.spawn(() =>
|
||||||
testServer(() =>
|
testServer(() =>
|
||||||
verpServer(() =>
|
verpServer(() =>
|
||||||
startHTTPServer(AppType.TRUSTED, 'trusted', trustedPort, () =>
|
builtinZoneMta.spawn(() =>
|
||||||
startHTTPServer(AppType.SANDBOXED, 'sandbox', sandboxPort, () =>
|
startHTTPServer(AppType.TRUSTED, 'trusted', trustedPort, () =>
|
||||||
startHTTPServer(AppType.PUBLIC, 'public', publicPort, () => {
|
startHTTPServer(AppType.SANDBOXED, 'sandbox', sandboxPort, () =>
|
||||||
privilegeHelpers.dropRootPrivileges();
|
startHTTPServer(AppType.PUBLIC, 'public', publicPort, () => {
|
||||||
|
privilegeHelpers.dropRootPrivileges();
|
||||||
|
|
||||||
tzupdate.start();
|
tzupdate.start();
|
||||||
|
|
||||||
importer.spawn(() =>
|
importer.spawn(() =>
|
||||||
feedcheck.spawn(() =>
|
feedcheck.spawn(() =>
|
||||||
senders.spawn(() => {
|
senders.spawn(() => {
|
||||||
triggers.start();
|
triggers.start();
|
||||||
gdprCleanup.start();
|
gdprCleanup.start();
|
||||||
|
|
||||||
postfixBounceServer(async () => {
|
postfixBounceServer(async () => {
|
||||||
await reportProcessor.init();
|
await reportProcessor.init();
|
||||||
log.info('Service', 'All services started');
|
log.info('Service', 'All services started');
|
||||||
appBuilder.setReady();
|
appBuilder.setReady();
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
45
server/lib/builtin-zone-mta.js
Normal file
45
server/lib/builtin-zone-mta.js
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const config = require('config');
|
||||||
|
const fork = require('child_process').fork;
|
||||||
|
const log = require('./log');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
let zoneMtaProcess;
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
spawn
|
||||||
|
};
|
||||||
|
|
||||||
|
function spawn(callback) {
|
||||||
|
if (config.builtinZoneMTA.enabled) {
|
||||||
|
log.info('ZoneMTA', 'Starting built-in Zone MTA process');
|
||||||
|
|
||||||
|
zoneMtaProcess = fork(
|
||||||
|
path.join(__dirname, '..', '..', 'zone-mta', 'index.js'),
|
||||||
|
['--config=' + path.join(__dirname, '..', '..', 'zone-mta', 'config', 'zonemta.js')],
|
||||||
|
{
|
||||||
|
cwd: path.join(__dirname, '..', '..', 'zone-mta'),
|
||||||
|
env: {NODE_ENV: process.env.NODE_ENV}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
zoneMtaProcess.on('message', msg => {
|
||||||
|
if (msg) {
|
||||||
|
if (msg.type === 'zone-mta-started') {
|
||||||
|
log.info('ZoneMTA', 'ZoneMTA process started');
|
||||||
|
return callback();
|
||||||
|
} else if (msg.type === 'entries-added') {
|
||||||
|
senders.scheduleCheck();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
zoneMtaProcess.on('close', (code, signal) => {
|
||||||
|
log.error('ZoneMTA', 'ZoneMTA process exited with code %s signal %s', code, signal);
|
||||||
|
});
|
||||||
|
|
||||||
|
} else {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
}
|
|
@ -328,9 +328,9 @@ class CampaignSender {
|
||||||
|
|
||||||
const getOverridable = key => {
|
const getOverridable = key => {
|
||||||
if (sendConfiguration[key + '_overridable'] && this.campaign[key + '_override'] !== null) {
|
if (sendConfiguration[key + '_overridable'] && this.campaign[key + '_override'] !== null) {
|
||||||
return campaign[key + '_override'];
|
return campaign[key + '_override'] || '';
|
||||||
} else {
|
} else {
|
||||||
return sendConfiguration[key];
|
return sendConfiguration[key] || '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,7 @@ const transports = new Map();
|
||||||
async function getOrCreateMailer(sendConfigurationId) {
|
async function getOrCreateMailer(sendConfigurationId) {
|
||||||
let sendConfiguration;
|
let sendConfiguration;
|
||||||
|
|
||||||
if (!sendConfiguration) {
|
if (!sendConfigurationId) {
|
||||||
sendConfiguration = await sendConfigurations.getSystemSendConfiguration();
|
sendConfiguration = await sendConfigurations.getSystemSendConfiguration();
|
||||||
} else {
|
} else {
|
||||||
sendConfiguration = await sendConfigurations.getById(contextHelpers.getAdminContext(), sendConfigurationId, false, true);
|
sendConfiguration = await sendConfigurations.getById(contextHelpers.getAdminContext(), sendConfigurationId, false, true);
|
||||||
|
@ -38,8 +38,35 @@ function invalidateMailer(sendConfigurationId) {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function _addDkimKeys(transport, mail) {
|
||||||
|
const sendConfiguration = transport.mailer.sendConfiguration;
|
||||||
|
|
||||||
|
if (sendConfiguration.mailer_type === sendConfigurations.MailerType.ZONE_MTA) {
|
||||||
|
if (!mail.headers) {
|
||||||
|
mail.headers = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const dkimDomain = sendConfiguration.mailer_settings.dkimDomain;
|
||||||
|
const dkimSelector = (sendConfiguration.mailer_settings.dkimSelector || '').trim();
|
||||||
|
const dkimPrivateKey = (sendConfiguration.mailer_settings.dkimPrivateKey || '').trim();
|
||||||
|
|
||||||
|
if (dkimSelector && dkimPrivateKey) {
|
||||||
|
const from = (mail.from.address || '').trim();
|
||||||
|
const domain = from.split('@').pop().toLowerCase().trim();
|
||||||
|
|
||||||
|
mail.headers['x-mailtrain-dkim'] = JSON.stringify({
|
||||||
|
domainName: dkimDomain || domain,
|
||||||
|
keySelector: dkimSelector,
|
||||||
|
privateKey: dkimPrivateKey
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
async function _sendMail(transport, mail, template) {
|
async function _sendMail(transport, mail, template) {
|
||||||
|
_addDkimKeys(transport, mail);
|
||||||
|
|
||||||
let tryCount = 0;
|
let tryCount = 0;
|
||||||
const trySend = (callback) => {
|
const trySend = (callback) => {
|
||||||
tryCount++;
|
tryCount++;
|
||||||
|
@ -209,6 +236,7 @@ async function _createTransport(sendConfiguration) {
|
||||||
}
|
}
|
||||||
|
|
||||||
transport.mailer = {
|
transport.mailer = {
|
||||||
|
sendConfiguration,
|
||||||
throttleWait: bluebird.promisify(throttleWait),
|
throttleWait: bluebird.promisify(throttleWait),
|
||||||
sendTransactionalMail: async (mail, template) => await _sendTransactionalMail(transport, mail, template),
|
sendTransactionalMail: async (mail, template) => await _sendTransactionalMail(transport, mail, template),
|
||||||
sendMassMail: async (mail, template) => await _sendMail(transport, mail)
|
sendMassMail: async (mail, template) => await _sendMail(transport, mail)
|
||||||
|
|
|
@ -666,46 +666,33 @@ async function getMessageByCid(messageCid) {
|
||||||
|
|
||||||
const [campaignCid, listCid, subscriptionCid] = messageCidElems;
|
const [campaignCid, listCid, subscriptionCid] = messageCidElems;
|
||||||
|
|
||||||
await knex.transaction(async tx => {
|
return await knex.transaction(async tx => {
|
||||||
const list = await tx('lists').where('cid', listCid).select('id');
|
const list = await tx('lists').where('cid', listCid).select('id');
|
||||||
const subscrTblName = subscriptions.getSubscriptionTableName(list.id);
|
const subscrTblName = subscriptions.getSubscriptionTableName(list.id);
|
||||||
|
|
||||||
const message = await tx('campaign_messages')
|
const message = await tx('campaign_messages')
|
||||||
.innerJoin('campaigns', 'campaign_messages.campaign', 'campaigns.id')
|
.innerJoin('campaigns', 'campaign_messages.campaign', 'campaigns.id')
|
||||||
.innerJoin(subscrTblName, subscrTblName + '.id', 'campaign_messages.subscription')
|
.innerJoin(subscrTblName, subscrTblName + '.id', 'campaign_messages.subscription')
|
||||||
.leftJoin('segments', 'segment.id', 'campaign_messages.segment') // This is just to make sure that the respective segment still exists or return null if it doesn't
|
|
||||||
.leftJoin('send_configurations', 'send_configurations.id', 'campaign_messages.send_configuration') // This is just to make sure that the respective send_configuration still exists or return null if it doesn't
|
|
||||||
.where(subscrTblName + '.cid', subscriptionCid)
|
.where(subscrTblName + '.cid', subscriptionCid)
|
||||||
.where('campaigns.cid', campaignCid)
|
.where('campaigns.cid', campaignCid)
|
||||||
.select([
|
.select([
|
||||||
'campaign_messages.id', 'campaign_messages.campaign', 'campaign_messages.list', 'segments.id AS segment', 'campaign_messages.subscription',
|
'campaign_messages.id', 'campaign_messages.campaign', 'campaign_messages.list', 'campaign_messages.subscription', 'campaign_messages.status'
|
||||||
'send_configurations.id AS send_configuration', 'campaign_messages.status', 'campaign_messages.response', 'campaign_messages.response_id',
|
])
|
||||||
'campaign_messages.updated', 'campaign_messages.created', 'send_configurations.verp_hostname AS verp_hostname'
|
.first();
|
||||||
]);
|
|
||||||
|
|
||||||
if (message) {
|
|
||||||
await shares.enforceEntityPermissionTx(tx, context, 'campaign', message.campaign, 'manageMessages');
|
|
||||||
}
|
|
||||||
|
|
||||||
return message;
|
return message;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getMessageByResponseId(responseId) {
|
async function getMessageByResponseId(responseId) {
|
||||||
await knex.transaction(async tx => {
|
return await knex.transaction(async tx => {
|
||||||
|
console.log(responseId);
|
||||||
const message = await tx('campaign_messages')
|
const message = await tx('campaign_messages')
|
||||||
.leftJoin('segments', 'segment.id', 'campaign_messages.segment') // This is just to make sure that the respective segment still exists or return null if it doesn't
|
|
||||||
.leftJoin('send_configurations', 'send_configurations.id', 'campaign_messages.send_configuration') // This is just to make sure that the respective send_configuration still exists or return null if it doesn't
|
|
||||||
.where('campaign_messages.response_id', responseId)
|
.where('campaign_messages.response_id', responseId)
|
||||||
.select([
|
.select([
|
||||||
'campaign_messages.id', 'campaign_messages.campaign', 'campaign_messages.list', 'segments.id AS segment', 'campaign_messages.subscription',
|
'campaign_messages.id', 'campaign_messages.campaign', 'campaign_messages.list', 'campaign_messages.subscription', 'campaign_messages.status'
|
||||||
'send_configurations.id AS send_configuration', 'campaign_messages.status', 'campaign_messages.response', 'campaign_messages.response_id',
|
])
|
||||||
'campaign_messages.updated', 'campaign_messages.created', 'send_configurations.verp_hostname AS verp_hostname'
|
.first();
|
||||||
]);
|
|
||||||
|
|
||||||
if (message) {
|
|
||||||
await shares.enforceEntityPermissionTx(tx, context, 'campaign', message.campaign, 'manageMessages');
|
|
||||||
}
|
|
||||||
|
|
||||||
return message;
|
return message;
|
||||||
});
|
});
|
||||||
|
@ -763,6 +750,7 @@ async function changeStatusByMessage(context, message, subscriptionStatus, updat
|
||||||
}
|
}
|
||||||
|
|
||||||
await _changeStatusByMessageTx(tx, context, message, subscriptionStatus);
|
await _changeStatusByMessageTx(tx, context, message, subscriptionStatus);
|
||||||
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -180,22 +180,28 @@ router.postAsync('/mailgun', uploads.any(), async (req, res) => {
|
||||||
|
|
||||||
|
|
||||||
router.postAsync('/zone-mta', async (req, res) => {
|
router.postAsync('/zone-mta', async (req, res) => {
|
||||||
if (typeof req.body === 'string') {
|
try {
|
||||||
req.body = JSON.parse(req.body);
|
if (typeof req.body === 'string') {
|
||||||
}
|
req.body = JSON.parse(req.body);
|
||||||
|
|
||||||
if (req.body.id) {
|
|
||||||
const message = await campaigns.getMessageByCid(req.body.id);
|
|
||||||
|
|
||||||
if (message) {
|
|
||||||
await campaigns.changeStatusByMessage(contextHelpers.getAdminContext(), message, SubscriptionStatus.BOUNCED, true);
|
|
||||||
log.verbose('ZoneMTA', 'Marked message %s as bounced', req.body.id);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
res.json({
|
if (req.body.id) {
|
||||||
success: true
|
const message = await campaigns.getMessageByResponseId(req.body.id);
|
||||||
});
|
console.log(message);
|
||||||
|
|
||||||
|
if (message) {
|
||||||
|
await campaigns.changeStatusByMessage(contextHelpers.getAdminContext(), message, SubscriptionStatus.BOUNCED, true);
|
||||||
|
log.verbose('ZoneMTA', 'Marked message %s as bounced', req.body.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
success: true
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -164,7 +164,7 @@ async function processCampaign(campaignId) {
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
log.error('Senders', `Sending campaign ${campaignId} failed with error: ${err.message}`)
|
log.error('Senders', `Sending campaign ${campaignId} failed with error: ${err.message}`)
|
||||||
log.verbose(err);
|
log.verbose(err.stack);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -204,7 +204,7 @@ async function scheduleCampaigns() {
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
log.error('Senders', `Scheduling campaigns failed with error: ${err.message}`)
|
log.error('Senders', `Scheduling campaigns failed with error: ${err.message}`)
|
||||||
log.verbose(err);
|
log.verbose(err.stack);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -249,7 +249,7 @@ async function processQueued() {
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
log.error('Senders', `Processing queued messages failed with error: ${err.message}`)
|
log.error('Senders', `Processing queued messages failed with error: ${err.message}`)
|
||||||
log.verbose(err);
|
log.verbose(err.stack);
|
||||||
}
|
}
|
||||||
|
|
||||||
queuedSchedulerRunning = false;
|
queuedSchedulerRunning = false;
|
||||||
|
@ -326,7 +326,7 @@ async function init() {
|
||||||
scheduleCampaigns();
|
scheduleCampaigns();
|
||||||
|
|
||||||
} else if (type === 'reload-config') {
|
} else if (type === 'reload-config') {
|
||||||
for (const worker of workerProcesses.keys()) {
|
for (const workerId of workerProcesses.keys()) {
|
||||||
sendToWorker(workerId, 'reload-config', msg.data);
|
sendToWorker(workerId, 'reload-config', msg.data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,7 +35,7 @@ async function processMessages(campaignId, subscribers) {
|
||||||
log.verbose('Senders', 'Message sent and status updated for %s:%s', subData.listId, subData.email || subData.subscriptionId);
|
log.verbose('Senders', 'Message sent and status updated for %s:%s', subData.listId, subData.email || subData.subscriptionId);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
log.error('Senders', `Sending message to ${subData.listId}:${subData.email} failed with error: ${err.message}`)
|
log.error('Senders', `Sending message to ${subData.listId}:${subData.email} failed with error: ${err.message}`)
|
||||||
log.verbose(err);
|
log.verbose(err.stack);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ const contextHelpers = require('../../../lib/context-helpers');
|
||||||
const mosaicoTemplates = require('../../../../shared/mosaico-templates');
|
const mosaicoTemplates = require('../../../../shared/mosaico-templates');
|
||||||
const {getGlobalNamespaceId} = require('../../../../shared/namespaces');
|
const {getGlobalNamespaceId} = require('../../../../shared/namespaces');
|
||||||
const {getAdminId} = require('../../../../shared/users');
|
const {getAdminId} = require('../../../../shared/users');
|
||||||
const { MailerType, getSystemSendConfigurationId, getSystemSendConfigurationCid } = require('../../../../shared/send-configurations');
|
const { MailerType, ZoneMTAType, getSystemSendConfigurationId, getSystemSendConfigurationCid } = require('../../../../shared/send-configurations');
|
||||||
const { enforce } = require('../../../lib/helpers');
|
const { enforce } = require('../../../lib/helpers');
|
||||||
const { EntityVals: TriggerEntityVals, EventVals: TriggerEventVals } = require('../../../../shared/triggers');
|
const { EntityVals: TriggerEntityVals, EventVals: TriggerEventVals } = require('../../../../shared/triggers');
|
||||||
const { SubscriptionSource } = require('../../../../shared/lists');
|
const { SubscriptionSource } = require('../../../../shared/lists');
|
||||||
|
@ -753,6 +753,7 @@ async function migrateSettings(knex) {
|
||||||
if (settings.dkimApiKey) {
|
if (settings.dkimApiKey) {
|
||||||
mailer_type = MailerType.ZONE_MTA;
|
mailer_type = MailerType.ZONE_MTA;
|
||||||
mailer_settings.dkimApiKey = settings.dkimApiKey;
|
mailer_settings.dkimApiKey = settings.dkimApiKey;
|
||||||
|
mailer_settings.zoneMtaType = ZoneMTAType.WITH_HTTP_CONF;
|
||||||
mailer_settings.dkimDomain = settings.dkimDomain;
|
mailer_settings.dkimDomain = settings.dkimDomain;
|
||||||
mailer_settings.dkimSelector = settings.dkimSelector;
|
mailer_settings.dkimSelector = settings.dkimSelector;
|
||||||
mailer_settings.dkimPrivateKey = settings.dkimPrivateKey;
|
mailer_settings.dkimPrivateKey = settings.dkimPrivateKey;
|
||||||
|
|
|
@ -6,6 +6,12 @@ const MailerType = {
|
||||||
AWS_SES: 'aws_ses'
|
AWS_SES: 'aws_ses'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const ZoneMTAType = {
|
||||||
|
REGULAR: 0,
|
||||||
|
WITH_HTTP_CONF: 1,
|
||||||
|
WITH_MAILTRAIN_HEADER_CONF: 2
|
||||||
|
}
|
||||||
|
|
||||||
function getSystemSendConfigurationId() {
|
function getSystemSendConfigurationId() {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
@ -16,6 +22,7 @@ function getSystemSendConfigurationCid() {
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
MailerType,
|
MailerType,
|
||||||
|
ZoneMTAType,
|
||||||
getSystemSendConfigurationId,
|
getSystemSendConfigurationId,
|
||||||
getSystemSendConfigurationCid
|
getSystemSendConfigurationCid
|
||||||
};
|
};
|
1
zone-mta
Submodule
1
zone-mta
Submodule
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 7990c49dda71c8cb65ddd83da3b785711d26894b
|
Loading…
Add table
Add a link
Reference in a new issue