diff --git a/core/server/services/bulk-email/bulk-email-processor.js b/core/server/services/bulk-email/bulk-email-processor.js index b1bd1a4faa..86fe1a419b 100644 --- a/core/server/services/bulk-email/bulk-email-processor.js +++ b/core/server/services/bulk-email/bulk-email-processor.js @@ -91,11 +91,11 @@ module.exports = { // only fetch pending or failed batches to avoid re-sending previously sent emails const batchIds = await models.EmailBatch .getFilteredCollectionQuery({filter: `email_id:${emailId}+status:[pending,failed]`}, knexOptions) - .select('id'); + .select('id', 'member_segment'); - const batchResults = await Promise.map(batchIds, async ({id: emailBatchId}) => { + const batchResults = await Promise.map(batchIds, async ({id: emailBatchId, member_segment: memberSegment}) => { try { - await this.processEmailBatch({emailBatchId, options}); + await this.processEmailBatch({emailBatchId, options, memberSegment}); return new SuccessfulBatch(emailBatchId); } catch (error) { return new FailedBatch(emailBatchId, error); @@ -133,7 +133,7 @@ module.exports = { }, // accepts an ID rather than an EmailBatch model to better support running via a job queue - async processEmailBatch({emailBatchId, options}) { + async processEmailBatch({emailBatchId, options, memberSegment}) { const knexOptions = _.pick(options, ['transacting', 'forUpdate']); const emailBatchModel = await models.EmailBatch @@ -163,7 +163,7 @@ module.exports = { try { // send the email - const sendResponse = await this.send(emailBatchModel.relations.email.toJSON(), recipientRows); + const sendResponse = await this.send(emailBatchModel.relations.email.toJSON(), recipientRows, memberSegment); // update batch success status return await emailBatchModel.save({ @@ -196,9 +196,10 @@ module.exports = { /** * @param {Email-like} emailData - The email to send, must be a POJO so emailModel.toJSON() before calling if needed * @param {[EmailRecipient]} recipients - The recipients to send the email to with their associated data + * @param {string?} memberSegment - The member segment of the recipients * @returns {Object} - {providerId: 'xxx'} */ - send(emailData, recipients) { + send(emailData, recipients, memberSegment) { const mailgunInstance = mailgunProvider.getInstance(); if (!mailgunInstance) { return; @@ -229,6 +230,10 @@ module.exports = { recipientData[recipient.member_email] = data; }); + if (memberSegment) { + emailData = postEmailSerializer.renderEmailForSegment(emailData, memberSegment); + } + return mailgunProvider.send(emailData, recipientData, replacements).then((response) => { debug(`sent message (${Date.now() - startTime}ms)`); return response; diff --git a/core/server/services/mega/mega.js b/core/server/services/mega/mega.js index 046dfa1f6f..a75276affc 100644 --- a/core/server/services/mega/mega.js +++ b/core/server/services/mega/mega.js @@ -367,7 +367,7 @@ async function getEmailMemberRows({emailModel, memberSegment, options}) { */ async function createSegmentedEmailBatches({emailModel, options}) { const segments = getSegmentsFromHtml(emailModel.get('html')); - const batchIds = []; + let batchIds = []; if (segments.length) { for (const memberSegment of segments) { @@ -376,11 +376,11 @@ async function createSegmentedEmailBatches({emailModel, options}) { memberSegment, options }); - batchIds.push(emailBatchIds); + batchIds = emailBatchIds; } } else { - const emailBatchIds = createEmailBatches({emailModel, options}); - batchIds.push(emailBatchIds); + const emailBatchIds = await createEmailBatches({emailModel, options}); + batchIds = emailBatchIds; } return batchIds; diff --git a/core/server/services/mega/post-email-serializer.js b/core/server/services/mega/post-email-serializer.js index 18f599cda9..ba2e088d1b 100644 --- a/core/server/services/mega/post-email-serializer.js +++ b/core/server/services/mega/post-email-serializer.js @@ -22,6 +22,20 @@ const getSite = () => { }); }; +const htmlToPlaintext = (html) => { + // same options as used in Post model for generating plaintext but without `wordwrap: 80` + // to avoid replacement strings being split across lines and for mail clients to handle + // word wrapping based on user preferences + return htmlToText.fromString(html, { + wordwrap: false, + ignoreImage: true, + hideLinkHrefIfSameAsText: true, + preserveNewlines: true, + returnDomByDefault: true, + uppercaseHeadings: false + }); +}; + /** * createUnsubscribeUrl * @@ -195,17 +209,7 @@ const serialize = async (postModel, options = {isBrowserPreview: false, apiVersi } post.html = mobiledocLib.mobiledocHtmlRenderer.render(JSON.parse(post.mobiledoc), {target: 'email'}); - // same options as used in Post model for generating plaintext but without `wordwrap: 80` - // to avoid replacement strings being split across lines and for mail clients to handle - // word wrapping based on user preferences - post.plaintext = htmlToText.fromString(post.html, { - wordwrap: false, - ignoreImage: true, - hideLinkHrefIfSameAsText: true, - preserveNewlines: true, - returnDomByDefault: true, - uppercaseHeadings: false - }); + post.plaintext = htmlToPlaintext(post.html); // Outlook will render feature images at full-size breaking the layout. // Content images fix this by rendering max 600px images - do the same for feature image here @@ -276,8 +280,27 @@ const serialize = async (postModel, options = {isBrowserPreview: false, apiVersi }; }; +function renderEmailForSegment(email, memberSegment) { + const result = {...email}; + const $ = cheerio.load(result.html); + + $('[data-gh-segment]').get().forEach((node) => { + if (node.attribs['data-gh-segment'] !== memberSegment) { //TODO: replace with NQL interpretation + $(node).remove(); + } else { + // Getting rid of the attribute for a cleaner html output + $(node).removeAttr('data-gh-segment'); + } + }); + result.html = $.html(); + result.plaintext = htmlToPlaintext(result.html); + + return result; +} + module.exports = { serialize, createUnsubscribeUrl, + renderEmailForSegment, parseReplacements }; diff --git a/test/unit/services/mega/post-email-serializer_spec.js b/test/unit/services/mega/post-email-serializer_spec.js index c7f2b4a064..f4ec886084 100644 --- a/test/unit/services/mega/post-email-serializer_spec.js +++ b/test/unit/services/mega/post-email-serializer_spec.js @@ -1,6 +1,6 @@ const should = require('should'); -const {parseReplacements} = require('../../../../core/server/services/mega/post-email-serializer'); +const {parseReplacements, renderEmailForSegment} = require('../../../../core/server/services/mega/post-email-serializer'); describe('Post Email Serializer', function () { it('creates replacement pattern for valid format and value', function () { @@ -31,4 +31,42 @@ describe('Post Email Serializer', function () { replaced.length.should.equal(0); }); + + describe('renderEmailForSegment', function () { + it('shouldn\'t change an email that has no member segment', function () { + const email = { + otherProperty: true, + html: '