Skip to content

Activate immediate gift email delivery at checkout - #29853

Open
kevinansfield wants to merge 1 commit into
agent/ber-3851-delivery-outcomesfrom
agent/ber-3851-checkout-activation
Open

Activate immediate gift email delivery at checkout#29853
kevinansfield wants to merge 1 commit into
agent/ber-3851-delivery-outcomesfrom
agent/ber-3851-checkout-activation

Conversation

@kevinansfield

Copy link
Copy Markdown
Member

Summary

  • add the delivery-method-discriminated checkout contract behind giftSubCustomization
  • normalize and reserve gift delivery metadata on the server before Stripe Checkout
  • reconstruct the delivery snapshot from paid Checkout metadata idempotently
  • wake immediate email delivery after the gift is persisted
  • preserve omitted and explicit link purchases when customization is disabled

Stack

6 of 6. Stacked on #29852. This is the only layer that makes immediate email delivery customer-reachable.

Issue: https://linear.app/ghost/issue/BER-3851/establish-immediate-email-delivery-for-gift-subscriptions

@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 78f03b9c-c8c1-4355-a0d2-f030315cecab

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Comment @coderabbitai help to get the list of available commands.

@kevinansfield
kevinansfield marked this pull request as ready for review August 10, 2026 15:11
@kevinansfield
kevinansfield force-pushed the agent/ber-3851-checkout-activation branch from f4f1e2d to 5cb25f2 Compare August 10, 2026 15:12
@chatgpt-codex-connector

Copy link
Copy Markdown

💡 Codex Review

deliveryMethod: input.deliveryMethod ?? 'link',

P1 Badge Expose email delivery through the Portal checkout

When a customer uses the bundled Portal, apps/portal/src/components/pages/gift-page.jsx:890-894 submits only the tier, duration/cadence, and buyer email, while apps/portal/src/actions.js:372-379 and apps/portal/src/utils/api.js:625-647 likewise omit all recipient and delivery fields. Consequently this fallback always selects link, so enabling giftSubCustomization still provides no customer-facing path to request the immediate email delivery added by this commit; the delivery fields and controls need to be threaded through Portal checkout.

AGENTS.md reference: AGENTS.md:L368-L371


