2022-11-21 12:29:53 +03:00
|
|
|
/* eslint-disable no-unused-vars */
|
|
|
|
|
2022-11-23 13:33:44 +03:00
|
|
|
/**
|
|
|
|
* @typedef {object} Post
|
|
|
|
* @typedef {object} Email
|
|
|
|
* @typedef {object} LimitService
|
|
|
|
*/
|
|
|
|
|
|
|
|
const BatchSendingService = require('./batch-sending-service');
|
|
|
|
const errors = require('@tryghost/errors');
|
|
|
|
const tpl = require('@tryghost/tpl');
|
|
|
|
const EmailRenderer = require('./email-renderer');
|
|
|
|
const EmailSegmenter = require('./email-segmenter');
|
|
|
|
|
|
|
|
const messages = {
|
|
|
|
archivedNewsletterError: 'Cannot send email to archived newsletters',
|
|
|
|
missingNewsletterError: 'The post does not have a newsletter relation'
|
|
|
|
};
|
|
|
|
|
2022-11-21 12:29:53 +03:00
|
|
|
class EmailService {
|
2022-11-23 13:33:44 +03:00
|
|
|
#batchSendingService;
|
|
|
|
#models;
|
|
|
|
#settingsCache;
|
|
|
|
#emailRenderer;
|
|
|
|
#emailSegmenter;
|
|
|
|
#limitService;
|
|
|
|
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
* @param {object} dependencies
|
|
|
|
* @param {BatchSendingService} dependencies.batchSendingService
|
|
|
|
* @param {object} dependencies.models
|
|
|
|
* @param {object} dependencies.models.Email
|
|
|
|
* @param {object} dependencies.settingsCache
|
|
|
|
* @param {EmailRenderer} dependencies.emailRenderer
|
|
|
|
* @param {EmailSegmenter} dependencies.emailSegmenter
|
|
|
|
* @param {LimitService} dependencies.limitService
|
|
|
|
*/
|
|
|
|
constructor({
|
|
|
|
batchSendingService,
|
|
|
|
models,
|
|
|
|
settingsCache,
|
|
|
|
emailRenderer,
|
|
|
|
emailSegmenter,
|
|
|
|
limitService
|
|
|
|
}) {
|
|
|
|
this.#batchSendingService = batchSendingService;
|
|
|
|
this.#models = models;
|
|
|
|
this.#settingsCache = settingsCache;
|
|
|
|
this.#emailRenderer = emailRenderer;
|
|
|
|
this.#emailSegmenter = emailSegmenter;
|
|
|
|
this.#limitService = limitService;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
async checkLimits() {
|
|
|
|
// Check host limit for allowed member count and throw error if over limit
|
|
|
|
// - do this even if it's a retry so that there's no way around the limit
|
|
|
|
if (this.#limitService.isLimited('members')) {
|
|
|
|
await this.#limitService.errorIfIsOverLimit('members');
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check host limit for disabled emails or going over emails limit
|
|
|
|
if (this.#limitService.isLimited('emails')) {
|
|
|
|
await this.#limitService.errorIfWouldGoOverLimit('emails');
|
|
|
|
}
|
2022-11-21 12:29:53 +03:00
|
|
|
}
|
|
|
|
|
2022-11-23 13:33:44 +03:00
|
|
|
/**
|
|
|
|
*
|
|
|
|
* @param {Post} post
|
|
|
|
* @returns {Promise<Email>}
|
|
|
|
*/
|
2022-11-21 12:29:53 +03:00
|
|
|
async createEmail(post) {
|
2022-11-23 13:33:44 +03:00
|
|
|
let newsletter = await post.getLazyRelation('newsletter');
|
|
|
|
if (!newsletter) {
|
|
|
|
throw new errors.EmailError({
|
|
|
|
message: tpl(messages.missingNewsletterError)
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
if (newsletter.get('status') !== 'active') {
|
|
|
|
// A post might have been scheduled to an archived newsletter.
|
|
|
|
// Don't send it (people can't unsubscribe any longer).
|
|
|
|
throw new errors.EmailError({
|
|
|
|
message: tpl(messages.archivedNewsletterError)
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
const emailRecipientFilter = post.get('email_recipient_filter');
|
|
|
|
|
|
|
|
const email = await this.#models.Email.add({
|
|
|
|
post_id: post.id,
|
|
|
|
newsletter_id: newsletter.id,
|
|
|
|
status: 'pending',
|
|
|
|
submitted_at: new Date(),
|
|
|
|
track_opens: !!this.#settingsCache.get('email_track_opens'),
|
|
|
|
track_clicks: !!this.#settingsCache.get('email_track_clicks'),
|
|
|
|
feedback_enabled: !!newsletter.get('feedback_enabled'),
|
|
|
|
recipient_filter: emailRecipientFilter,
|
2022-11-29 13:27:17 +03:00
|
|
|
subject: this.#emailRenderer.getSubject(post),
|
2022-11-23 13:33:44 +03:00
|
|
|
from: this.#emailRenderer.getFromAddress(post, newsletter),
|
|
|
|
replyTo: this.#emailRenderer.getReplyToAddress(post, newsletter),
|
2022-11-29 13:27:17 +03:00
|
|
|
email_count: await this.#emailSegmenter.getMembersCount(newsletter, emailRecipientFilter),
|
|
|
|
source: post.get('lexical') || post.get('mobiledoc'),
|
|
|
|
source_type: post.get('lexical') ? 'lexical' : 'mobiledoc'
|
2022-11-23 13:33:44 +03:00
|
|
|
});
|
|
|
|
|
|
|
|
try {
|
|
|
|
await this.checkLimits();
|
|
|
|
this.#batchSendingService.scheduleEmail(email);
|
|
|
|
} catch (e) {
|
|
|
|
await email.save({
|
|
|
|
status: 'failed',
|
|
|
|
error: e.message || 'Something went wrong while scheduling the email'
|
|
|
|
}, {patch: true});
|
|
|
|
}
|
|
|
|
|
|
|
|
return email;
|
2022-11-21 12:29:53 +03:00
|
|
|
}
|
|
|
|
async retryEmail(email) {
|
2022-11-23 13:33:44 +03:00
|
|
|
await this.checkLimits();
|
|
|
|
this.#batchSendingService.scheduleEmail(email);
|
|
|
|
return email;
|
2022-11-21 12:29:53 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
async previewEmail(post, newsletter, segment) {
|
|
|
|
// eslint-disable-next-line no-restricted-syntax
|
|
|
|
throw new Error('Previewing an email has not been implemented yet. Turn off the email stability flag is you need this functionality.');
|
|
|
|
}
|
|
|
|
|
|
|
|
async sendTestEmail(post, newsletter, segment, emails) {
|
|
|
|
// eslint-disable-next-line no-restricted-syntax
|
|
|
|
throw new Error('Sending a test email has not been implemented yet. Turn off the email stability flag is you need this functionality.');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
module.exports = EmailService;
|