ghost 5.37.0 → 5.38.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (154) hide show
  1. package/components/{tryghost-adapter-cache-memory-ttl-5.37.0.tgz → tryghost-adapter-cache-memory-ttl-5.38.0.tgz} +0 -0
  2. package/components/tryghost-adapter-cache-redis-5.38.0.tgz +0 -0
  3. package/components/{tryghost-adapter-manager-5.37.0.tgz → tryghost-adapter-manager-5.38.0.tgz} +0 -0
  4. package/components/{tryghost-api-framework-5.37.0.tgz → tryghost-api-framework-5.38.0.tgz} +0 -0
  5. package/components/{tryghost-api-version-compatibility-service-5.37.0.tgz → tryghost-api-version-compatibility-service-5.38.0.tgz} +0 -0
  6. package/components/tryghost-audience-feedback-5.38.0.tgz +0 -0
  7. package/components/tryghost-bootstrap-socket-5.38.0.tgz +0 -0
  8. package/components/{tryghost-constants-5.37.0.tgz → tryghost-constants-5.38.0.tgz} +0 -0
  9. package/components/tryghost-custom-theme-settings-service-5.38.0.tgz +0 -0
  10. package/components/{tryghost-data-generator-5.37.0.tgz → tryghost-data-generator-5.38.0.tgz} +0 -0
  11. package/components/tryghost-domain-events-5.38.0.tgz +0 -0
  12. package/components/tryghost-dynamic-routing-events-5.38.0.tgz +0 -0
  13. package/components/tryghost-email-analytics-provider-mailgun-5.38.0.tgz +0 -0
  14. package/components/{tryghost-email-analytics-service-5.37.0.tgz → tryghost-email-analytics-service-5.38.0.tgz} +0 -0
  15. package/components/tryghost-email-content-generator-5.38.0.tgz +0 -0
  16. package/components/tryghost-email-events-5.38.0.tgz +0 -0
  17. package/components/tryghost-email-service-5.38.0.tgz +0 -0
  18. package/components/{tryghost-email-suppression-list-5.37.0.tgz → tryghost-email-suppression-list-5.38.0.tgz} +0 -0
  19. package/components/tryghost-event-aware-cache-wrapper-5.38.0.tgz +0 -0
  20. package/components/{tryghost-express-dynamic-redirects-5.37.0.tgz → tryghost-express-dynamic-redirects-5.38.0.tgz} +0 -0
  21. package/components/tryghost-external-media-inliner-5.38.0.tgz +0 -0
  22. package/components/tryghost-extract-api-key-5.38.0.tgz +0 -0
  23. package/components/tryghost-html-to-plaintext-5.38.0.tgz +0 -0
  24. package/components/tryghost-i18n-5.38.0.tgz +0 -0
  25. package/components/{tryghost-importer-handler-content-files-5.37.0.tgz → tryghost-importer-handler-content-files-5.38.0.tgz} +0 -0
  26. package/components/tryghost-importer-revue-5.38.0.tgz +0 -0
  27. package/components/tryghost-job-manager-5.38.0.tgz +0 -0
  28. package/components/tryghost-link-redirects-5.38.0.tgz +0 -0
  29. package/components/tryghost-link-replacer-5.38.0.tgz +0 -0
  30. package/components/{tryghost-link-tracking-5.37.0.tgz → tryghost-link-tracking-5.38.0.tgz} +0 -0
  31. package/components/{tryghost-magic-link-5.37.0.tgz → tryghost-magic-link-5.38.0.tgz} +0 -0
  32. package/components/tryghost-mailgun-client-5.38.0.tgz +0 -0
  33. package/components/tryghost-member-attribution-5.38.0.tgz +0 -0
  34. package/components/tryghost-member-events-5.38.0.tgz +0 -0
  35. package/components/tryghost-members-api-5.38.0.tgz +0 -0
  36. package/components/tryghost-members-csv-5.38.0.tgz +0 -0
  37. package/components/{tryghost-members-events-service-5.37.0.tgz → tryghost-members-events-service-5.38.0.tgz} +0 -0
  38. package/components/{tryghost-members-importer-5.37.0.tgz → tryghost-members-importer-5.38.0.tgz} +0 -0
  39. package/components/tryghost-members-offers-5.38.0.tgz +0 -0
  40. package/components/tryghost-members-payments-5.38.0.tgz +0 -0
  41. package/components/tryghost-members-ssr-5.38.0.tgz +0 -0
  42. package/components/{tryghost-members-stripe-service-5.37.0.tgz → tryghost-members-stripe-service-5.38.0.tgz} +0 -0
  43. package/components/tryghost-milestones-5.38.0.tgz +0 -0
  44. package/components/tryghost-minifier-5.38.0.tgz +0 -0
  45. package/components/tryghost-mw-api-version-mismatch-5.38.0.tgz +0 -0
  46. package/components/{tryghost-mw-cache-control-5.37.0.tgz → tryghost-mw-cache-control-5.38.0.tgz} +0 -0
  47. package/components/tryghost-mw-error-handler-5.38.0.tgz +0 -0
  48. package/components/tryghost-mw-session-from-token-5.38.0.tgz +0 -0
  49. package/components/tryghost-mw-update-user-last-seen-5.38.0.tgz +0 -0
  50. package/components/tryghost-mw-version-match-5.38.0.tgz +0 -0
  51. package/components/tryghost-mw-vhost-5.38.0.tgz +0 -0
  52. package/components/tryghost-oembed-service-5.38.0.tgz +0 -0
  53. package/components/tryghost-package-json-5.38.0.tgz +0 -0
  54. package/components/{tryghost-referrers-5.37.0.tgz → tryghost-referrers-5.38.0.tgz} +0 -0
  55. package/components/tryghost-security-5.38.0.tgz +0 -0
  56. package/components/tryghost-session-service-5.38.0.tgz +0 -0
  57. package/components/tryghost-settings-path-manager-5.38.0.tgz +0 -0
  58. package/components/tryghost-slack-notifications-5.38.0.tgz +0 -0
  59. package/components/tryghost-staff-service-5.38.0.tgz +0 -0
  60. package/components/tryghost-stats-service-5.38.0.tgz +0 -0
  61. package/components/tryghost-tiers-5.38.0.tgz +0 -0
  62. package/components/{tryghost-update-check-service-5.37.0.tgz → tryghost-update-check-service-5.38.0.tgz} +0 -0
  63. package/components/tryghost-verification-trigger-5.38.0.tgz +0 -0
  64. package/components/{tryghost-version-notifications-data-service-5.37.0.tgz → tryghost-version-notifications-data-service-5.38.0.tgz} +0 -0
  65. package/components/tryghost-webmentions-5.38.0.tgz +0 -0
  66. package/core/boot.js +11 -4
  67. package/core/built/admin/assets/{chunk.143.27cd10a38f877e715b35.js → chunk.143.c6802c882a911797ce4f.js} +6 -6
  68. package/core/built/admin/assets/{chunk.178.dd6cf17fb0986acf19d6.js → chunk.178.09faefd4027fcba4113d.js} +4 -4
  69. package/core/built/admin/assets/{chunk.652.bb618bc5abf23bed4e87.js → chunk.220.9ca2950240aba3fced21.js} +1836 -1774
  70. package/core/built/admin/assets/{chunk.79.4a959c324df25480b90e.js → chunk.79.acb7dd01e1c785f4920c.js} +12 -11
  71. package/core/built/admin/assets/{ghost-2948791640be026b987b88f89034bc85.js → ghost-35103ff053c43f1dfa7f35821c3c2412.js} +29 -29
  72. package/core/built/admin/assets/{ghost-efbe4dcc249d119a955b038aae5c980d.css → ghost-a9307c9cfe26a4bc621e02cd3bae421a.css} +1 -1
  73. package/core/built/admin/assets/{ghost-dark-6ea4b338f17a43c204b7c1e207b90cd7.css → ghost-dark-f309cf445255344e4861a95ecb8f1920.css} +1 -1
  74. package/core/built/admin/assets/vendor-b982e3bf1020bff77b2a3c44d5f59e55.js +269 -269
  75. package/core/built/admin/index.html +5 -5
  76. package/core/frontend/helpers/ghost_head.js +4 -1
  77. package/core/frontend/services/routing/StaticPagesRouter.js +1 -1
  78. package/core/frontend/services/sitemap/base-generator.js +5 -1
  79. package/core/server/adapters/storage/LocalImagesStorage.js +1 -1
  80. package/core/server/api/endpoints/email-previews.js +2 -43
  81. package/core/server/api/endpoints/emails.js +1 -22
  82. package/core/server/api/endpoints/utils/serializers/output/mappers/emails.js +14 -8
  83. package/core/server/data/importer/import-manager.js +8 -1
  84. package/core/server/data/migrations/versions/4.9/05-fix-missed-mobiledoc-url-transforms.js +1 -1
  85. package/core/server/lib/common/events.js +16 -23
  86. package/core/server/models/base/plugins/relations.js +5 -3
  87. package/core/server/models/index.js +5 -0
  88. package/core/server/services/comments/emails.js +2 -2
  89. package/core/server/services/email-service/wrapper.js +2 -0
  90. package/core/server/services/link-tracking/LinkClickRepository.js +1 -1
  91. package/core/server/services/media-inliner/service.js +49 -3
  92. package/core/server/services/mentions/service.js +6 -1
  93. package/core/server/services/posts/posts-service.js +3 -14
  94. package/core/server/services/staff/index.js +2 -0
  95. package/core/server/services/url/Urls.js +10 -2
  96. package/core/shared/labs.js +0 -1
  97. package/package.json +138 -138
  98. package/yarn.lock +267 -259
  99. package/components/tryghost-adapter-cache-redis-5.37.0.tgz +0 -0
  100. package/components/tryghost-audience-feedback-5.37.0.tgz +0 -0
  101. package/components/tryghost-bootstrap-socket-5.37.0.tgz +0 -0
  102. package/components/tryghost-custom-theme-settings-service-5.37.0.tgz +0 -0
  103. package/components/tryghost-domain-events-5.37.0.tgz +0 -0
  104. package/components/tryghost-dynamic-routing-events-5.37.0.tgz +0 -0
  105. package/components/tryghost-email-analytics-provider-mailgun-5.37.0.tgz +0 -0
  106. package/components/tryghost-email-content-generator-5.37.0.tgz +0 -0
  107. package/components/tryghost-email-events-5.37.0.tgz +0 -0
  108. package/components/tryghost-email-service-5.37.0.tgz +0 -0
  109. package/components/tryghost-event-aware-cache-wrapper-5.37.0.tgz +0 -0
  110. package/components/tryghost-external-media-inliner-5.37.0.tgz +0 -0
  111. package/components/tryghost-extract-api-key-5.37.0.tgz +0 -0
  112. package/components/tryghost-html-to-plaintext-5.37.0.tgz +0 -0
  113. package/components/tryghost-i18n-5.37.0.tgz +0 -0
  114. package/components/tryghost-importer-revue-5.37.0.tgz +0 -0
  115. package/components/tryghost-job-manager-5.37.0.tgz +0 -0
  116. package/components/tryghost-link-redirects-5.37.0.tgz +0 -0
  117. package/components/tryghost-link-replacer-5.37.0.tgz +0 -0
  118. package/components/tryghost-mailgun-client-5.37.0.tgz +0 -0
  119. package/components/tryghost-member-attribution-5.37.0.tgz +0 -0
  120. package/components/tryghost-member-events-5.37.0.tgz +0 -0
  121. package/components/tryghost-members-api-5.37.0.tgz +0 -0
  122. package/components/tryghost-members-csv-5.37.0.tgz +0 -0
  123. package/components/tryghost-members-offers-5.37.0.tgz +0 -0
  124. package/components/tryghost-members-payments-5.37.0.tgz +0 -0
  125. package/components/tryghost-members-ssr-5.37.0.tgz +0 -0
  126. package/components/tryghost-milestones-5.37.0.tgz +0 -0
  127. package/components/tryghost-minifier-5.37.0.tgz +0 -0
  128. package/components/tryghost-mw-api-version-mismatch-5.37.0.tgz +0 -0
  129. package/components/tryghost-mw-error-handler-5.37.0.tgz +0 -0
  130. package/components/tryghost-mw-session-from-token-5.37.0.tgz +0 -0
  131. package/components/tryghost-mw-update-user-last-seen-5.37.0.tgz +0 -0
  132. package/components/tryghost-mw-version-match-5.37.0.tgz +0 -0
  133. package/components/tryghost-mw-vhost-5.37.0.tgz +0 -0
  134. package/components/tryghost-oembed-service-5.37.0.tgz +0 -0
  135. package/components/tryghost-package-json-5.37.0.tgz +0 -0
  136. package/components/tryghost-security-5.37.0.tgz +0 -0
  137. package/components/tryghost-session-service-5.37.0.tgz +0 -0
  138. package/components/tryghost-settings-path-manager-5.37.0.tgz +0 -0
  139. package/components/tryghost-slack-notifications-5.37.0.tgz +0 -0
  140. package/components/tryghost-staff-service-5.37.0.tgz +0 -0
  141. package/components/tryghost-stats-service-5.37.0.tgz +0 -0
  142. package/components/tryghost-tiers-5.37.0.tgz +0 -0
  143. package/components/tryghost-verification-trigger-5.37.0.tgz +0 -0
  144. package/components/tryghost-webmentions-5.37.0.tgz +0 -0
  145. package/core/server/services/bulk-email/bulk-email-processor.js +0 -289
  146. package/core/server/services/bulk-email/index.js +0 -1
  147. package/core/server/services/mega/email-preview.js +0 -54
  148. package/core/server/services/mega/feedback-buttons.js +0 -66
  149. package/core/server/services/mega/index.js +0 -14
  150. package/core/server/services/mega/mega.js +0 -626
  151. package/core/server/services/mega/post-email-serializer.js +0 -559
  152. package/core/server/services/mega/segment-parser.js +0 -20
  153. package/core/server/services/mega/template.js +0 -1319
  154. /package/core/built/admin/assets/{chunk.652.bb618bc5abf23bed4e87.js.LICENSE.txt → chunk.220.9ca2950240aba3fced21.js.LICENSE.txt} +0 -0
