Ghost/ghost/email-service/lib/email-service.js

142 lines
4.8 KiB
JavaScript
Raw Normal View History

/* eslint-disable no-unused-vars */
/**
* @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 MailgunEmailProvider = require('./mailgun-email-provider');
const messages = {
archivedNewsletterError: 'Cannot send email to archived newsletters',
missingNewsletterError: 'The post does not have a newsletter relation'
};
class EmailService {
#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');
}
}
/**
*
* @param {Post} post
* @returns {Promise<Email>}
*/
async createEmail(post) {
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,
subject: this.#emailRenderer.getSubject(post),
from: this.#emailRenderer.getFromAddress(post, newsletter),
replyTo: this.#emailRenderer.getReplyToAddress(post, newsletter),
email_count: await this.#emailSegmenter.getMembersCount(newsletter, emailRecipientFilter),
source: post.get('lexical') || post.get('mobiledoc'),
source_type: post.get('lexical') ? 'lexical' : 'mobiledoc'
});
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;
}
async retryEmail(email) {
await this.checkLimits();
this.#batchSendingService.scheduleEmail(email);
return email;
}
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;