Skip to content

Two billing models in one product, and neither one pretending the other doesn't exist

A content platform sold both by subscription and as a lifetime deal, where a redeemed code and a monthly plan resolve to the same entitlement, with no parallel systems.

Board

Northgate Copy Studio · every article, counted in one place

New articleSearch articles/FH

Drafts on the board

23

In review

5 waiting on an editor

Published

18 live on client sites

Documents

23of 250

In review

5 articles
Calder EnergyHeat pumps in older homes: a buyer's guideOBFH1,840 words 2 h
Brackenfold LegalWhat a first-time landlord owes the councilPMFH1,260 words 5 h
Sixpenny KitchenAutumn menu: cooking with the last of the squashJASK960 words Yesterday
Little Wren NurseriesChoosing a nursery: twelve questions to askOBSK1,420 words Yesterday
Kittiwake TravelWalking the Dales in October without a carJAFH2,110 words Tue

Published

18 articles
Solar panels and listed buildingsCalder Energy16 Sep
Deposit protection, explained plainlyBrackenfold Legal15 Sep
Batch cooking for a week of school lunchesSixpenny Kitchen15 Sep
Settling-in sessions: what to expectLittle Wren Nurseries12 Sep
The Settle–Carlisle line by seasonKittiwake Travel11 Sep
Loft insulation grants in 2026Calder Energy10 Sep
Section 21 and what replaced itBrackenfold Legal9 Sep
Five soups for the first cold weekSixpenny Kitchen8 Sep
Funded hours for two-year-oldsLittle Wren Nurseries5 Sep
Northumberland's quiet beachesKittiwake Travel4 Sep
Smart meters without the jargonCalder Energy3 Sep
Inventories that settle disputesBrackenfold Legal2 Sep
Bread without a mixerSixpenny Kitchen1 Sep
Outdoor play in wet weatherLittle Wren Nurseries29 Aug
Island hopping on the Hebrides ferriesKittiwake Travel28 Aug
EPC ratings for sellersCalder Energy27 Aug
Rent increases and fair noticeBrackenfold Legal26 Aug
Jam-making for beginnersSixpenny Kitchen25 Aug

Counted on one board

In review5
Published18
All drafts23

Nothing is counted twice: an article is in one column at a time.

Writing against

2 sources
Tier 2 codeGM-T2-8KQD-47XR
Scheduling add-on£9.00 a month + VAT
Publish articles
Scheduled publishing
Tables
Embeds

How the work was scoped

Industry
Media & Publishing
Duration
18 weeks
Cooperation model
Time and materials
Services
Platform engineeringBillingEditorial tooling
Integrations
StripeLifetime-deal marketplace APITransactional emailSentry
Technologies
Next.jsTypeScriptPrismaNextAuthCKEditorPostgreSQL
Team
1 Project lead2 Full-stack engineers1 QA engineer

Client name withheld under NDA. Engagement details are shown to the extent our agreement permits.

One entitlement, two sources

A redeemed code and an active subscription resolve to the same answer about what an account can do.

Entitlement is resolved once from whichever source granted it, so the rest of the application never asks how an account paid. A redeemed lifetime code and an active Stripe subscription both write into the same entitlement record with the same shape, which is what stops the codebase filling up with the branch that eventually gets one of the two cases wrong.

What shipped
  • One entitlement record, whatever granted it
  • Code and subscription write the same shape
  • No branch anywhere asking how an account paid
What this workspace can do

One resolved entitlement, traced back to whatever granted each part of it

Copy entitlement idSearch articles/FH

Resolved

resolve(ws_7Hq2Nd)

Granted

6 capabilities

Locked

2 not in any source

Sources

2 CodeAdd-on

Resolved entitlement

Resolved 14:02:11 · one record
CapabilityValueGranted by
Publish articlesarticles.publishtrueTier 2 code
Review steparticles.reviewtrueTier 2 code
Documentsdocuments.limit250Tier 2 code
Seatsseats.limit5Tier 2 code
Scheduled publishingpublish.scheduletrueScheduling add-on
Headline & SEO checksseo.checkstrueScheduling add-on
Tablesblocks.tablesfalseNeeds a Tier 3 code
Embedsblocks.embedsfalseNeeds a Tier 3 code