DomainEvents.subscribe(StartGiftDeliveryFlushEvent, async () => {
if (!labsService.isSet('giftSubCustomization')) {
return;

P2 Badge Preserve delivery wakeups while the flag is disabled

If giftSubCustomization is disabled when a scheduled retry callback fires, this return consumes the one-shot wake while leaving the gift pending with an already-due delivery_attempt_at. Labs settings can be re-enabled without restarting, but recoverAll() runs only during wrapper initialization, so the recipient's email remains stranded until a restart or an unrelated delivery happens to wake the processor; either retain processing for already-created deliveries or explicitly recover schedules when the flag is enabled.


if (labs.isSet('giftSubCustomization')) {
gifts.init({

P2 Badge Initialize gift analytics after a live flag enable

When Ghost boots with giftSubCustomization off and an administrator later enables the writable Labs flag, checkout and delivery immediately use the live flag value, but this gift analytics wrapper remains uninitialized for the lifetime of the process. sendDeliveryForGift() may schedule the recurring fetch job, yet no gift event processor has subscribed, so provider delivery/failure outcomes stay unknown until Ghost restarts; initialize the processor independently of the boot-time flag or handle flag transitions.


if (value.code === 'ENOTFOUND' || value.code === 'EAI_AGAIN' || value.code === 'ECONNREFUSED') {
return 'recoverable';

P1 Badge Retry common transient transport failures

When Nodemailer reports a routine transient connection failure such as ETIMEDOUT, ESOCKETTIMEDOUT, or ECONNRESET, GhostMailer preserves that code on the nested err, but this recoverable-code list does not recognize it. The classifier therefore returns ambiguous, and sendDeliveryForGift() deliberately leaves the gift in sending without scheduling a retry, so a single SMTP timeout or connection reset can permanently prevent the paid gift email from reaching its recipient; classify the standard timeout/reset/socket codes as recoverable.


try {
const key = await this.#internalKeys.get('ghost-scheduler');
this.#adapter.schedule(this.#buildJob(time.getTime(), key));
} catch (err) {

P1 Badge Retain a fallback when scheduling a retry fails

When obtaining the scheduler key or queuing the one-shot job fails, this method only logs and returns, while the caller has already moved the gift back to pending with a future delivery_attempt_at. I checked ghost/core/core/server/adapters/scheduling/error-capture.ts:69-85: the production scheduling wrapper also swallows both synchronous throws and rejected scheduling promises, so this try cannot observe adapter failures. Because there is no periodic gift-delivery sweep, that retry is never attempted until a process restart or an unrelated delivery wake; the pending retry needs a durable fallback or scheduling failure must be propagated and handled.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@nx-cloud

nx-cloud Bot commented Aug 10, 2026

Copy link
Copy Markdown

🤖 Nx Cloud AI Fix

Ensure the fix-ci command is configured to always run in your CI pipeline to get automatic fixes in future runs. For more information, please see https://nx.dev/ci/features/self-healing-ci


View your CI Pipeline Execution ↗ for commit 7a0b3ac

Command Status Duration Result
nx run ghost:test:ci:integration ✅ Succeeded 3m 5s View ↗
nx run ghost:test:integration ✅ Succeeded 3m 4s View ↗
nx run ghost:test:legacy ✅ Succeeded 2m 55s View ↗
nx run ghost:test:e2e ✅ Succeeded 2m 40s View ↗
nx run-many -t test:unit -p ghost ✅ Succeeded 31s View ↗
nx run ghost-monorepo:lint:boundaries ✅ Succeeded 17s View ↗
nx run-many --target=build --projects=tag:publi... ✅ Succeeded <1s View ↗
nx run @tryghost/admin:build ✅ Succeeded 5s View ↗
nx run-many -t lint -p ghost,ghost-monorepo ✅ Succeeded 18s View ↗

💡 Verify your cache is correct by running tasks in a sandbox. Read docs ↗


☁️ Nx Cloud last updated this comment at 2026-08-11 16:56:54 UTC

@codecov

codecov Bot commented Aug 10, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 96.77419% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 75.40%. Comparing base (ace1b32) to head (7a0b3ac).

Files with missing lines Patch % Lines
...services/webhook/checkout-session-event-service.js 85.71% 1 Missing ⚠️
Additional details and impacted files
@@                         Coverage Diff                          @@
##           agent/ber-3851-delivery-outcomes   #29853      +/-   ##
====================================================================
+ Coverage                             75.39%   75.40%   +0.01%     
====================================================================
  Files                                  1617     1617              
  Lines                                142057   142087      +30     
  Branches                              17596    17603       +7     
====================================================================
+ Hits                                 107104   107144      +40     
+ Misses                                33901    33891      -10     
  Partials                               1052     1052              
Flag Coverage Δ
e2e-tests 77.52% <96.77%> (+0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@kevinansfield
kevinansfield force-pushed the agent/ber-3851-checkout-activation branch from 5cb25f2 to 1d445d5 Compare August 10, 2026 17:30
@kevinansfield
kevinansfield force-pushed the agent/ber-3851-checkout-activation branch from 1d445d5 to da35866 Compare August 11, 2026 08:43
@kevinansfield
kevinansfield force-pushed the agent/ber-3851-checkout-activation branch from da35866 to 28735cf Compare August 11, 2026 12:04
@kevinansfield
kevinansfield force-pushed the agent/ber-3851-checkout-activation branch from 28735cf to 2113b91 Compare August 11, 2026 12:09
ref https://linear.app/ghost/issue/BER-3851/establish-immediate-email-delivery-for-gift-subscriptions
The final stack layer makes the capability reachable only through validated, server-authored checkout metadata while preserving legacy link purchases when the development flag is off.
@kevinansfield
kevinansfield force-pushed the agent/ber-3851-checkout-activation branch from 2113b91 to 7a0b3ac Compare August 11, 2026 16:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant