Yesterday I sent out an email to all subscribers using the public preview feature, which I’ve successfully used before when I used to only have free and paid subscribers. I’ve now launched 3 different paid tiers, and last night’s email was only supposed to be visible to two tiers, but a third tier that did not have access (and still doesn’t according to the Ghost settings) did receive the full access to the email (while free subscribers got hit with the paywall in the email). However, when I checked on the site, this tier does get hit with the paywall on the site. So it seems that the limiting tier access isn’t extending to the emails. Help?
Since you’re hosted on Magic Pages, can you send me the link to the post in question via email/chat? I’ll have a look to see what happened there
Update: @adventuretoawaken sent me the relevant data so I could check. Turns out, this seems like a bug:
opened 06:39AM - 14 May 26 UTC
needs:triage
When a post has `visibility: 'tiers'` and includes a public preview divider, th… e newsletter email delivers the **full** post content to all paid members, including those on tiers that do not have access. On the website, the same members are correctly paywalled. This contradicts the documented behavior at https://ghost.org/help/public-previews/, which states:
> "The public preview always uses the post access settings to determine how visitors and members see the content on your site, or in their inbox as an email newsletter."
**Root cause:** The email pipeline never invokes `content-gating.js` and has no concept of per-tier segmentation. It collapses tier-restricted posts to a binary `status:free` / `status:-free` split, treating `visibility: 'tiers'` as if it were `visibility: 'paid'`.
Three places in the email pipeline conspire to produce the bug:
1. **`ghost/core/core/server/services/email-service/email-renderer.js:335`** — `getSegments()` hardcodes the segment list to `['status:free', 'status:-free']`. There is no per-tier segment, so the renderer cannot produce a "paid but wrong tier → show paywall" variant.
2. **`ghost/core/core/server/services/email-service/email-renderer.js:397`** — `renderBody()` treats `visibility === 'tiers'` identically to `visibility === 'paid'`:
```js
const isPaidPost = post.get('visibility') === 'paid' || post.get('visibility') === 'tiers';
```
The paywall is then only inserted when `segment === 'status:free'` (lines 402–410), so every paid member, regardless of tier, receives the full content.
3. **`ghost/core/core/server/services/email-service/email-segmenter.js:28-64`** — `getMemberFilterForSegment()` filters only on newsletter visibility (`members` / `paid`) and never references `post.tiers`. The recipient query for `status:-free` therefore includes paid members of every tier.
The website path is correct: `ghost/core/core/server/services/members/content-gating.js` `checkPostAccess()` maps `post.tiers` to an NQL query (`product:'tier-slug'`) and evaluates it against `member.products`. The email pipeline never calls this.
### Steps to Reproduce
1. On a site with three active paid tiers (`tier-a`, `tier-b`, `tier-c`), have at least one active paid member on each tier.
2. Create a post and add a public preview divider in the body.
3. In the post settings, set Access to "Specific tiers" and select only `tier-a` and `tier-b`.
4. Publish & email the post to all subscribers.
5. As the `tier-c` member, (a) open the received email and (b) visit the post URL while logged in.
**Expected**
- Email: `tier-c` member sees only the public preview portion followed by the paywall.
- Website: `tier-c` member sees only the public preview portion followed by the paywall.
**Actual**
- Email: `tier-c` member receives the **full** post content, with no paywall. ❌
- Website: `tier-c` member is correctly paywalled. ✅
- Free members are correctly paywalled in both places. ✅
- `tier-a` / `tier-b` members correctly receive the full content in both places. ✅
This is verifiable at the DB level: `email_batches` for the affected send contains only two rows — `member_segment = 'status:free'` and `member_segment = 'status:-free'` — confirming no per-tier batch was created.
### Suggested fix direction
A correct fix likely requires all three sites to change together:
- `getSegments()` should, for `visibility: 'tiers'` posts that include a `` marker, return both an "authorized tiers" segment (e.g. `product:'tier-a',product:'tier-b'`) and an "everyone else" segment so the renderer produces two versions.
- `renderBody()` should insert the paywall whenever the rendered segment is not in the post's authorized-tier set, not just when `segment === 'status:free'`.
- `getMemberFilterForSegment()` should AND the segment expression into the recipient query so each batch actually delivers the right rendered version to the right members.
### Ghost Version
6.38.0 (relevant code paths unchanged on `main` at the time of filing)
### Node.js Version
22.x
### How did you install Ghost?
Magic Pages
### Database type
MySQL 8
I thought this looked familiar!
opened 05:03PM - 02 Oct 25 UTC
closed 06:40AM - 08 Oct 25 UTC
feature request
### Issue Summary
Sending an email with a paywall and with access set to a spec… ific tier is not respected. Members on the other tier are receiving the full emailed content.
This is a show-stopper for sites that want some content to be reserved in their newsletters for only the highest tier, rather than all paid members.
### Steps to Reproduce
1- Create two paid tiers
2- Put members on these tiers
3- Create a post in the editor. Add a public preview, and mark the access as being one of the tiers only.
4- Send the newsletter (I did publish and email). Observe that all paying members get full-text.
### Ghost Version
6.1
### Node.js Version
Ghost Pro
### How did you install Ghost?
Ghost Pro
### Database type
MySQL 5.7
### Browser & OS version
na
### Relevant log / error output
```shell
```
### Code of Conduct
- [x] I agree to be friendly and polite to people in this repository
jannis
May 14, 2026, 12:58pm
5
Oh well…
I only searched for open issues, not expecting that there would be a closed one for this