The editor, seats and publishing read this record and nothing else. No screen asks whether the workspace holds a code or a subscription; each asks for a capability, and gets one answer.

Lifetime codeTier 2

Never renews
CodeGM-T2-8KQD-47XR
CampaignDeal restock · Aug 2026
Redeemed11 Aug 2026, 10:41:07
source: "code:rdm_31Fk9w"grants: {  "articles.publish": true,  "articles.review": true,  "documents.limit": 250,  "seats.limit": 5 },until: null

SubscriptionScheduling add-on

Active
Subscriptionsub_1PzR4kNg
Billed£9.00 a month + VAT
Current period ends18 Sep 2026
source: "sub:sub_1PzR4kNg"grants: {  "publish.schedule": true,  "seo.checks": true },until: "2026-09-18"
On screen

The resolved answer to what this workspace can do, each capability traced back to the source that granted it: a Tier 2 lifetime code and a monthly add-on, both writing the same three-field shape into one record.

Redeem a code

A code redeems once, on one workspace, and every attempt is kept

Where to buy codesSearch articles/FH

Enter a code

Signed in as Northgate Copy Studio
GM-T2-8KQD-47XRTier 2
Format recognisedGM · tier 2 · checksum valid
Campaign openDeal restock · Aug 2026 · closes 30 Sep 2026
Never redeemedNo redemption recorded against this code
Replaces Tier 1 on this workspaceTier 1 stays in the history
Redeem Tier 2 code Single use: one request wins, any other is refused

What Tier 2 changes

Tier 1 → Tier 2
CapabilityNowAfter
Seats15
Documents50250
Review stepNoYes
Tables & embedsLockedLocked

Redeeming writes the code’s grants into this workspace’s one entitlement. The scheduling add-on is untouched.

Every attempt on this workspace

Newest first · kept for disputes
CodeTierCampaignAttemptedOutcomeWhy
GM-T2-8KQD-47XRTier 2Deal restock · Aug 2026NowReady to redeemAll checks passed
GM-T2-2MFA-38QSTier 2Launch fortnight10 Aug 2026, 16:22:48Refused · already usedRedeemed by another workspace on 9 Jun 2026
GM-T2-6RNB-15JVTier 2Launch fortnight3 Aug 2026, 11:05:13Refused · expiredCampaign closed 15 Jun 2026
GM-T1-3HWP-92LCTier 1Launch fortnight4 Jun 2026, 09:12:31RedeemedGranted Tier 1 to this workspace

Audit record

code GM-T1-3HWP-92LCaccount ws_7Hq2Ndat 2026-06-04T09:12:31Zcampaign launch-fortnight

Redemption with an audit trail

On screen

A Tier 2 code going in, with every earlier attempt on this workspace listed below it (redeemed, expired, already used) and the audit record of code, account, timestamp and campaign.

Codes are single-use and every redemption is recorded, so a disputed code is settleable.

Codes are single-use and redemption is atomic, so two people submitting the same code at the same moment resolve to one winner and one clear refusal, never two entitlements. Every redemption records the code, the account, the timestamp and the source campaign. That's what makes a disputed code an answerable question instead of a judgment call.

What shipped
  • Atomic single-use redemption; concurrent attempts resolve cleanly
  • Code, account, timestamp and campaign recorded on every redemption
  • A disputed code is settled by the record, not by judgment

Billing without a special case

Lifetime and subscription records sit in one view; support doesn't need to know which they're looking at.

Support sees one billing view listing both kinds of account, with the source shown as a field on the same screen. Nobody has to know in advance whether they're looking at a lifetime holder or a subscriber, which was the point: the deal brought in thousands of accounts at once, and a support flow with a special case would have failed on volume alone.

What shipped
  • One billing view; source is a field, not a separate screen
  • Support never needs to know which kind first
  • Designed for the volume the deal was about to deliver
Billing

Every account in one list · the source is a field, not a separate screen

Export CSVThu 17 Sep 2026

Accounts

11 shown · lifetime and subscription together
Account, email or codeSource: anyStatus: any
AccountSourceMonthlyStatus
Northgate Copy Studiows_7Hq2NdTier 2 codeScheduling£9.00Active
Harbourline Pressws_2Lm8QaTeam monthly£59.00Active
Wren & Oak Contentws_9Tc4VbTier 3 code—Active
Pellam Marketingws_4Rx1KpStudio monthly£29.00Payment failed
Dunmore Tutorsws_6Ha7YeTier 1 code—Active
Foxglove Socialws_1Qd5ZmTier 2 code2 seats£12.00Active
Mill Lane Bakeryws_8Nf3JtStudio monthly£29.00Ends 30 Sep
Tern & Heron Travelws_3Wg6UcTier 2 code—Active
Ashby Lettings Co.ws_5Pk2LsTeam monthly£59.00Active
Oriel Health Writersws_7Jy9DrTier 3 codeScheduling£9.00Active
Tollgate Recruitmentws_0Ce4HnTier 1 code—Active
NCNorthgate Copy Studiows_7Hq2Nd · owner Ffion HaleActive

Sources

Tier 2 codeScheduling add-on

One ledger

Newest first
Scheduling add-on18 Sep 2026 · Monthly · £9.00 + VAT£10.80Next charge
Scheduling add-on18 Aug 2026 · Monthly · £9.00 + VAT£10.80Paid
Tier 2 code11 Aug 2026 · Lifetime · paid on the marketplace£0.00Never renews
Tier 1 code4 Jun 2026 · Lifetime · replaced by Tier 2£0.00Replaced
Lifetime codes£0.00 · never renews
Scheduling add-on£9.00 + £1.80 VAT
Billed to date£10.80
Next charge · 18 Sep 2026£10.80
Open entitlementRedemption record
On screen

Support's one billing view: lifetime and subscription accounts in the same list with the source as a field, and one workspace's code that never renews and add-on that bills monthly, in the same ledger and totalled together.

Heat pumps in older homes: a buyer's guide

Calder Energy · written by Orla Brennan · in review with Ffion Hale · saved 14:06

Approve & scheduleSearch articles/FH
Paragraph1,840 words

Heat pumps in older homes: a buyer’s guide

For Calder Energy · 8 minute read

A Victorian terrace can run on a heat pump. The question is rarely whether the house is too old, and more often whether the radiators are large enough to deliver heat at a lower flow temperature.

Air source or ground source?

Most terraces have no room for a ground loop, so the choice usually makes itself. Where there is a garden, the comparison below is the one buyers ask about first.

Table: air source against ground sourcePasted from the brief · kept as plain text until tables are availableblocks.tables
Air sourceGround sourceSpace neededOutside wallGarden trenchDisruptionA day or twoA week or more

Before any quote, ask for a room-by-room heat loss survey. It is the only document that tells you which radiators need replacing, and it is the one most often skipped.

Grants change the arithmetic considerably, so the next section walks through what a homeowner can claim.

Why tables and embeds are locked

Checked on load
can("blocks.tables")false · in no source on this workspace
can("blocks.embeds")false · in no source on this workspace
can("articles.publish")true
can("publish.schedule")true

Decided by

GM-T2-8KQD-47XRTier 2 redeemed 11 Aug 2026
Redeem a Tier 3 codeCompare tiers

The editor asks for a capability. It never reads a tier or a plan name, so pricing can change without it.

Headline & SEO checks

Scheduling add-on
Headline length42 characters
DescriptionWritten · 148 characters
Alt text1 image without it
On screen

Writing against Tier 2: tables and embeds stay locked, the side panel shows each capability check and names the code that decides it, and the SEO checks come from the add-on.

Gated on capability, not plan name

The editor checks the entitlement, so pricing changes never reach into the product.

The editor and every other gated surface check a capability (can this account publish, can it exceed the document limit), never a plan name. That means pricing can be renamed, repackaged or restructured without an edit reaching into product code, and it's the reason the tier reshuffle after the launch took an afternoon instead of a sprint.

