- {{#if (and @member.name @member.email)}} - {{@member.email}} - {{/if}} -
-- {{svg-jar "member-add"}} - Created — {{moment-format (moment-site-tz @member.createdAtUTC) "D MMM YYYY"}} -
- {{#if this.referrerSource}} -- {{svg-jar "earth"}} - Source — {{this.referrerSource}} -
- {{/if}} - {{#if (and @member.attribution.url @member.attribution.title)}} -- {{svg-jar "posts"}} - Page — {{ @member.attribution.title }} -
- {{!-- Learn more → --}} - {{/if}} -- {{#if @member.name}} - We’ll show {{first-name @member.name}}’s email stats here once they receive their first newsletter. - {{else}} - We’ll show this member’s email stats here once they receive their first newsletter. - {{/if}} -
-Emails received
-Emails opened
-Average open rate
-- {{{this.error.details}}} -
- {{#if this.error.failures}} -{{failure.ref}}{{#if failure.message}}: {{failure.message}}{{/if}}- This is an authentication link to sign into {{this.config.blogTitle}} as {{this.member.email}}, you can send it to them if they need it, or use it to sign into their account for customer support. -
- - -This link is only valid for the next 24 hours
-- You’re about to delete label "{{this.label.name}}". This is permanent! We warned you, k? -
-- - Published - {{#if @post.hasEmail}} - {{#if this.isHovered}} - and sent to {{gh-pluralize @post.email.emailCount "member"}} - {{else}} - and sent - {{/if}} - {{/if}} - -
- {{/unless}} - - {{else}} -- {{#if @post.isScheduled}} - - Scheduled - {{#if this.isHovered}} - - {{#if @post.emailOnly}} - to be sent - {{this.scheduledText}} to {{humanize-recipient-filter @post.emailSegment}} - {{else}} - to be published {{if @post.newsletter "and sent "}} - {{this.scheduledText}} - {{#if @post.newsletter}} - to {{humanize-recipient-filter @post.emailSegment}} - {{/if}} - {{/if}} - - {{/if}} - - {{/if}} - - {{#if @post.isDraft}} - - Draft - - {{/if}} - - {{#if @post.isPublished}} - - Published - {{#if @post.didEmailFail}} - but failed to send newsletter - {{else if @post.hasBeenEmailed}} - and sent - {{#if this.isHovered}} - to {{gh-pluralize @post.email.emailCount "member"}} - {{/if}} - {{/if}} - - {{/if}} - - {{#if @post.isSent}} - - {{#if @post.didEmailFail}} - Failed to send newsletter - {{else}} - Sent - {{#if this.isHovered}} - to {{gh-pluralize @post.email.emailCount "member"}} - {{/if}} - {{/if}} - - {{/if}} -
- {{/unless}} -Already sent
', + plaintext: 'Already sent', + track_opens: false, + submitted_at: new Date(), + newsletter_id: newsletters[0].id, + }, + { context: { internal: true } }, + ); + const batch = await models.EmailBatch.add( + { email_id: email.id, status: 'submitted' }, + { context: { internal: true } }, + ); + for (const member of recipients) { + await models.EmailRecipient.add( + { + email_id: email.id, + batch_id: batch.id, + member_id: member.id, + member_uuid: member.uuid, + member_email: member.email, + processed_at: new Date(), + }, + { context: { internal: true } }, + ); + } + + await agent.delete(`/members/${recipients[0].id}`).expectStatus(204); + await agent.delete(`/members/${recipients[1].id}`).expectStatus(204); + + assert.equal( + await membersService.verificationTrigger.checkVerificationRequired({ + newsletterSend: true, + }), + false, + 'Two removed recipients should be within the threshold', + ); + + // Changing a member's address removes the old one just as deleting the member does + await agent + .put(`/members/${recipients[2].id}/`) + .body({ members: [{ email: 'removed-recipient-3-changed@example.com' }] }) + .expectStatus(200); + + const { body: nextPostBody } = await agent + .post('/posts/') + .body({ posts: [{ title: 'Next send', status: 'draft' }] }) + .expectStatus(201); + const nextPost = nextPostBody.posts[0]; + + const { body } = await agent + .put(`/posts/${nextPost.id}/?newsletter=${newsletters[0].get('slug')}`) + .body({ posts: [{ ...nextPost, status: 'published' }] }) + .expectStatus(403); + + assert.equal(body.errors[0].code, 'EMAIL_VERIFICATION_NEEDED'); + assert.equal(settingsCache.get('email_verification_required'), true); + assert.deepEqual( + receivedWebhookRequests.map(({ body: webhookBody }) => ({ + method: webhookBody.method, + amountTriggered: webhookBody.amountTriggered, + threshold: webhookBody.threshold, + })), + [{ method: 'removed_recipients', amountTriggered: 3, threshold: 2 }], + ); + }); }); }); diff --git a/ghost/core/test/e2e-api/admin/posts-delete-email-data.test.ts b/ghost/core/test/e2e-api/admin/posts-delete-email-data.test.ts new file mode 100644 index 00000000000..4d797599f05 --- /dev/null +++ b/ghost/core/test/e2e-api/admin/posts-delete-email-data.test.ts @@ -0,0 +1,100 @@ +import ObjectId from 'bson-objectid'; +import assert from 'node:assert/strict'; +import { agentProvider, fixtureManager } from '../../utils/e2e-framework'; + +// @ts-expect-error Module has no type declarations. +import models from '../../../core/server/models'; + +type Agent = Awaited