Skip to content

Sync with upstream Ghost v6.27.0#63

Open
madewithlove-machine-user wants to merge 445 commits intomainfrom
chore/sync-v6.27.0
Open

Sync with upstream Ghost v6.27.0#63
madewithlove-machine-user wants to merge 445 commits intomainfrom
chore/sync-v6.27.0

Conversation

@madewithlove-machine-user
Copy link
Copy Markdown

Syncing fork to upstream release v6.27.0.

vershwal and others added 30 commits March 19, 2026 16:30
TryGhost#26869)

ref https://linear.app/ghost/issue/HKG-1643/

The files upload endpoint accepted any file type without validation,
unlike images and media which check extension and MIME type. With the
move to a shared CDN domain, we don't want to upload files with
executable content types.

This commit adds:
- An allowlist of supported file extensions (based on Ghost Pro data)
- Extension-only validation middleware on the upload route
- Content-type resolution that serves browser-renderable types (images,
PDF, JSON, audio, video, fonts) with their natural type, overrides
formats (HTML, JS, CSS, XML) to text/plain, and defaults everything else
to application/octet-stream (forced download)
- Users can zip unsupported file types as an escape hatch
ref https://linear.app/ghost/issue/BER-3414/

## Summary

- added a shared filter core for field definitions, field resolution,
AST helpers, codecs, and NQL query compilation
- moved the members list filter state, query translation, hydration, and
bulk/export/search flows onto that shared core while keeping the
existing `membersForward` rollout model unchanged
- removed the legacy members filter config path and intentionally left
the comments moderation runtime on its current implementation for
follow-up work
no ref