What shipped
  • Gates ask for a capability, never a plan name
  • Pricing can be restructured without touching product code
  • The post-launch tier reshuffle cost an afternoon

Built for the spike

A lifetime deal is a load event with a date on it, and the launch was planned as one.

A lifetime deal is a load event with a date printed on it, so it was planned as one. The redemption path was load-tested at several times the expected peak, signup and redemption were separated so a failure in one couldn't block the other, and the database had its indexes built for the specific queries launch day would hammer, not for the steady state.

What shipped
  • Redemption path load-tested at several times expected peak
  • Signup and redemption separated so one can't block the other
  • Indexes built for launch-day queries, not the steady state
Launch fortnight · week one

2–8 Jun 2026 · a load event with a date on it, planned as one

No degraded windowThu 17 Sep 2026

Downtime this week

None

Codes redeemed twice

0 guaranteed by a unique constraint

Redemption path load test

Several times expected peak

Signups and redemptions, launch week

Drawn to shape · no scale
SignupsSignup service · own workers, own queueHealthy all week
RedemptionsRedemption service · own workers, own queueHealthy all week
Tue 2 JunWed 3 JunThu 4 JunFri 5 JunSat 6 JunSun 7 JunMon 8 Jun

Two paths, so one cannot block the other

Signup
Create accountVerify emailEmpty workspace
Redemption
QueueAtomic redeemWrite entitlement

An account exists before a code is redeemed against it. If redemption slows, signups still complete; if signup slows, codes already queued still redeem.

Indexes built for launch-day queries

PostgreSQL
IndexServes
Code(codeHash) UNIQUEThe redeem lookup
Redemption(codeId) UNIQUESingle use: a second insert fails
Redemption(workspaceId, attemptedAt)A workspace's attempts list
Entitlement(workspaceId) UNIQUEEvery capability check
Account(emailLower) UNIQUESignup's duplicate check
On screen

The launch desk for week one: no downtime, zero codes redeemed twice, signups and redemptions on separate paths drawn to shape with no scale, and the indexes built for launch-day queries.

Introduction

What we were brought in to do

The content platform launched on a lifetime-deal marketplace and kept selling subscriptions afterward. We built the platform so both kinds of customer resolve to one entitlement model instead of two.

A content platform launching on a lifetime-deal marketplace while continuing to sell subscriptions. That combination brings thousands of accounts in two weeks and then leaves two permanently different kinds of customer in the same product. The engagement was scoped around getting the entitlement model right before the launch date, while it could still be done cleanly.

Full-Stack Engineering

Where the old way broke

  1. 01

    A lifetime deal brings thousands of accounts in two weeks, and if the code redemption path is bolted on beside subscriptions the two disagree immediately: a lifetime customer gets billed, or a lapsed subscriber keeps access because the entitlement lives in two places.

    The obvious implementation, a code redemption path beside the subscription check, produces two sources of truth for the same question, and they disagree within two weeks. The specific failures are both bad: a lifetime customer who gets billed, and a lapsed subscriber who keeps access because one of the two checks still returns true.

    One entitlement resolved from whichever source granted it. A redeemed code and an active subscription produce the same answer to 'what can this account do', and the billing views show both without treating either as the exception.

What we built together

  • Made entitlement a single resolved value, not a check against two systems

    Resolving entitlement once was settled before the launch date was set, because retrofitting it with thousands of accounts already redeemed isn't feasible.

  • Built code redemption as a first-class flow with its own audit trail

    Redemption is atomic and single-use, and records the code, account, timestamp and campaign, so a disputed code is settled by the record.

  • Kept lifetime and subscription billing visible side by side in one view

    One billing view lists both kinds of account with the source as a field, so support never needs to know which they're looking at first.

  • Built the article tooling against the entitlement, never against a plan name

    The editor and every gated surface check a capability, never a plan name, which is why the post-launch tier reshuffle took an afternoon.

  • Planned for the launch spike, because a lifetime deal is a load event with a date on it

    The launch was planned as a load event: the redemption path was tested well above expected peak, and signup was separated from redemption.

Process

