-
-
Notifications
You must be signed in to change notification settings - Fork 11.2k
Added paid member welcome email sending #25676
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
WalkthroughMemberRepository.update now conditionally enqueues a paid welcome email via the Outbox when a member's status transitions to "paid". The update derives the request/source context, checks a paid-welcome-email config flag and a source whitelist, and—if a paid welcome template is active—creates an Outbox entry carrying a MemberCreatedEvent with status 'paid' and emits StartOutboxProcessingEvent. Integration and unit tests were added to cover active/inactive/missing paid templates and source-based allow/deny cases. Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
5b45a25 to
67dc363
Compare
e930dff to
02b8ad6
Compare
ghost/core/core/server/services/members/members-api/repositories/MemberRepository.js
Outdated
Show resolved
Hide resolved
ghost/core/core/server/services/members/members-api/repositories/MemberRepository.js
Outdated
Show resolved
Hide resolved
02b8ad6 to
0683c5f
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (1)
ghost/core/test/unit/server/services/members/members-api/repositories/MemberRepository.test.js (1)
557-557: Optional: Remove unnecessary blank line.This blank line appears to serve no purpose in the assertion block. Consider removing it for consistency.
assert.equal(outboxCall.event_type, 'MemberCreatedEvent'); - const payload = JSON.parse(outboxCall.payload);
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
ghost/core/core/server/services/members/members-api/repositories/MemberRepository.js(1 hunks)ghost/core/test/integration/services/member-welcome-emails.test.js(2 hunks)ghost/core/test/unit/server/services/members/members-api/repositories/MemberRepository.test.js(2 hunks)
🧰 Additional context used
🧠 Learnings (7)
📓 Common learnings
Learnt from: troyciesco
Repo: TryGhost/Ghost PR: 25288
File: ghost/core/core/server/services/member-welcome-emails/jobs/lib/process-entries.js:46-64
Timestamp: 2025-11-10T23:10:17.470Z
Learning: In ghost/core/core/server/services/member-welcome-emails/jobs/lib/process-entries.js and the outbox processing flow, entries are marked as PROCESSING before being processed. If a failure occurs after email send but before deletion, the entry remains stuck in PROCESSING state (not reprocessed). This intentional design prevents duplicate emails. Handling stuck PROCESSING entries is planned for a separate PR.
📚 Learning: 2025-11-10T23:10:17.470Z
Learnt from: troyciesco
Repo: TryGhost/Ghost PR: 25288
File: ghost/core/core/server/services/member-welcome-emails/jobs/lib/process-entries.js:46-64
Timestamp: 2025-11-10T23:10:17.470Z
Learning: In ghost/core/core/server/services/member-welcome-emails/jobs/lib/process-entries.js and the outbox processing flow, entries are marked as PROCESSING before being processed. If a failure occurs after email send but before deletion, the entry remains stuck in PROCESSING state (not reprocessed). This intentional design prevents duplicate emails. Handling stuck PROCESSING entries is planned for a separate PR.
Applied to files:
ghost/core/test/integration/services/member-welcome-emails.test.jsghost/core/test/unit/server/services/members/members-api/repositories/MemberRepository.test.jsghost/core/core/server/services/members/members-api/repositories/MemberRepository.js
📚 Learning: 2025-03-13T09:00:20.205Z
Learnt from: mike182uk
Repo: TryGhost/Ghost PR: 22471
File: apps/admin-x-activitypub/src/utils/pending-activity.ts:13-71
Timestamp: 2025-03-13T09:00:20.205Z
Learning: The pending activity utilities in the Ghost ActivityPub module are covered by tests in the file `apps/admin-x-activitypub/test/unit/utils/pending-activity.ts`.
Applied to files:
ghost/core/test/integration/services/member-welcome-emails.test.jsghost/core/test/unit/server/services/members/members-api/repositories/MemberRepository.test.js
📚 Learning: 2025-03-13T09:00:20.205Z
Learnt from: mike182uk
Repo: TryGhost/Ghost PR: 22471
File: apps/admin-x-activitypub/src/utils/pending-activity.ts:13-71
Timestamp: 2025-03-13T09:00:20.205Z
Learning: The pending activity utilities in Ghost's ActivityPub module are thoroughly tested in `apps/admin-x-activitypub/test/unit/utils/pending-activity.ts`, which covers `generatePendingActivityId`, `isPendingActivity`, and `generatePendingActivity` functions.
Applied to files:
ghost/core/test/integration/services/member-welcome-emails.test.js
📚 Learning: 2025-11-24T17:29:43.865Z
Learnt from: CR
Repo: TryGhost/Ghost PR: 0
File: e2e/AGENTS.md:0-0
Timestamp: 2025-11-24T17:29:43.865Z
Learning: Applies to e2e/**/*.test.ts : Use factory pattern for all test data creation instead of hard-coded data or direct database manipulation
Applied to files:
ghost/core/test/integration/services/member-welcome-emails.test.js
📚 Learning: 2025-08-11T19:39:00.428Z
Learnt from: kevinansfield
Repo: TryGhost/Ghost PR: 24651
File: ghost/core/test/utils/urlUtils.js:53-57
Timestamp: 2025-08-11T19:39:00.428Z
Learning: In Ghost's test utilities, when fixing specific issues like async behavior, it's preferred to maintain existing error handling patterns (even if suboptimal) to keep PRs focused on their primary objective. Error handling improvements can be addressed in separate, dedicated PRs.
Applied to files:
ghost/core/core/server/services/members/members-api/repositories/MemberRepository.js
📚 Learning: 2025-11-24T11:12:15.712Z
Learnt from: sagzy
Repo: TryGhost/Ghost PR: 24673
File: ghost/i18n/lib/i18n.js:34-35
Timestamp: 2025-11-24T11:12:15.712Z
Learning: In the Ghost i18n package (ghost/i18n/lib/i18n.js), changing existing locale codes requires backwards compatibility handling for users who have already configured those locales. Such changes should be done in a separate PR with migration logic rather than included in feature PRs.
Applied to files:
ghost/core/core/server/services/members/members-api/repositories/MemberRepository.js
🧬 Code graph analysis (1)
ghost/core/test/integration/services/member-welcome-emails.test.js (5)
ghost/core/core/server/services/outbox/jobs/lib/process-outbox.js (1)
db(2-2)ghost/core/core/server/services/members/members-api/repositories/MemberRepository.js (1)
ObjectId(7-7)ghost/core/core/server/services/member-welcome-emails/constants.js (1)
MEMBER_WELCOME_EMAIL_SLUGS(3-6)ghost/core/core/boot.js (1)
models(93-93)ghost/core/core/server/models/outbox.js (1)
OUTBOX_STATUSES(3-8)
🔇 Additional comments (5)
ghost/core/core/server/services/members/members-api/repositories/MemberRepository.js (1)
1423-1447: LGTM: Paid welcome email logic properly integrated.The implementation correctly:
- Checks config flag and source whitelist before proceeding
- Lazily fetches the paid welcome email only when needed
- Validates the email is active before creating the outbox entry
- Uses
new Date()for timestamp, which is appropriate since this represents a real-time status transition (unlike the create flow where historical timestamps may be preserved)- Dispatches the processing event with proper transactional options
ghost/core/test/unit/server/services/members/members-api/repositories/MemberRepository.test.js (1)
717-1036: LGTM: Comprehensive test coverage for paid welcome email outbox integration.The new test suite properly validates:
- ✓ Outbox entry creation when member status transitions to paid with all required payload fields
- ✓ Skipping outbox creation when config is not set (preventing unintended sends)
- ✓ Skipping for disallowed sources (import, admin, api) to respect source whitelist
- ✓ Skipping when paid welcome email template is inactive
The test setup with comprehensive mocks follows existing patterns and allows each test to override specific behaviors as needed. All assertions are appropriate and thorough.
ghost/core/test/integration/services/member-welcome-emails.test.js (3)
50-58: LGTM: Paid welcome email fixture properly configured.The fixture setup correctly mirrors the free email pattern and uses the appropriate constant for the slug. The subject line clearly distinguishes this as the paid member welcome email.
65-65: LGTM: Cleanup properly maintains test isolation.The paid email cleanup follows the existing pattern and ensures proper test isolation.
250-296: LGTM: Integration tests properly validate paid welcome email edge cases.Both test cases correctly verify that:
- ✓ No email is sent when the paid template is inactive (lines 250-273)
- ✓ No email is sent when the paid template doesn't exist (lines 275-296)
- ✓ Outbox entries remain with appropriate status/messages for audit trail
- ✓ GhostMailer.send is never invoked in these scenarios
The tests follow the established pattern from the free email tests and align with the learned behavior where entries remain in the outbox when processing cannot complete, preventing duplicate sends.
69a0905 to
406a8de
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (1)
ghost/core/test/integration/services/member-welcome-emails.test.js (1)
275-296: Consider adding a happy path test for paid welcome emails.While the negative test cases (inactive and missing templates) are well-covered, there's no integration test verifying that a paid welcome email is successfully sent when the template is active and a member upgrades to paid status. Consider adding a test similar to the existing ones but with status: 'paid' and an active paid template to ensure end-to-end functionality.
📜 Review details
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
ghost/core/core/server/services/members/members-api/repositories/MemberRepository.js(1 hunks)ghost/core/test/integration/services/member-welcome-emails.test.js(2 hunks)ghost/core/test/unit/server/services/members/members-api/repositories/MemberRepository.test.js(2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- ghost/core/core/server/services/members/members-api/repositories/MemberRepository.js
🧰 Additional context used
🧠 Learnings (5)
📓 Common learnings
Learnt from: troyciesco
Repo: TryGhost/Ghost PR: 25288
File: ghost/core/core/server/services/member-welcome-emails/jobs/lib/process-entries.js:46-64
Timestamp: 2025-11-10T23:10:17.470Z
Learning: In ghost/core/core/server/services/member-welcome-emails/jobs/lib/process-entries.js and the outbox processing flow, entries are marked as PROCESSING before being processed. If a failure occurs after email send but before deletion, the entry remains stuck in PROCESSING state (not reprocessed). This intentional design prevents duplicate emails. Handling stuck PROCESSING entries is planned for a separate PR.
📚 Learning: 2025-11-10T23:10:17.470Z
Learnt from: troyciesco
Repo: TryGhost/Ghost PR: 25288
File: ghost/core/core/server/services/member-welcome-emails/jobs/lib/process-entries.js:46-64
Timestamp: 2025-11-10T23:10:17.470Z
Learning: In ghost/core/core/server/services/member-welcome-emails/jobs/lib/process-entries.js and the outbox processing flow, entries are marked as PROCESSING before being processed. If a failure occurs after email send but before deletion, the entry remains stuck in PROCESSING state (not reprocessed). This intentional design prevents duplicate emails. Handling stuck PROCESSING entries is planned for a separate PR.
Applied to files:
ghost/core/test/integration/services/member-welcome-emails.test.jsghost/core/test/unit/server/services/members/members-api/repositories/MemberRepository.test.js
📚 Learning: 2025-03-13T09:00:20.205Z
Learnt from: mike182uk
Repo: TryGhost/Ghost PR: 22471
File: apps/admin-x-activitypub/src/utils/pending-activity.ts:13-71
Timestamp: 2025-03-13T09:00:20.205Z
Learning: The pending activity utilities in the Ghost ActivityPub module are covered by tests in the file `apps/admin-x-activitypub/test/unit/utils/pending-activity.ts`.
Applied to files:
ghost/core/test/integration/services/member-welcome-emails.test.js
📚 Learning: 2025-03-13T09:00:20.205Z
Learnt from: mike182uk
Repo: TryGhost/Ghost PR: 22471
File: apps/admin-x-activitypub/src/utils/pending-activity.ts:13-71
Timestamp: 2025-03-13T09:00:20.205Z
Learning: The pending activity utilities in Ghost's ActivityPub module are thoroughly tested in `apps/admin-x-activitypub/test/unit/utils/pending-activity.ts`, which covers `generatePendingActivityId`, `isPendingActivity`, and `generatePendingActivity` functions.
Applied to files:
ghost/core/test/integration/services/member-welcome-emails.test.js
📚 Learning: 2025-11-24T17:29:43.865Z
Learnt from: CR
Repo: TryGhost/Ghost PR: 0
File: e2e/AGENTS.md:0-0
Timestamp: 2025-11-24T17:29:43.865Z
Learning: Applies to e2e/**/*.test.ts : Use factory pattern for all test data creation instead of hard-coded data or direct database manipulation
Applied to files:
ghost/core/test/integration/services/member-welcome-emails.test.js
🧬 Code graph analysis (1)
ghost/core/test/unit/server/services/members/members-api/repositories/MemberRepository.test.js (4)
ghost/core/core/server/models/outbox.js (1)
Outbox(10-19)ghost/core/core/server/services/members/members-api/repositories/MemberRepository.js (2)
subscriptionData(1057-1087)config(11-11)ghost/core/core/boot.js (1)
config(445-445)ghost/core/core/server/services/outbox/jobs/lib/process-entries.js (1)
payload(51-51)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
- GitHub Check: Legacy tests (Node 22.18.0, sqlite3)
- GitHub Check: Legacy tests (Node 22.18.0, mysql8)
- GitHub Check: Acceptance tests (Node 22.18.0, sqlite3)
- GitHub Check: Ghost-CLI tests
- GitHub Check: Acceptance tests (Node 22.18.0, mysql8)
- GitHub Check: Unit tests (Node 22.18.0)
- GitHub Check: Lint
- GitHub Check: Build & Push Docker Image
🔇 Additional comments (8)
ghost/core/test/integration/services/member-welcome-emails.test.js (3)
50-58: LGTM!The paid template setup mirrors the free template pattern and includes all necessary fields.
65-65: LGTM!Proper cleanup of the paid template ensures tests remain isolated.
250-273: LGTM!The test correctly verifies that inactive paid templates prevent email sending, with appropriate assertions on both the mailer call count and the outbox message.
ghost/core/test/unit/server/services/members/members-api/repositories/MemberRepository.test.js (5)
717-849: LGTM!The test suite setup is comprehensive with appropriate mocks for all dependencies. The mock structures correctly simulate Bookshelf models and Stripe objects.
855-900: LGTM!The test correctly simulates a member upgrade scenario (free → paid) and thoroughly validates the outbox payload structure, including all required fields and timestamp.
902-940: LGTM!The test correctly verifies that the paid welcome email feature respects the configuration flag and doesn't create outbox entries when the config is not set.
942-988: LGTM!The test comprehensively validates source filtering, ensuring paid welcome emails are only sent for member-initiated subscriptions and not for imports, admin actions, or API calls.
990-1035: LGTM!The test correctly verifies that inactive paid welcome email templates prevent outbox entry creation, ensuring the feature respects template activation status.
406a8de to
c7d2f49
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (2)
ghost/core/test/unit/server/services/members/members-api/repositories/MemberRepository.test.js (2)
924-969: Add event_type assertion for completeness.The test thoroughly verifies the payload structure, but it doesn't assert the
event_typefield like the corresponding create test does at line 625. For consistency and completeness, consider adding this assertion.🔎 Apply this diff to add the event_type assertion:
sinon.assert.calledOnce(Outbox.add); +const outboxCall = Outbox.add.firstCall.args[0]; +assert.equal(outboxCall.event_type, 'MemberCreatedEvent'); + -const payload = JSON.parse(Outbox.add.firstCall.args[0].payload); +const payload = JSON.parse(outboxCall.payload); assert.equal(payload.status, 'paid');
1059-1104: LGTM! Consider adding test for missing template.This test correctly verifies that inactive paid welcome emails don't trigger outbox entries. The AutomatedEmail mock override is properly implemented.
While integration tests cover the case where no paid template exists (as noted in the AI summary), consider adding a unit test here where
AutomatedEmail.findOnereturnsnullfor completeness and to catch issues earlier in the test pyramid.Optional: Add test case for missing template
it('does NOT create outbox entry when paid welcome email does not exist', async function () { sinon.stub(config, 'get').withArgs('memberWelcomeEmailTestInbox').returns('test-inbox@example.com'); Member.edit.resolves({ attributes: {status: 'paid'}, _previousAttributes: {status: 'free'}, get: sinon.stub().callsFake((key) => { const data = {status: 'paid'}; return data[key]; }) }); AutomatedEmail.findOne.resolves(null); const repo = new MemberRepository({ Member, Outbox, MemberPaidSubscriptionEvent, StripeCustomerSubscription, MemberProductEvent, MemberStatusEvent, stripeAPIService, productRepository, AutomatedEmail, OfferRedemption: mockOfferRedemption }); sinon.stub(repo, 'getSubscriptionByStripeID').resolves(null); await repo.linkSubscription({ id: 'member_id_123', subscription: subscriptionData }, { transacting: { executionPromise: Promise.resolve() }, context: {} }); sinon.assert.notCalled(Outbox.add); });
📜 Review details
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
ghost/core/core/server/services/members/members-api/repositories/MemberRepository.js(1 hunks)ghost/core/test/integration/services/member-welcome-emails.test.js(2 hunks)ghost/core/test/unit/server/services/members/members-api/repositories/MemberRepository.test.js(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- ghost/core/core/server/services/members/members-api/repositories/MemberRepository.js
- ghost/core/test/integration/services/member-welcome-emails.test.js
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: troyciesco
Repo: TryGhost/Ghost PR: 25288
File: ghost/core/core/server/services/member-welcome-emails/jobs/lib/process-entries.js:46-64
Timestamp: 2025-11-10T23:10:17.470Z
Learning: In ghost/core/core/server/services/member-welcome-emails/jobs/lib/process-entries.js and the outbox processing flow, entries are marked as PROCESSING before being processed. If a failure occurs after email send but before deletion, the entry remains stuck in PROCESSING state (not reprocessed). This intentional design prevents duplicate emails. Handling stuck PROCESSING entries is planned for a separate PR.
📚 Learning: 2025-11-10T23:10:17.470Z
Learnt from: troyciesco
Repo: TryGhost/Ghost PR: 25288
File: ghost/core/core/server/services/member-welcome-emails/jobs/lib/process-entries.js:46-64
Timestamp: 2025-11-10T23:10:17.470Z
Learning: In ghost/core/core/server/services/member-welcome-emails/jobs/lib/process-entries.js and the outbox processing flow, entries are marked as PROCESSING before being processed. If a failure occurs after email send but before deletion, the entry remains stuck in PROCESSING state (not reprocessed). This intentional design prevents duplicate emails. Handling stuck PROCESSING entries is planned for a separate PR.
Applied to files:
ghost/core/test/unit/server/services/members/members-api/repositories/MemberRepository.test.js
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
- GitHub Check: Acceptance tests (Node 22.18.0, mysql8)
- GitHub Check: Acceptance tests (Node 22.18.0, sqlite3)
- GitHub Check: Ghost-CLI tests
- GitHub Check: Legacy tests (Node 22.18.0, sqlite3)
- GitHub Check: Legacy tests (Node 22.18.0, mysql8)
- GitHub Check: Unit tests (Node 22.18.0)
- GitHub Check: Lint
- GitHub Check: Build & Push Docker Image
🔇 Additional comments (4)
ghost/core/test/unit/server/services/members/members-api/repositories/MemberRepository.test.js (4)
786-797: LGTM!The test suite setup is well-structured and declares all necessary dependencies for testing the paid welcome email outbox integration.
798-918: LGTM!Comprehensive beforeEach setup with appropriate mocks for all dependencies. The mock structure correctly mirrors the production models and allows for flexible test scenarios.
971-1009: LGTM!This test correctly verifies that the outbox entry is not created when the configuration flag is missing, ensuring the feature respects the configuration settings.
1011-1057: LGTM!The test properly validates that paid welcome emails are not sent for disallowed sources (import, admin, api). The iteration pattern with history reset is the correct approach for testing multiple scenarios.
React E2E Tests FailedTo view the Playwright test report locally, run: REPORT_DIR=$(mktemp -d) && gh run download 20341960495 -n playwright-report-react -D "$REPORT_DIR" && npx playwright show-report "$REPORT_DIR" |
closes https://linear.app/ghost/issue/NY-804
The settings UI allows publishers to enable and configure a paid welcome email (with a flag enabled), but Ghost doesn't actually send the welcome emails for new paid members.
This adds the functionality to send the paid welcome emails for newly created paid members, and members upgrading from a free account to a paid plan.