Binary file
@@ -1,289 +0,0 @@
1
- const _ = require('lodash');
2
- const Promise = require('bluebird');
3
- const moment = require('moment-timezone');
4
- const errors = require('@tryghost/errors');
5
- const tpl = require('@tryghost/tpl');
6
- const logging = require('@tryghost/logging');
7
- const models = require('../../models');
8
- const MailgunClient = require('@tryghost/mailgun-client');
9
- const sentry = require('../../../shared/sentry');
10
- const debug = require('@tryghost/debug')('mega');
11
- const postEmailSerializer = require('../mega/post-email-serializer');
12
- const configService = require('../../../shared/config');
13
- const settingsCache = require('../../../shared/settings-cache');
14
-
15
- async function sleep(ms) {
16
- return new Promise((resolve) => {
17
- setTimeout(resolve, ms);
18
- });
19
- }
20
-
21
- const messages = {
22
- error: 'The email service received an error from mailgun and was unable to send.'
23
- };
24
-
25
- const mailgunClient = new MailgunClient({config: configService, settings: settingsCache});
26
-
27
- /**
28
- * An object representing batch request result
29
- * @typedef { Object } BatchResultBase
30
- * @property { string } data - data that is returned from Mailgun or one which Mailgun was called with
31
- */
32
- class BatchResultBase {
33
- constructor(id) {
34
- this.id = id;
35
- }
36
- }
37
-
38
- class SuccessfulBatch extends BatchResultBase { }
39
-
40
- class FailedBatch extends BatchResultBase {
41
- constructor(id, error) {
42
- super(...arguments);
43
- error.originalMessage = error.message;
44
-
45
- if (error.statusCode >= 500) {
46
- error.message = 'Email service is currently unavailable - please try again';
47
- } else if (error.statusCode === 401) {
48
- error.message = 'Email failed to send - please verify your credentials';
49
- } else if (error.message && error.message.toLowerCase().includes('dmarc')) {
50
- error.message = 'Unable to send email from domains implementing strict DMARC policies';
51
- } else if (error.message.includes(`'to' parameter is not a valid address`)) {
52
- error.message = 'Recipient is not a valid address';
53
- } else {
54
- error.message = `Email failed to send "${error.originalMessage}" - please verify your email settings`;
55
- }
56
-
57
- this.error = error;
58
- }
59
- }
60
-
61
- /**
62
- * An email address
63
- * @typedef { string } EmailAddress
64
- */
65
-
66
- /**
67
- * An object representing an email to send
68
- * @typedef { Object } Email
69
- * @property { string } html - The html content of the email
70
- * @property { string } subject - The subject of the email
71
- */
72
-
73
- module.exports = {
74
- BATCH_SIZE: MailgunClient.BATCH_SIZE,
75
- SuccessfulBatch,
76
- FailedBatch,
77
-
78
- // accepts an ID rather than an Email model to better support running via a job queue
79
- async processEmail({emailModel, options}) {
80
- const knexOptions = _.pick(options, ['transacting', 'forUpdate']);
81
- const emailId = emailModel.get('id');
82
-
83
- // get batch IDs via knex to avoid model instantiation
84
- // only fetch pending or failed batches to avoid re-sending previously sent emails
85
- const batchIds = await models.EmailBatch
86
- .getFilteredCollectionQuery({filter: `email_id:${emailId}+status:[pending,failed]`}, knexOptions)
87
- .select('id', 'member_segment');
88
-
89
- const batchResults = await Promise.map(batchIds, async ({id: emailBatchId, member_segment: memberSegment}) => {
90
- try {
91
- await this.processEmailBatch({emailBatchId, options, memberSegment});
92
- return new SuccessfulBatch(emailBatchId);
93
- } catch (error) {
94
- return new FailedBatch(emailBatchId, error);
95
- }
96
- }, {concurrency: 2});
97
-
98
- const successes = batchResults.filter(response => (response instanceof SuccessfulBatch));
99
- const failures = batchResults.filter(response => (response instanceof FailedBatch));
100
- const emailStatus = failures.length ? 'failed' : 'submitted';
101
-
102
- let error;
103
-
104
- if (failures.length) {
105
- error = failures[0].error.message;
106
- }
107
-
108
- if (error && error.length > 2000) {
109
- error = error.substring(0, 2000);
110
- }
111
-
112
- try {
113
- await models.Email.edit({
114
- status: emailStatus,
115
- results: JSON.stringify(successes),
116
- error: error,
117
- error_data: JSON.stringify(failures) // NOTE: need to discuss how we store this
118
- }, {
119
- id: emailModel.id
120
- });
121
- } catch (err) {
122
- sentry.captureException(err);
123
- logging.error(err);
124
- }
125
-
126
- return batchResults;
127
- },
128
-
129
- // accepts an ID rather than an EmailBatch model to better support running via a job queue
130
- async processEmailBatch({emailBatchId, options, memberSegment}) {
131
- logging.info('[sendEmailJob] Processing email batch ' + emailBatchId);
132
-
133
- const knexOptions = _.pick(options, ['transacting', 'forUpdate']);
134
-
135
- const emailBatchModel = await models.EmailBatch
136
- .findOne({id: emailBatchId}, Object.assign({}, knexOptions, {withRelated: 'email'}));
137
-
138
- if (!emailBatchModel) {
139
- throw new errors.IncorrectUsageError({
140
- message: 'Provided email_batch id does not match a known email_batch record',
141
- context: {
142
- id: emailBatchId
143
- }
144
- });
145
- }
146
-
147
- if (!['pending','failed'].includes(emailBatchModel.get('status'))) {
148
- throw new errors.IncorrectUsageError({
149
- message: 'Email batches can only be processed when in the "pending" or "failed" state',
150
- context: `Email batch "${emailBatchId}" has state "${emailBatchModel.get('status')}"`
151
- });
152
- }
153
-
154
- // Patch to prevent saving the related email model
155
- await emailBatchModel.save({status: 'submitting'}, {...knexOptions, patch: true});
156
-
157
- try {
158
- // get recipient rows via knex to avoid costly bookshelf model instantiation
159
- let recipientRows = await models.EmailRecipient.getFilteredCollectionQuery({filter: `batch_id:${emailBatchId}`}, knexOptions);
160
-
161
- // For an unknown reason, the returned recipient rows is sometimes an empty array
162
- // refs https://github.com/TryGhost/Team/issues/2246
163
- let counter = 0;
164
- while (recipientRows.length === 0 && counter < 5) {
165
- logging.info('[sendEmailJob] Found zero recipients [retries:' + counter + '] for email batch ' + emailBatchId);
166
-
167
- counter += 1;
168
- await sleep(200);
169
- recipientRows = await models.EmailRecipient.getFilteredCollectionQuery({filter: `batch_id:${emailBatchId}`}, knexOptions);
170
- }
171
- if (counter > 0) {
172
- logging.info('[sendEmailJob] Recovered recipients [retries:' + counter + '] for email batch ' + emailBatchId + ' - ' + recipientRows.length + ' recipients found');
173
- }
174
-
175
- // Load newsletter data on email
176
- await emailBatchModel.relations.email.getLazyRelation('newsletter', {require: false, ...knexOptions});
177
-
178
- // Load post data on email - for content gating on paywall
179
- await emailBatchModel.relations.email.getLazyRelation('post', {require: false, ...knexOptions});
180
-
181
- // send the email
182
- const sendResponse = await this.send(emailBatchModel.relations.email.toJSON(), recipientRows, memberSegment);
183
-
184
- logging.info('[sendEmailJob] Submitted email batch ' + emailBatchId);
185
-
186
- // update batch success status
187
- return await emailBatchModel.save({
188
- status: 'submitted',
189
- provider_id: sendResponse.id.trim().replace(/^<|>$/g, '')
190
- }, Object.assign({}, knexOptions, {patch: true}));
191
- } catch (error) {
192
- logging.info('[sendEmailJob] Failed email batch ' + emailBatchId);
193
-
194
- // update batch failed status
195
- await emailBatchModel.save({status: 'failed'}, {...knexOptions, patch: true});
196
-
197
- // log any error that didn't come from the provider which would have already logged it
198
- if (!error.code || error.code !== 'BULK_EMAIL_SEND_FAILED') {
199
- let ghostError = new errors.EmailError({
200
- err: error,
201
- code: 'BULK_EMAIL_SEND_FAILED',
202
- message: `Error sending email batch ${emailBatchId}`,
203
- context: error.message
204
- });
205
- sentry.captureException(ghostError);
206
- logging.error(ghostError);
207
- throw ghostError;
208
- }
209
-
210
- throw error;
211
- } finally {
212
- // update all email recipients with a processed_at
213
- await models.EmailRecipient
214
- .where({batch_id: emailBatchId})
215
- .save({processed_at: moment()}, Object.assign({}, knexOptions, {autoRefresh: false, patch: true}));
216
- }
217
- },
218
-
219
- /**
220
- * @param {Email-like} emailData - The email to send, must be a POJO so emailModel.toJSON() before calling if needed
221
- * @param {EmailRecipient[]} recipients - The recipients to send the email to with their associated data
222
- * @param {string?} memberSegment - The member segment of the recipients
223
- * @returns {Promise<Object>} - {providerId: 'xxx'}
224
- */
225
- async send(emailData, recipients, memberSegment) {
226
- logging.info(`[sendEmailJob] Sending email batch to ${recipients.length} recipients`);
227
-
228
- const mailgunConfigured = mailgunClient.isConfigured();
229
- if (!mailgunConfigured) {
230
- logging.warn('Bulk email has not been configured');
231
- return;
232
- }
233
-
234
- const startTime = Date.now();
235
- debug(`sending message to ${recipients.length} recipients`);
236
-
237
- // Update email content for this segment before searching replacements
238
- emailData = postEmailSerializer.renderEmailForSegment(emailData, memberSegment);
239
-
240
- // Check all the used replacements in this email
241
- const replacements = postEmailSerializer.parseReplacements(emailData);
242
-
243
- // collate static and dynamic data for each recipient ready for provider
244
- const recipientData = {};
245
- const newsletterUuid = emailData.newsletter ? emailData.newsletter.uuid : null;
246
- recipients.forEach((recipient) => {
247
- // static data for every recipient
248
- const data = {
249
- unique_id: recipient.member_uuid,
250
- unsubscribe_url: postEmailSerializer.createUnsubscribeUrl(recipient.member_uuid, {newsletterUuid})
251
- };
252
-
253
- // computed properties on recipients - TODO: better way of handling these
254
- recipient.member_first_name = (recipient.member_name || '').split(' ')[0];
255
-
256
- // dynamic data from replacements
257
- replacements.forEach(({id, recipientProperty, fallback}) => {
258
- data[id] = recipient[recipientProperty] || fallback || '';
259
- });
260
-
261
- recipientData[recipient.member_email] = data;
262
- });
263
-
264
- try {
265
- const response = await mailgunClient.send(emailData, recipientData, replacements);
266
- debug(`sent message (${Date.now() - startTime}ms)`);
267
- logging.info(`[sendEmailJob] Sent message (${Date.now() - startTime}ms)`);
268
- return response;
269
- } catch (err) {
270
- let ghostError = new errors.EmailError({
271
- err,
272
- message: tpl(messages.error),
273
- context: `Mailgun Error ${err.error.status}: ${err.error.details}`,
274
- // REF: possible mailgun errors https://documentation.mailgun.com/en/latest/api-intro.html#errors
275
- help: `https://ghost.org/docs/newsletters/#bulk-email-configuration`,
276
- code: 'BULK_EMAIL_SEND_FAILED'
277
- });
278
-
279
- sentry.captureException(ghostError);
280
- logging.error(ghostError);
281
-
282
- debug(`failed to send message (${Date.now() - startTime}ms)`);
283
- throw ghostError;
284
- }
285
- },
286
-
287
- // NOTE: for testing only!
288
- _mailgunClient: mailgunClient
289
- };
@@ -1 +0,0 @@
1
- module.exports = require('./bulk-email-processor');
@@ -1,54 +0,0 @@
1
- const postEmailSerializer = require('./post-email-serializer');
2
- const models = require('../../models');
3
-
4
- class EmailPreview {
5
- /**
6
- * @param {Object} post - Post model object instance
7
- * @param {Object} options
8
- * @param {String} options.newsletter - newsletter slug
9
- * @param {String} options.memberSegment - member segment filter
10
- * @returns {Promise<Object>}
11
- */
12
- async generateEmailContent(post, {newsletter, memberSegment} = {}) {
13
- let newsletterModel = await post.getLazyRelation('newsletter');
14
- if (!newsletterModel) {
15
- if (newsletter) {
16
- newsletterModel = await models.Newsletter.findOne({slug: newsletter});
17
- } else {
18
- newsletterModel = await models.Newsletter.getDefaultNewsletter();
19
- }
20
- }
21
-
22
- let emailContent = await postEmailSerializer.serialize(post, newsletterModel, {
23
- isBrowserPreview: true
24
- });
25
-
26
- if (memberSegment) {
27
- emailContent = postEmailSerializer.renderEmailForSegment(emailContent, memberSegment);
28
- }
29
-
30
- // Do fake replacements, just like a normal email, but use fallbacks and empty values
31
- const replacements = postEmailSerializer.parseReplacements(emailContent);
32
-
33
- replacements.forEach((replacement) => {
34
- emailContent[replacement.format] = emailContent[replacement.format].replace(
35
- replacement.regexp,
36
- replacement.fallback || ''
37
- );
38
- });
39
-
40
- // Replace unsubscribe URL (%recipient.unsubscribe_url% replacement)
41
- // We should do this only here because replacements should happen at the very end only, just like when an actual email would be send
42
- const previewUnsubscribeUrl = postEmailSerializer.createUnsubscribeUrl(null);
43
- emailContent.html = emailContent.html.replace('%recipient.unsubscribe_url%', previewUnsubscribeUrl);
44
- emailContent.plaintext = emailContent.plaintext.replace('%recipient.unsubscribe_url%', previewUnsubscribeUrl);
45
-
46
- return {
47
- subject: emailContent.subject,
48
- html: emailContent.html,
49
- plaintext: emailContent.plaintext
50
- };
51
- }
52
- }
53
-
54
- module.exports = EmailPreview;
@@ -1,66 +0,0 @@
1
- const audienceFeedback = require('../audience-feedback');
2
-
3
- const templateStrings = {
4
- like: '%{feedback_button_like}%',
5
- dislike: '%{feedback_button_dislike}%'
6
- };
7
-
8
- const generateLinks = (postId, uuid, html) => {
9
- const positiveLink = audienceFeedback.service.buildLink(
10
- uuid,
11
- postId,
12
- 1
13
- );
14
- const negativeLink = audienceFeedback.service.buildLink(
15
- uuid,
16
- postId,
17
- 0
18
- );
19
-
20
- html = html.replace(new RegExp(templateStrings.like, 'g'), positiveLink.href);
21
- html = html.replace(new RegExp(templateStrings.dislike, 'g'), negativeLink.href);
22
-
23
- return html;
24
- };
25
-
26
- const getTemplate = () => {
27
- const likeButtonHtml = getButtonHtml(
28
- templateStrings.like,
29
- 'More like this',
30
- 'https://static.ghost.org/v5.0.0/images/more-like-this.png'
31
- );
32
- const dislikeButtonHtml = getButtonHtml(
33
- templateStrings.dislike,
34
- 'Less like this',
35
- 'https://static.ghost.org/v5.0.0/images/less-like-this.png'
36
- );
37
-
38
- return (`
39
- <tr>
40
- <td dir="ltr" width="100%" style="background-color: #ffffff; text-align: center; padding: 40px 4px; border-bottom: 1px solid #e5eff5" align="center">
41
- <h3 style="text-align: center; margin-bottom: 22px; font-size: 17px; letter-spacing: -0.2px; margin-top: 0 !important;">Give feedback on this post</h3>
42
- <table role="presentation" border="0" cellpadding="0" cellspacing="0" style="margin: auto; width: auto !important;">
43
- <tr>
44
- ${likeButtonHtml}
45
- ${dislikeButtonHtml}
46
- </tr>
47
- </table>
48
- </td>
49
- </tr>
50
- `);
51
- };
52
-
53
- function getButtonHtml(href, buttonText, iconUrl) {
54
- return (`
55
- <td dir="ltr" valign="top" align="center" style="vertical-align: top; font-family: inherit; font-size: 14px; text-align: center; padding: 0 8px;" nowrap>
56
- <a href="${href}" target="_blank">
57
- <img src="${iconUrl}" border="0" width="156" height="38" alt="${buttonText}">
58
- </a>
59
- </td>
60
- `);
61
- }
62
-
63
- module.exports = {
64
- generateLinks,
65
- getTemplate
66
- };
@@ -1,14 +0,0 @@
1
- module.exports = {
2
- get mega() {
3
- return require('./mega');
4
- },
5
-
6
- get postEmailSerializer() {
7
- return require('./post-email-serializer');
8
- },
9
-
10
- get EmailPreview() {
11
- return require('./email-preview');
12
- }
13
- };
14
-