Phase by phase

  1. Phase 1: One Entitlement

    Whatever Granted It

    Resolved what an account can do to a single value, so a lifetime code and a monthly plan are two sources of one answer instead of two competing checks.

    • Entitlement Model
    • Source Resolution
    • Feature Gates
  2. Phase 2: Redemption

    A Code Is A Transaction

    Built redemption as a real flow with validation, single use, and an audit trail, because a code redeemed twice is a support conversation nobody can settle without one.

    • Redeem Flow
    • Single-Use Enforcement
    • Redemption Audit
  3. Phase 3: Billing Side By Side

    Neither One Is The Exception

    Showed lifetime and subscription billing in the same surface, so support doesn't have to know which kind of customer they're looking at before they can help.

    • Unified Billing View
    • Lifetime Records
    • Subscription Records
  4. Phase 4: The Editorial Side

    Articles Against The Entitlement

    Built the article authoring and management tooling against the resolved entitlement instead of a plan name, so pricing can change without touching the editor.

    • Article Authoring
    • CKEditor Integration
    • Content Management
Seats

5 of 5 seats in use · seats come from the entitlement, not from a plan

Add a writerSearch articles/FH

People in this workspace

5 seats used
Seats5 of 5
FHFfion HaleOwner · editor · joined 4 Jun 2026Seat 1 of 5
SKSanjay KaurEditor · joined 11 Aug 2026Seat 2 of 5
OBOrla BrennanWriter · joined 12 Aug 2026Seat 3 of 5
PMPriya MistryWriter · joined 12 Aug 2026Seat 4 of 5
JAJonah AdeyemiWriter · joined 2 Sep 2026Seat 5 of 5
1 invitation waiting for a seatRhys Llewellyn · sent today

Where the seats come from

Resolved from 2 sources
Tier 2 codeLifetime · never renews5 seats
Scheduling add-onGrants scheduling, not seats0 seats
Seat add-onNot on this workspace—

seats.limit = 5 · the same number billing shows support.

Add a writer

Invite by email

rhys@northgatecopy.co.ukWriter
The fifth seat is the last one your code coversGM-T2-8KQD-47XR grants 5 seats. A sixth writer needs one of two routes.
Lifetime routeSeat 6 of 10
Redeem a Tier 3 code
10 seats, never renews Also grants tables and embeds Replaces Tier 2; the history keeps both
Redeem a code
Monthly routeSeat 6 of 6
Add one seat monthly
£6.00 a month + VAT, cancel any time Billed on the same ledger as the scheduling add-on Your Tier 2 code keeps everything it grants
Add a seat · £6.00/month

Either route writes to the same entitlement, so the invitation goes out the moment seats.limit reads 6 or more.

On screen

Seats: the fifth seat is the last one the code covers, so adding a writer offers both routes out, a Tier 3 code or a monthly seat, either of which raises the same limit.

Operational results after launch

1

Entitlement systems

0

Codes redeemed twice

1

Billing views to learn

No downtime

Launch-week signups absorbed

Entitlement systems and billing views are counts. Codes redeemed twice is zero, guaranteed by atomic redemption, not merely observed. Launch-week signups absorbed with no downtime is a statement about the launch itself: the redemption path handled the deal's volume without a degraded window.

Client name withheld under NDA. Figures are approximate, drawn from the engagement’s own reporting.

Ways of working

About our collaboration

We resolved entitlement to a single value before the marketplace launch date, not after it, which is the only reason the launch spike was uneventful. Redemption, billing views and the editorial tooling were all built against that one value.

Time and materials over eighteen weeks, with the launch spike treated as a deliverable in its own right, planned for instead of merely monitored. The redemption path was load-tested at several times the expected peak, and signup and redemption were separated so a failure in one couldn't block the other on the day.

What it settled

What we'd carry into the next one

04
  1. 01

    Two billing models checked separately will disagree within two weeks of launch.

    Two checks for one question is the failure: they disagree within two weeks, and both directions of the disagreement are commercially bad.

  2. 02

    Entitlement resolved once is what lets pricing change without touching the product.

    Resolving entitlement once is what makes pricing changeable. With capability gates, repackaging is a billing change, not a product change.

  3. 03

    A code with no audit trail is a support dispute you can't settle either way.

    An unaudited code can't be settled in either direction, which turns a five-pound support question into a judgment call nobody can defend.

  4. 04

    A lifetime deal is a load event with a date on it. Plan the spike like a launch, because it is one.

    The deal has a date, so the traffic is predictable, which makes it the rare load event you can plan for precisely instead of simply absorbing.

One workspace, four conditions

A code and a subscription are two sources of one answer.

A copy studio’s workspace resolved with only its Tier 2 lifetime code, then with a monthly add-on beside it, then when a sixth writer meets the seat limit, and finally when the same code is submitted again. Each capability shows the source that grants it. Switch tabs, or use the arrow keys once one is focused.

The lifetime code is the only source. It writes its grants into the workspace's one entitlement record, and every screen reads that record. What the code doesn't grant stays locked, and each lock names what would open it.

Sources

Tier 2 codeLifetime · never renews

GM-T2-8KQD-47XR

Scheduling add-onNot on this workspace

—

Entitlement recordwrites: 1

Northgate Copy Studio

The resolution

  1. Load every source for ws_7Hq2Nd
  2. Tier 2 code: 4 grants
  3. No subscription on this workspace
  4. One entitlement resolved

What this workspace can do, and which source grants it

  • Publish articlesarticles.publish = true Tier 2 code
  • Review steparticles.review = true Tier 2 code
  • Documentsdocuments.limit = 250 Tier 2 code
  • Seatsseats.limit = 5 Tier 2 code
  • Scheduled publishingpublish.schedule = falseNeeds the scheduling add-on
  • Headline & SEO checksseo.checks = falseNeeds the scheduling add-on
  • Tablesblocks.tables = falseNeeds a Tier 3 code
  • Embedsblocks.embeds = falseNeeds a Tier 3 code

4 granted, 4 locked, from one source, in one record.

Why it cannot disagree with billing: the gates ask for a capability and billing lists the sources that granted it, and both read the same record. Step timing here is illustrative.

Architecture

From whatever granted it to one answer about what an account can do

Entitlement is resolved once. The rest of the application never asks how an account paid, which is what keeps the branch that eventually gets one of the two cases wrong out of the codebase.

  1. 01 · Source
    Lifetime code or Stripe subscriptionTwo ways to pay, and both write the same entitlement shape. Neither is treated as the exception.
  2. 02 · Intake
    Signup and redemption, apartSeparated so a failure in one can't block the other; the redemption path was load-tested at several times the expected peak.
  3. 03 · Engine
    Atomic single-use redemptionTwo people submitting the same code at the same moment resolve to one winner and one clear refusal, never two entitlements.
  4. 04 · State
    Entitlement and audit in PostgreSQLOne entitlement record per account, and every redemption stores the code, account, timestamp and campaign. Indexes built for launch-day queries.
  5. 05 · Delivery
    Capability gates in Next.jsThe editor and every gated surface ask for a capability, never a plan name, so pricing can change without touching product code.

What a lifetime deal can go wrong on

Code abuse, lost access & billing drift

A code redeems once, and every attempt is on record

Redemption is atomic and single-use, so concurrent submissions of one code resolve to one winner and one clear refusal. Each redemption records the code, the account, the timestamp and the source campaign, so a disputed code is settled by the record, not by judgment.

No second check to disagree

Access comes from one entitlement resolved from whichever source granted it. No subscription check runs beside the code path, so a lifetime customer isn't billed and a lapsed subscriber doesn't keep access because one of two checks still returns true.

Billing shows the same sources the app reads

Support has one billing view listing lifetime and subscription accounts together, with the source as a field. What billing shows and what the gates allow come from the same record, so neither can drift from the other.

Launching a lifetime deal next to your subscriptions? Scope your build in 3 minutes.

Scope your build
Have a project?

Let's talk

Running a large platform, shaping a first MVP, or getting a product ready for a funding round? Tell us where you are. We'll shape the process around it, and stay with you after launch.