Skip to content

[pull] main from TryGhost:main - #1441

Merged
pull[bot] merged 9 commits into
code:mainfrom
TryGhost:main
Aug 25, 2026
Merged

pull[bot] merged 9 commits into
code:mainfrom
TryGhost:main

Conversation

@pull

@pull pull Bot commented Aug 25, 2026 •

Copy link
Copy Markdown

See Commits and Changes for more details.


Created by pull[bot] (v2.0.0-alpha.4)

Can you help keep this open source service alive? 💖 Please sponsor : )

kevinansfield and others added 9 commits August 25, 2026 09:14
ref https://linear.app/ghost/issue/BER-3852

Gift checkout was only discoverable through direct Portal links, leaving eligible visitors and members without an in-product path. This adds guarded promotion entry points while keeping checkout availability independent from merchandising controls.

- Added independent signup and account promotion settings with Admin controls and live preview support
- Showed promotions only when the feature, persisted setting, and a giftable paid offering are available
- Limited account promotion to paid and complimentary members, excluding gift recipients
- Preserved direct `#/portal/gift` access when either promotion setting is disabled
- Added settings persistence coverage and translator context for the new Portal copy
)

no ref

Moves the gift flush scheduler into the scheduling adapter layer as
`SignedFlushScheduler`. It keeps time deduplication, delayed flush
timing, and queue rebuilds at boot and after scheduler-key rotation in
one place, while gift delivery and reminder scheduling provide their
endpoint and pending times.

Extracts `buildSignedJob` for constructing signed Admin API callbacks
and reuses it in automations without changing its polling, jitter, or
idempotency behaviour.
Ref https://ghost.slack.com/archives/C09D3RD4M88/p1787592510553609

Kept the gift delivery calendar anchored and consistently sized when it flips above the field and buyers browse between months.
no-issue

With reuseConnection enabled (the default), every Redis cache adapter in the
process shares one singleton store, created with whichever adapter happened to
be instantiated first at boot. Because writes never passed a per-call TTL,
cache-manager fell back to that store's creation-time TTL, so the first
adapter's TTL was silently applied to every cache feature.
ref https://linear.app/ghost/issue/HKG-1975

The cleanup body lived in a closure over the gifts service's init()
locals, so it couldn't be handed to a job handler. GiftService already
owns three of the four phases and the delivery service dependency, so
the only wiring change is widening its Pick to include recoverPending.
The phases are moved verbatim - this is preparation for the jobs-service
migration, not a behaviour change.
ref https://linear.app/ghost/issue/HKG-1975

The legacy bree worker did no work: it posted a domain event back to
the main thread and reported "done" immediately, so job success and
duration were meaningless. Dispatching a CleanGiftsJob through the jobs
service runs the same code on the same thread, but job.completed now
fires when cleanup actually finishes.

The worker, the domain event and the legacy registration go in the same
commit so the job is never registered in both systems. Scheduling moves
to initBackgroundServices because gifts init() runs before the jobs
service exists. The old started/completed logs are replaced by a
structured clean_gifts.completed event whose counts start at null, not
zero, so a phase whose swallowed error skipped it doesn't look idle.
ref https://linear.app/ghost/issue/GVA-911/

This commit wires up the async 1-click export to a configurable webhook url, behind a feature flag.

When `hostSettings.export.webhookUrl` is configured, `POST
/ghost/api/admin/exports/` posts a signed export event to the host's
webhook and returns 202; the host builds a full archive and emails the
site owner a download link.

## API contract

**Endpoint**: `POST /ghost/api/admin/exports/` 

Available to Owner + Administrator staff users.

### Request

#### Body

```json
{
    "type": "export",
    "siteId": "<hostSettings:siteId>",
    "components": {"content": true, "members": true, "analytics": true, "themes": true, "routes": true, "media": false}
}
```
- `{"components": {...}}`: keys a subset of `content | members |
analytics | themes | routes | media`, boolean values, at least one true
- the host emails the download link to the site owner; the payload
carries no recipient

#### Auth headers

| Header | Value |
|---|---|
| `X-Ghost-Request-Timestamp` | `Date.now().toString()` (ms) |
| `X-Ghost-Signature` | `base64( HMAC-SHA256( secret,
"{timestamp}:{rawBody}" ) )` |

The signing key lives at `hostSettings:export:webhookSecret`. 

### Responses

- 202 Accepted (empty body) on success
- 404 when the webhook URL isn't configured
- 400 when the URL is set but the secret is missing
- 502 when the forward fails
ref https://linear.app/ghost/issue/BER-3892/

Gift emails currently use the default sender without a reply-to address,
so replies do not reliably reach site owners.

This uses the configured member support address, including its existing
default-address fallback, for buyer confirmations, recipient deliveries,
and reminder emails. It covers both transactional mail and direct bulk
Mailgun delivery.
ref https://linear.app/ghost/issue/BER-3888/ui-fixup

Gift subscriptions now display one year instead of twelve months,
emphasise the full duration, and omit the colon from the redemption
email button.
@pull pull Bot locked and limited conversation to collaborators Aug 25, 2026
@pull pull Bot added the ⤵️ pull label Aug 25, 2026
@pull
pull Bot merged commit ee7d10a into code:main Aug 25, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants