mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-02-24 23:48:13 -05:00
no issue - By default for new sites, support address is set same as from address to `noreply` , with full email address using the domain for `@` - For newsletter emails, the support address was missing the default site domain to be added to address if its `noreply` - Fix updates the support address to use the same format as from address and add relevant domain for default case
152 lines
6.1 KiB
JavaScript
152 lines
6.1 KiB
JavaScript
const _ = require('lodash');
|
|
const errors = require('@tryghost/errors');
|
|
const {i18n} = require('../../lib/common');
|
|
const logging = require('../../../shared/logging');
|
|
const mailgunProvider = require('./mailgun');
|
|
const configService = require('../../../shared/config');
|
|
const settingsCache = require('../settings/cache');
|
|
const sentry = require('../../../shared/sentry');
|
|
const debug = require('ghost-ignition').debug('mega');
|
|
|
|
/**
|
|
* An object representing batch request result
|
|
* @typedef { Object } BatchResultBase
|
|
* @property { string } data - data that is returned from Mailgun or one which Mailgun was called with
|
|
*/
|
|
class BatchResultBase {
|
|
}
|
|
|
|
class SuccessfulBatch extends BatchResultBase {
|
|
constructor(data) {
|
|
super();
|
|
this.data = data;
|
|
}
|
|
}
|
|
|
|
class FailedBatch extends BatchResultBase {
|
|
constructor(error, data) {
|
|
super();
|
|
error.originalMessage = error.message;
|
|
|
|
if (error.statusCode >= 500) {
|
|
error.message = 'Email service is currently unavailable - please try again';
|
|
} else if (error.statusCode === 401) {
|
|
error.message = 'Email failed to send - please verify your credentials';
|
|
} else if (error.message && error.message.toLowerCase().includes('dmarc')) {
|
|
error.message = 'Unable to send email from domains implementing strict DMARC policies';
|
|
} else if (error.message.includes(`'to' parameter is not a valid address`)) {
|
|
error.message = 'Recipient is not a valid address';
|
|
} else {
|
|
error.message = 'Email failed to send - please verify your email settings';
|
|
}
|
|
|
|
this.error = error;
|
|
this.data = data;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* An email address
|
|
* @typedef { string } EmailAddress
|
|
*/
|
|
|
|
/**
|
|
* An object representing an email to send
|
|
* @typedef { Object } Email
|
|
* @property { string } html - The html content of the email
|
|
* @property { string } subject - The subject of the email
|
|
*/
|
|
|
|
module.exports = {
|
|
SuccessfulBatch,
|
|
FailedBatch,
|
|
/**
|
|
* @param {Email} message - The message to send
|
|
* @param {[EmailAddress]} recipients - the recipients to send the email to
|
|
* @param {[object]} recipientData - list of data keyed by email to inject into the email
|
|
* @returns {Promise<Array<BatchResultBase>>} An array of promises representing the success of the batch email sending
|
|
*/
|
|
async send(message, recipients, recipientData = {}) {
|
|
let BATCH_SIZE = 1000;
|
|
const mailgunInstance = mailgunProvider.getInstance();
|
|
if (!mailgunInstance) {
|
|
return;
|
|
}
|
|
let fromAddress = message.from;
|
|
if (/@localhost$/.test(message.from) || /@ghost.local$/.test(message.from)) {
|
|
fromAddress = 'localhost@example.com';
|
|
logging.warn(`Rewriting bulk email from address ${message.from} to ${fromAddress}`);
|
|
|
|
BATCH_SIZE = 2;
|
|
}
|
|
|
|
const blogTitle = settingsCache.get('title') ? settingsCache.get('title').replace(/"/g, '\\"') : '';
|
|
let supportAddress = message.supportAddress;
|
|
delete message.supportAddress;
|
|
const replyAddressOption = settingsCache.get('members_reply_address');
|
|
const replyToAddress = (replyAddressOption === 'support') ? supportAddress : fromAddress;
|
|
fromAddress = blogTitle ? `"${blogTitle}"<${fromAddress}>` : fromAddress;
|
|
|
|
const chunkedRecipients = _.chunk(recipients, BATCH_SIZE);
|
|
|
|
return Promise.map(chunkedRecipients, (toAddresses, chunkIndex) => {
|
|
const recipientVariables = {};
|
|
toAddresses.forEach((email) => {
|
|
recipientVariables[email] = recipientData[email];
|
|
});
|
|
|
|
const batchData = {
|
|
to: toAddresses,
|
|
from: fromAddress,
|
|
'h:Reply-To': replyToAddress || fromAddress,
|
|
'recipient-variables': recipientVariables
|
|
};
|
|
|
|
const bulkEmailConfig = configService.get('bulkEmail');
|
|
|
|
if (bulkEmailConfig && bulkEmailConfig.mailgun && bulkEmailConfig.mailgun.tag) {
|
|
Object.assign(batchData, {
|
|
'o:tag': [bulkEmailConfig.mailgun.tag, 'bulk-email']
|
|
});
|
|
}
|
|
|
|
if (bulkEmailConfig && bulkEmailConfig.mailgun && bulkEmailConfig.mailgun.testmode) {
|
|
Object.assign(batchData, {
|
|
'o:testmode': true
|
|
});
|
|
}
|
|
|
|
const messageData = Object.assign({}, message, batchData);
|
|
|
|
// Rename plaintext field to text for Mailgun
|
|
messageData.text = messageData.plaintext;
|
|
delete messageData.plaintext;
|
|
|
|
return new Promise((resolve) => {
|
|
const batchStartTime = Date.now();
|
|
debug(`sending message batch ${chunkIndex + 1} to ${toAddresses.length}`);
|
|
mailgunInstance.messages().send(messageData, (error, body) => {
|
|
if (error) {
|
|
// NOTE: logging an error here only but actual handling should happen in more sophisticated batch retry handler
|
|
// REF: possible mailgun errors https://documentation.mailgun.com/en/latest/api-intro.html#errors
|
|
let ghostError = new errors.EmailError({
|
|
err: error,
|
|
context: i18n.t('errors.services.mega.requestFailed.error')
|
|
});
|
|
|
|
sentry.captureException(ghostError);
|
|
logging.warn(ghostError);
|
|
|
|
// NOTE: these are generated variables, so can be regenerated when retry is done
|
|
const data = _.omit(batchData, ['recipient-variables']);
|
|
debug(`failed message batch ${chunkIndex + 1} (${Date.now() - batchStartTime}ms)`);
|
|
resolve(new FailedBatch(error, data));
|
|
} else {
|
|
debug(`sent message batch ${chunkIndex + 1} (${Date.now() - batchStartTime}ms)`);
|
|
resolve(new SuccessfulBatch(body));
|
|
}
|
|
});
|
|
});
|
|
}, {concurrency: 10});
|
|
}
|
|
};
|