Iterative cleanup and improvements to the welcome email design
customization modal (PRs TryGhost#26841, TryGhost#26842, TryGhost#26843). Fixes bugs found
during review, removes dead code, adds missing functionality (image
upload, Ghost badge toggle), and refactors the preview to be composable
and visually consistent with the existing newsletter preview.
no ref

* Abstracted the translateCadence function to utils since we need it in
multiple places.
* Fixed two more places cadence wasn't wrapped for translations.
* Removed "{amountOff} off" string, since we are already using "{amount}
off" elsewhere.
…a API (TryGhost#26837)

closes https://linear.app/tryghost/issue/ONC-1553

- The `add()` method in `memberBREADService` passed the full `options`
object (including `withRelated: ['labels', 'newsletters']`) to
`setComplimentarySubscription()`.
- When the Stripe customer had existing subscriptions, fetching related
stripeSubscriptions with those options caused a "labels is not defined
on the model" error on StripeCustomerSubscription models.
- This fix uses `sharedOptions` (context + transacting only), matching
the pattern already used for `linkStripeCustomer()`.
---
### Summary

When creating a comped member via the Admin API with a
`stripe_customer_id` pointing to a Stripe customer that has existing
subscriptions, `setComplimentarySubscription()` received the full
options object including `withRelated: ['labels', 'newsletters']`.
Inside that method,
`member.related('stripeSubscriptions').fetch(options)` attempted to
eager-load `labels` on `StripeCustomerSubscription` models, which don't
have that relation — throwing "labels is not defined on the model".

### Impact

The bug's impact depends on the Stripe customer's existing
subscriptions:

- **Customer with a paid subscription + `comped: true`**: The comp
conversion never runs. The member ends up with `paid` status instead of
`comped`, and the API returns a 500. This is a real data issue — the
member doesn't get their complimentary access.
- **Customer with an existing comp subscription**: All database records
are written correctly by `linkStripeCustomer()` before the error occurs,
so the member is properly set up. However, the API still returns a 500
to the caller.
- **Customer with no subscriptions**: The bug doesn't trigger because
there are no `StripeCustomerSubscription` models to eager-load on.

### Fix

Uses `sharedOptions` (only `context` + `transacting`) instead of the
full `options` when calling `setComplimentarySubscription()` on line 406
of `member-bread-service.js`. This matches the pattern already
established for the `linkStripeCustomer()` call on line 381.

---------

Co-authored-by: Chris Raible <chris@ghost.org>
Closes
https://linear.app/ghost/issue/BER-3451/make-sure-that-tier-names-in-the-table-truncate

Tier names will now truncate and not break the layout of the table.

| Before | After |
|--------|--------|
| <img width="504" height="331" alt="Screenshot 2026-03-19 at 14 14 45"
src="https://github.com/user-attachments/assets/bced60d9-708a-4a21-a1c9-34341c309217"
/> | <img width="452" height="335" alt="Screenshot 2026-03-19 at 14 14
26"
src="https://github.com/user-attachments/assets/4495b68e-d25c-44ed-b8f0-2dd97172ab66"
/> |
Closes
https://linear.app/ghost/issue/BER-3452/use-sensible-operators-and-defaults-for-each-filter-type

Makes sure we map to Ember for operators and improves Name and Email
filters to use contains instead of is as a default for fuzzy searching.
The deploy-to-staging label was only checked inside the CI trigger_cd job
which runs on push events. If a developer added the label after CI had
already started, the deploy flag was missed and the CD pipeline ran as a
dry run. This adds a dedicated workflow that fires on the label event
itself (pull_request_target: labeled), waits for CI build artifacts to be
ready, then dispatches to Ghost-Moya — the same pattern as the preview
label. The existing trigger_cd label check is preserved so subsequent
pushes to a labeled PR still auto-redeploy.

Also adds CI-wait polling to the preview label workflow for consistency —
previously it dispatched immediately on label addition, which could fail
if CI hadn't finished pushing the GHCR image yet.
no refs

This build artifact shouldn't be committed since it's generated as part
of the build pipeline, but it wasn't included in `.gitignore`. This adds
it to prevent anyone from accidentally committing it.
… format (TryGhost#26863)

closes https://linear.app/ghost/issue/ONC-1560/re-unable-to-access-my-admin-site

## Summary

- Bumps `@tryghost/image-transform` from 1.4.6 to 1.4.13, which upgrades
Sharp from 0.34.2 to 0.34.5
- Adds `fontconfig` to the production Docker image (~7MB increase)

## Root Cause

A customer uploaded an SVG favicon containing a `<text>` element. When
Ghost tried to rasterize it to PNG (via the
`/content/images/size/w256h256/format/png/` endpoint), Sharp's bundled
librsvg needed fontconfig to resolve fonts for the text. Two problems:

1. **Sharp 0.34.2** (librsvg 2.60.0) **segfaults** when fontconfig is
missing — killing the Ghost process instantly with no error log. Sharp
0.34.5 (librsvg 2.61.2) handles this gracefully.
2. **The production Docker image** (`node:22-bookworm-slim`) has no
fontconfig installed, so even without the segfault, SVG `<text>`
elements render as empty placeholders.

This caused a **boot loop**: every page load triggered a favicon resize
request → process crash → restart → crash again.

## Reproduction

Confirmed segfault in a clean `node:22-bookworm-slim` container (same
base as production):

```
$ docker run --rm node:22-bookworm-slim bash -c '
  npm install sharp@0.34.2 && node -e "
  require(\"sharp\")(Buffer.from(
    \"<svg xmlns='http://www.w3.org/2000/svg'><text>A</text></svg>\"
  )).resize(256).png().toBuffer()"'

Fontconfig error: Cannot load default config file: No such file: (null)
Segmentation fault (exit 139)
```

Same test with `sharp@0.34.5` → succeeds (no segfault, but text renders
as placeholder).
Same test with `sharp@0.34.5` + `fontconfig` installed → succeeds with
correct text rendering.

## Changes

| Change | Why |
|---|---|
| Bump `@tryghost/image-transform` to 1.4.13 (Sharp 0.34.2 → 0.34.5) |
Prevents segfault — librsvg 2.61.2 handles missing fontconfig gracefully
instead of crashing |
| Add `fontconfig` to `Dockerfile.production` runtime layer | Provides
font resolution so SVG `<text>` elements render correctly (~7MB image
size increase) |

## Test plan

- [x] Verified Sharp 0.34.2 segfaults in `node:22-bookworm-slim` (no
fontconfig)
- [x] Verified Sharp 0.34.5 does not segfault in same environment
- [x] Verified fontconfig survives the `build-essential` purge step in
Dockerfile
- [x] Verified correct SVG text rendering with fontconfig on staging
preview
- [x] Verified fontconfig adds ~7MB to production image size
no ref

This scaffolding is needed before we can migrate over the browser tests
to e2e.
The upstream Koenig packages have been migrated from JavaScript to
TypeScript, which changes their module format and export structure. This
update bumps all `@tryghost/kg-*` and `@tryghost/koenig-lexical`
packages to their latest versions and adapts Ghost's codebase to work
with the new builds:

- Updated CJS `require()` calls to use `.default` where the TypeScript
build uses default exports (`mobiledoc.js`, `json-to-html.js`,
`email-renderer.js`, `markdown-renderer.js`, clean-basic-html util)
- Fixed the `UnsplashSearchModal` `onImageInsert` prop to match the
exported `InsertImagePayload` type from `kg-unsplash-selector`
- Added `react/jsx-runtime` and `react-dom/server` as UMD globals in the
Ember app route so Koenig UMD bundles resolve them at runtime
- Increased test timeout for HTML conversion tests to accommodate slower
first-run initialization of the new TypeScript-built packages

Co-authored-by: Kevin Ansfield <kevin@lookingsideways.co.uk>
ref https://linear.app/ghost/issue/NY-1168/

- moved to single wrapper .hbs for emails (member welcome emails,
newsletters); welcome emails brought into line with newsletters

Ideally, we can use the same wrapper for all emailed content, leaving it
to the consumer to adjust anything specific to the use case. This is a
safer way to use the same styles to maintain behavior, and each
'component' (e.g. post header) should handle its own, compartmentalized
behavior to not disrupt the whole.

Transactional emails like the magic link do not use this currently,
though it could be brought into this as we add further email
customization options.
refs
https://linear.app/ghost/issue/ONC-1569/post-attribution-for-links-in-emails-seems-to-be-broken-on-pro
closes
https://linear.app/ghost/issue/ONC-1568/email-only-post-attribution-incorrectly-resolves-to-url-type-instead

## Problem

Attribution for links in email-only posts does not correctly resolve to
the post itself. For example, if a free member clicks a link in an
email-only post they received, then upgrade to a paid membership within
the same browser session, the newly created subscription should be
attributed to the email only post. Currently though, the subscription
would be attributed as a `url` and, depending on the link, the URL might
be the `/email/:uuid` route of the post, or the Homepage.

## Root cause

In
`/ghost/core/core/server/services/member-attribution/url-translator.js`,
Ghost had previously been filtering out email only posts, because the
Post.findOne() query implicitly filters to only `published` posts, while
an email-only post will have a status of `sent`.

## Fix

Updated the query in the url-translator to filter for
`status:[published,sent]` and `type:[post,page]`. With this change, the
attribution correctly resolves to the `post` itself, rather than the
generic `url`.

There is also a change to the attribution builder to support this: since
email only post's don't have a URL registered in the URL service, the
`attribution_url` was being set to `null`. This adds a conditional to
check if the post is email only, and if so, it sets the
`attribution_url` to `/email/:uuid` for the post.

Finally, it also updated the link on the members page to the post to
point to the Post Analytics, rather than the post on the frontend. This
change is made for regular `published` posts in addition to the `sent`
posts for consistency.


### Files changed
- **`url-translator.js`** — Added fallback `findOne` query with
`status:sent` for email-only posts; added `/email/:uuid/` URL for sent
posts
- **`attribution-builder.js`** — Use `/email/:uuid/` URL when resolving
attribution for email-only posts at read time
fixes https://linear.app/ghost/issue/BER-3447/add-global-search-next-to-filter-button-in-the-header

Reintroduce the global search in the header, matching the Ember version of the members list
closes https://linear.app/ghost/issue/BER-3346/let-users-save-filtered-member-views

Implemented the equivalent of post custom views for members on top of the existing hooks and state management we already had in place.
…26924)

fixes https://linear.app/ghost/issue/BER-3464/prevent-sidebar-flicker-during-navigation-preference-updates

Enabling the `keepPreviousData` flag ensures that query data won't cycle through `undefined` when the derived query re-evaluates.
…ryGhost#26778)

closes
[GVA-730](https://linear.app/ghost/issue/GVA-730/add-webhook-changes-behind-feature-flag)

The `verificationFlow` feature flag now keeps the existing escalation
email path as default while enabling a parallel webhook path through
`VerificationTrigger` and `verification-webhook-service.ts`. This wires
both strategies in `members/service.js` and adds tests to assert webhook
payload behavior without regressing the legacy flow.
…#26891)

closes https://linear.app/ghost/issue/BER-3441/

HTML-to-mobiledoc/lexical parse errors from `?source=html` requests are
client input validation failures, not unexpected server errors. The
explicit `sentry.captureException()` calls were reporting these to
Sentry before re-throwing as `ValidationError` (422), causing excessive
noise when integrations retry bad requests. The `ValidationError` alone
is sufficient — clients get a proper 422 and Ghost's error middleware
already knows not to report 4xx errors to Sentry.
no ref

Some legacy sites incorrectly have object shaped shared views. This restores the previous fallback to [].
no ref
- Added transistor labs flag to cardConfig via useFeatureFlag
- Set visibilitySettings to 'none' to hide visibility panel in email
context
- Bumped @tryghost/koenig-lexical to 1.7.27
ref https://linear.app/ghost/issue/NY-1154/
- existing preview substitution was bolted-on to visibility gating,
which was inappropriate; it is now its own step

The intent of this change is to provide a pattern for subtituting
content in the Preview pane in Admin > Post Editor. In this case, we
substitute a user-specific iframe with a placeholder rather than using a
real user's uuid and display a real user's content.
## Summary
- migrate the portal donations browser coverage into top-level e2e as
portal-support.test.ts
- add the donation flow and page-object support needed to keep the new
tests readable
- remove the old ghost/core/test/e2e-browser/portal/donations.spec.js
coverage

## Testing
- cd e2e && yarn eslint helpers/pages/public/public-page.ts
helpers/pages/stripe/fake-checkout-page.ts helpers/pages/portal/index.ts
helpers/pages/portal/support-success-page.ts
helpers/pages/portal/notification-page.ts
helpers/playwright/flows/donations.ts helpers/playwright/flows/index.ts
helpers/services/settings/settings-service.ts
helpers/services/stripe/fake-stripe-server.ts
tests/public/portal-support.test.ts
- cd e2e && yarn test:types
- cd /Users/slars/dev/ghost/Ghost/e2e && GHOST_E2E_SKIP_BUILD=1 yarn
test tests/public/portal-support.test.ts
bobvaneck and others added 28 commits April 7, 2026 07:34
ref TryGhost#23361

Added two missing strings for the new retention offers. They were
provided by a Pro customer.
no-issue                                                                                                                                                       
                                                                                                                                                                 
Static theme assets (/assets/*) and public assets (/public/*) were                                                                                             
always served from the origin, even when other content types like                                                                                              
images, media, and files already supported CDN base URLs. This meant                                                                                           
sites couldn't offload their heaviest cacheable resources to a                                                                                                 
cookie-free CDN domain, hurting page load performance and increasing                                                                                           
request overhead from unnecessary session cookies.                                                                                                             
                                                                                                                                                                 
Adding `urls:assets` completes the CDN story - sites can now serve all                                                                                           
static resources from a dedicated domain, matching the existing                                                                                                
pattern for `urls:image`, `urls:media`, and `urls:files`.
…ost#27197)

ref https://linear.app/ghost/issue/BER-3484

After a gift is purchased, the `GiftService` sends a staff notification
email to all admin / owner users with the purchaser info and amount
no ref

- backfills empty i18n context hints in `context.json`
- future PR will prevent empty hints from being added
(TryGhost#27171)
ref TryGhost#1338

- Established the primitive composition layer, added primitive stories,
and grouped Storybook navigation to prioritize primitives before
higher-level abstractions.
The old pattern split the sentence "You've successfully subscribed to"
into a fragment key with the site title appended outside the translated
string. This meant translators couldn't reorder words around the site
title. The new key uses @doist/react-interpolate to embed <strong> within
a single translatable string, preserving existing translations.
no ref

- We have CI checks to make sure that new i18n keys are added to
context.json when they're generated, but there's no requirement to add
any actual explanation for the key
- This fixes that. When generate-context.js runs locally it will log a
warning, but in CI it will error
- This will apply to any i18n strings added from here on out - existing
empty strings will be backfilled in
TryGhost#27170
Closes
https://linear.app/ghost/issue/DES-1339/tags-page-has-broken-togglegroup

When the new tokens were added, surface-elevated was set to white which
meant it wasn't indistinguishable from a white page/card/panel
background. This tweaks adjusts its value to improve contrast.

| Before | After |
|--------|--------|
| <img width="448" height="131" alt="Screenshot 2026-04-07 at 14 06 14"
src="https://github.com/user-attachments/assets/54d878e7-29a5-4629-b088-bb00a2b51b43"
/> | <img width="452" height="129" alt="Screenshot 2026-04-07 at 14 05
56"
src="https://github.com/user-attachments/assets/ac85d433-ef73-4771-a035-cbcab41279e4"
/> |
ref https://linear.app/ghost/issue/BER-3476

- added static UI for gift redemption for design team to continue with
the UI, while engineers integrate with the backend. This uses mocked API
responses that are temporary
- the new page `#/portal/gift/redeem/<token>` is behind a feature flag:
if flag is disabled, the gift redemption popup does not render
towards https://linear.app/ghost/issue/NY-1188

This change should have no user impact. It adds
`welcome_email_automations` and `welcome_email_automated_emails` tables.

These are split up from the existing `automated_emails` table with two
additions in `welcome_email_automated_emails`:

- `delay_days`
- `next_welcome_email_automated_email_id`

You may wish to review a diff between `automated_emails` and the new
tables:

```diff
diff --git a/j.js b/j.js
index dded3c3..68070f4 100644
--- a/j.js
+++ b/j.js
@@ -1,8 +1,16 @@
-automated_emails: {
+welcome_email_automations: {
     id: {type: 'string', maxlength: 24, nullable: false, primary: true},
     status: {type: 'string', maxlength: 50, nullable: false, defaultTo: 'inactive', validations: {isIn: [['active', 'inactive']]}},
     name: {type: 'string', maxlength: 191, nullable: false, unique: true},
     slug: {type: 'string', maxlength: 191, nullable: false, unique: true},
+    created_at: {type: 'dateTime', nullable: false},
+    updated_at: {type: 'dateTime', nullable: true}
+},
+welcome_email_automated_emails: {
+    id: {type: 'string', maxlength: 24, nullable: false, primary: true},
+    welcome_email_automation_id: {type: 'string', maxlength: 24, nullable: false, references: 'welcome_email_automations.id', constraintName: 'weae_automation_id_foreign', cascadeDelete: true},
+    next_welcome_email_automated_email_id: {type: 'string', maxlength: 24, nullable: true, references: 'welcome_email_automated_emails.id', constraintName: 'weae_next_email_id_foreign', cascadeDelete: false},
+    delay_days: {type: 'integer', nullable: false},
     subject: {type: 'string', maxlength: 300, nullable: false},
     lexical: {type: 'text', maxlength: 1000000000, fieldtype: 'long', nullable: true},
     sender_name: {type: 'string', maxlength: 191, nullable: true},
@@ -10,9 +18,5 @@ automated_emails: {
     sender_reply_to: {type: 'string', maxlength: 191, nullable: true, validations: {isEmail: true}},
     email_design_setting_id: {type: 'string', maxlength: 24, nullable: false, references: 'email_design_settings.id'},
     created_at: {type: 'dateTime', nullable: false},
-    updated_at: {type: 'dateTime', nullable: true},
-    '@@indexes@@': [
-        ['slug'],
-        ['status']
-    ]
+    updated_at: {type: 'dateTime', nullable: true}
 },
```
towards https://linear.app/ghost/issue/NY-1188
ref TryGhost#27183

We just added two new tables: `welcome_email_automations` and
`welcome_email_automated_emails`. This adds dormant models for those,
which are based on the existing `AutomatedEmail` model.

**You may wish to review a diff of these with their associated
`AutomatedEmail` counterpart, which will make these changes look much
smaller.** For example, I ran these commands as part of self-review:

```sh
vimdiff ghost/core/core/server/models/{automated-email,welcome-email-automation}.js
vimdiff ghost/core/core/server/models/{automated-email,welcome-email-automated-email}.js
vimdiff ghost/core/test/unit/server/models/{automated-email,welcome-email-automation}.test.js
vimdiff ghost/core/test/unit/server/models/{automated-email,welcome-email-automated-email}.test.js
```
…bled (TryGhost#27191)

closes
https://linear.app/ghost/issue/NY-1197/use-design-settings-from-the-database-when-rendering-welcome-emails

## Summary

- Updated the welcome email renderer to use custom design settings
(background color, button style, header image, footer content, badge,
etc.) from the database instead of hardcoded defaults
- All new behavior is gated behind the
`welcomeEmailsDesignCustomization` labs flag — existing welcome emails
are unchanged when the flag is off
- The service wrapper reinitializes when the labs flag changes, so
toggling the flag takes effect without a restart

## Changes

**Renderer** (`member-welcome-email-renderer.js`):
- Reads `designSettings` and passes them through to `getEmailDesign()`
(when flag is on) or uses hardcoded defaults (when flag is off)
- Reuses `registerHelpers` and the newsletter styles partial for
consistent rendering with newsletter emails
- Passes header image, footer content, badge, and header title settings
to the wrapper template

**Service** (`service.js`):
- Loads `emailDesignSetting` relation when fetching automated emails
(flag-gated)
- Caches design settings at load time alongside other email data
- Wrapper class tracks labs flag state and reinitializes when it changes

**Template** (`wrapper.hbs`):
- Added `post-content-row` / `post-content` classes for
newsletter-consistent content styling
- Added conditional footer content, badge, and powered-by-Ghost sections
ref TryGhost#27087
ref https://linear.app/ghost/issue/PLA-18/

The above commit disabled the scheduling tests to patch a fix. In
effect, we should never have the scheduler point to anything but an
admin API endpoint. While the scheduler was kept flexible, at this point
there doesn't seem to be a reason to not enforce more rigidity/safety.

W/r to testing, it's difficult to test the full scheduling workflow.
I've updated the tests to successfully emit the scheduler request - we
don't ensure that the scheduler pushes a message back to Ghost, though
we may be able to do that with a standalone SchedulerService in the
future, or something of the like. Right now, Docker communication is
difficult, and that's what caused the original issue.
closes
https://linear.app/ghost/issue/NY-1164/add-ability-to-edit-sender-info-using-existing-verification-flow

## Summary

Adds the ability for admins to customize sender name, email, and
reply-to address for welcome emails, shared across both free and paid
welcome email types.

## Changes

### Backend
- New `PUT /automated_emails/senders/` endpoint to update `sender_name`,
`sender_email`, and `sender_reply_to` across all welcome emails in a
single call
- New `PUT /automated_emails/verifications/` endpoint to verify pending
email changes via single-use token (magic link)
- `MemberWelcomeEmailService` now uses per-automated-email sender
overrides when sending, falling back to newsletter/default settings
- Email verification flow for `sender_reply_to` changes (uses existing
`SingleUseTokenProvider`)
- Test sends now also respect per-email sender overrides

### Frontend
- New `useEditAutomatedEmailSenders` and `useVerifyAutomatedEmailSender`
API hooks
- `EmailPreview` component updated to show From/Reply-to in envelope
header, with optional recipient/subject line visibility props
- Member emails settings page handles `verifyEmail` query param on load,
shows confirmation modal after successful verification

### Tests
- E2E API tests for shared sender editing and token-based verification
- Integration tests confirming sender overrides are applied when sending
real and test welcome emails

---------

Co-authored-by: Chris Raible <chris@ghost.org>
no issue

- the `yarn.lock` file had become out of sync, causing it to update when running `yarn install`
- this contains the up-to-date lockfile
This PR contains the following updates:

| Package | Change |
[Age](https://docs.renovatebot.com/merge-confidence/) |
[Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| [human-number](https://redirect.github.com/Kikobeats/human-number) |
[`2.0.8` →
`2.0.9`](https://renovatebot.com/diffs/npm/human-number/2.0.8/2.0.9) |
![age](https://developer.mend.io/api/mc/badges/age/npm/human-number/2.0.9?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/human-number/2.0.8/2.0.9?slim=true)
|

---

### Release Notes

<details>
<summary>Kikobeats/human-number (human-number)</summary>

###
[`v2.0.9`](https://redirect.github.com/Kikobeats/human-number/blob/HEAD/CHANGELOG.md#209-2026-04-03)

[Compare
Source](https://redirect.github.com/Kikobeats/human-number/compare/v2.0.8...v2.0.9)

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - Only on Sunday and Saturday ( * * * * 0,6 ), Between 12:00
AM and 12:59 PM, only on Monday ( * 0-12 * * 1 ), Between 10:00 PM and
11:59 PM, Monday through Friday ( * 22-23 * * 1-5 ), Between 12:00 AM
and 04:59 AM, Tuesday through Saturday ( * 0-4 * * 2-6 ) in timezone
Etc/UTC.

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/TryGhost/Ghost).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4xMDIuMTEiLCJ1cGRhdGVkSW5WZXIiOiI0My4xMDIuMTEiLCJ0YXJnZXRCcmFuY2giOiJtYWluIiwibGFiZWxzIjpbXX0=-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
…es (TryGhost#27185)

towards https://linear.app/ghost/issue/NY-1188
ref TryGhost#27183
ref TryGhost#27184

Previous patches added new dormant tables and models. This change
actually uses them.

More specifically, this does a database migration to move
`automated_emails` to `welcome_email_automations` and
`welcome_email_automated_emails`. Then, it updates all relevant code to
use those new tables.

The old model is deleted, but the tables are not. (That's forthcoming.)
closes https://linear.app/ghost/issue/BER-3506/rework-feature-flagging-for-release

Reworked `membersForward` to gate React rendering on `/members` instead of changing sidebar URLs. Added React support for `/members/import`, kept Ember fallback routes, prevented duplicate Ember rendering under the flag, and restored missing members access checks.
…#27205)

ref https://linear.app/ghost/issue/BER-3484

The buyer now receives a transactional email with the gift redemption
link, tier name, amount paid, cadence, and expiration date after a
successful gift checkout. Follows the `comments-service` pattern with a
dedicated `GiftEmailService` + `GiftEmailRenderer` and co-located
`Handlebars` templates. Email delivery is best-effort (failures are
logged but don't break the webhook handler)
ref https://linear.app/ghost/issue/ONC-1597/

- Webhook triggers now use `request-external` by default instead of `@tryghost/request`
- Internal IP destinations can still be enabled with `security.allowWebhookInternalIPs` for self-hosted setups

To allow webhooks to reach internal IP destinations, add to your Ghost config:

```
{
  "security": {
    "allowWebhookInternalIPs": true
  }
}
```

Co-authored-by: Fabien O'Carroll <fabien@allou.is>
Closes
https://linear.app/ghost/issue/DES-1309/welcome-emails-body-font-regression

Koenig's styles were winning the specificity battle here.

| Before | After |
|--------|--------|
| <img width="817" height="633" alt="Screenshot 2026-04-08 at 09 41 11"
src="https://github.com/user-attachments/assets/a6d28510-af4a-418a-bb1d-34499b87fee4"
/> | <img width="817" height="634" alt="Screenshot 2026-04-08 at 09 40
54"
src="https://github.com/user-attachments/assets/a569a34e-fe6a-4070-851b-c6c175ad42d0"
/> |
ref https://linear.app/ghost/issue/ONC-1595/

- shared path normalization across local storage reads and URL conversion so image path handling stays consistent
ref
https://linear.app/ghost/issue/FEA-480/native-share-buttons-with-link-based-referral-param-for-attribution

This PR adds a new small modal component to Portal that allows a reader
to more easily share a post. It fires by adding #/share on any post
link. The modal lets visitors copy the link to the post and share to top
social platforms.

---------

Co-authored-by: Troy Ciesco <tmciesco@gmail.com>
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.