Typestate
Sign in

Newsletter with paid tiers

Readers subscribe for free or pay monthly. Paid issues go to paying readers first, and open to everyone after 30 days.

For a writer who sends a newsletter and charges for some issues.

Stripe logo StripeResend logo Resend
Start it with your agentStarting from a blueprint does not count against the action changes of your plan.

Start the newsletter with paid tiers blueprint on Typestate

Asked you for your website's address and the address your emails come from.

Started Newsletter with paid tiers: 3 record types, 4 web endpoints.

7 actions are compiling.

Deployed to Sandbox. Try it at your Sandbox address.

What your agent does when you start this blueprint

What runs

Each step has 3 parts: who starts it, how it reaches your backend, and the action that answers.

  1. A readersubscribes for free
    Web endpointPOST /subscribe
    SubscribeSaves the reader and welcomes them.saves a Readersends 1 email
  2. A readerupgrades to EUR 5 a month on a Stripe page
    Web endpointPOST /upgrade
    Upgrade to PaidSends the reader to pay on Stripe.opens a Stripe page
  3. Stripesays the subscription started, and the reader is paid
    Payment eventa subscription starts
    Start Paid ReadingMakes the reader a paying reader from now.saves a Membershipsends 1 email
  4. Yousend an issue, paid or free
    Web endpoint, your keyPOST /issues
    Send an IssueKeeps the issue and hands out one email for each reader.saves an Issueruns an action for each reader
  5. Your backendemails each reader their copy
    Another actionSend an Issue, in the background
    Email an Issue to a ReaderEmails one reader their copy.sends 1 email
  6. A readeropens an issue: a paid one is free after 30 days
    Web endpointGET /issues/:issue_id
    Read an IssueAnswers the issue. A paid one waits 30 days for a free reader.reads only
  • Web endpoint: your website or app calls it
  • Payment event: Stripe tells your backend
  • Another action starts it

The rule you will want to change first

A paid issue opens to free readers 30 days after it was sent.

It lives in the action Read an Issue. Tell your agent how you want it instead: one sentence changes, and you read the change before it goes Live.

What it is made of

The map shows the records it keeps, the actions it runs and the services it calls.

What it keeps

Readerperson, email
Membershipperson, subscription, change, event
Issuetitle, body, paid, sent at

What it does

Subscribeweb endpoint
Upgrade to Paidweb endpoint
Start Paid Readingwhen a subscription starts
End Paid Readingwhen a subscription ends
Email an Issue to a Readerrun by Send an Issue, in the background
Send an Issueweb endpoint, your key
Read an Issueweb endpointThe rule to change first

What it reaches

StripeTake one-off payments, deposits and subscriptions on your own Stripe account.
ResendSend email from your own address: sign-in links, receipts, reminders.
Your website4 web endpoints

What it keeps

Reader

2 fields

One subscriber, with the address issues go to.

person
required
email
required, an email address, never twice

A reader sees only their own subscription.

Membership

4 fields

Each start and end of a paid subscription. Only the payment events write them.

person
required
subscription
required
change
required, one of started, ended
event
required, never twice

Only the payment events write memberships. Your key and the readers read them through the actions.

Issue

4 fields

One issue of the newsletter.

title
required, at most 150 characters
body
required, at most 100000 characters
paid
required, yes or no
sent at
required, a date and time

Only your key sends issues. Who may read one is the rule in Read an Issue.

What it does

Open an action to read its steps: each sentence, with the calls that do it under it.

SubscribeA signed-in reader subscribes for free.

Who starts it

From the webPOST /subscribe

What it is given

Emailtext, needed

What it does

  • input.emailwhat the caller gives
  • readera value it works out
  • Readera record type
  • Readeran attribute of a record or an answer
  • "Subscribe Email"a fixed text or number
  • caller.idwhat it knows without being told
  • toa part of an email or a payment

Find the Reader whose person_id is the signed-in person.When one matches, answer with that Reader's id and email, and change nothing.

reader is

Find the first Reader where

person_id is caller.id

when none carry on without one

When reader holds a value

Answer

email is reader.email

id is reader.id

Create a Reader with email set to the given email address, and person_id set to the signed-in person.

Add a Reader

set email to input.email

set person_id to caller.id

Email the given address with the subject "You are subscribed", saying the next issue comes to this address.

Send an email

to input.email

template "Subscribe Email"

Answer with the new Reader's id and email.

Answer

email is new_reader.email

id is new_reader.id

Upgrade to PaidA reader pays monthly on a Stripe page.

Who starts it

From the webPOST /upgrade

What it is given

Nothing. It needs no input.

What it does

  • readera value it works out
  • Readera record type
  • Readeran attribute of a record or an answer
  • "started"a fixed text or number
  • caller.idwhat it knows without being told
  • referencea part of an email or a payment
  • refusewhere it says no

Find the Reader whose person_id is the signed-in person.When none matches, refuse with "Subscribe first".

reader is

Find the first Reader where

person_id is caller.id

when none refuse "Subscribe first"

Find the latest Membership whose person_id is the signed-in person.When its change is started, refuse with "You already pay. Thank you".

membership is

Find the first Membership where

person_id is caller.id

sorted by created_at, highest first

when none carry on without one

When membership holds a value

and membership.change is "started"

Refuse "You already pay. Thank you"

Open a payment page for the Monthly product, with the signed-in person's id as the reference.

page is

Open a payment page for Monthly

reference is caller.id

Answer with the address of that payment page.

Answer

url is page.url

Start Paid ReadingWhen a subscription starts, the reader is paid from that moment.

What it is given

IDtext, needed

Subscriptiontext, needed

Persontext, needed

What it does

  • input.idwhat the caller gives
  • told_beforea value it works out
  • Membershipa record type
  • Membershipan attribute of a record or an answer
  • "started"a fixed text or number
  • caller.emailwhat it knows without being told
  • toa part of an email or a payment

A subscription started.It reads the event's id, the subscription and the person who pays, and it runs as that person, who is the signed-in person.When a Membership already has the event's id, stop and answer nothing.Create a Membership with change set to started, person_id set to the person who pays, subscription_id set to the given subscription, and event_id set to the event's id.

The event id never repeats, so the same event told twice is saved once and thanked once.

told_before is

Find the first Membership where

event_id is input.id

when none carry on without one

When told_before holds a value

Answer

Add a Membership

set change to "started"

set event_id to input.id

set person_id to input.person

set subscription_id to input.subscription

Email the signed-in person, at the address they signed in with, with the subject "Thank you for your support".

Send an email

to caller.email

template "Start Paid Reading Email"

End Paid ReadingWhen a subscription ends, the reader goes back to the free issues.

What it is given

IDtext, needed

Subscriptiontext, needed

Persontext, needed

What it does

  • input.idwhat the caller gives
  • Membershipa record type
  • Membershipan attribute of a record or an answer
  • "ended"a fixed text or number

A subscription ended.It reads the event's id, the subscription and the person who paid.Create a Membership with change set to ended, person_id set to the person who paid, subscription_id set to the given subscription, and event_id set to the event's id.

The event id never repeats, so the same event told twice is saved once. The Reader is not changed, so the person stays subscribed to the free issues.

Add a Membership

set change to "ended"

set event_id to input.id

set person_id to input.person

set subscription_id to input.subscription

Email an Issue to a ReaderOne reader's copy of an issue, sent as its own piece of background work.

What it is given

Titletext, needed

Bodytext, needed

Emailtext, needed

What it does

  • input.emailwhat the caller gives
  • toa part of an email or a payment

Email the given email address with the given title as the subject and the given body as the text.

Send an email

to input.email

template "Email an Issue to a Reader Email"

body input.body

title input.title

Send an IssueYou send an issue, with your key. Paid issues go to paying readers only.

Who starts it

From the webPOST /issues

What it is given

Titletext, needed

Bodytext, needed

Paidyes or no, needed

What it does

  • input.bodywhat the caller gives
  • readersa value it works out
  • Issuea record type
  • Issuean attribute of a record or an answer
  • falsea fixed text or number
  • nowwhat it knows without being told
  • bodya part of an email or a payment

Create an Issue with title, body and paid set to the given title, body and paid, and sent_at set to now.

Add an Issue

set body to input.body

set paid to input.paid

set sent_at to now

set title to input.title

A reader pays when the latest Membership whose person_id is that Reader's person_id has the change started.When that Issue is paid, it goes to the Readers who pay.When it is not paid, it goes to every Reader.

readers is

Find all Readers

recipients is an empty list

For each reader in readers

membership is

Find the first Membership where

person_id is reader.person_id

sorted by created_at, highest first

when none carry on without one

When issue.paid is false

or all of

membership holds a value

and membership.change is "started"

Adds the value named reader to recipients.

For each of those Readers, run the Email an Issue to a Reader flow in the background, with that Issue's title, that Issue's body and the Reader's email.

Each copy is its own piece of work, so one address that fails does not stop the others.

For each recipient in recipients, at most 10000

Run Email an Issue to a Reader in the background

body is issue.body

email is recipient.email

title is issue.title

Answer with the count of Readers it goes to.

Answer

count is the number of the value named recipients

Read an IssueA reader opens an issue on your site. A paid issue opens to free readers after 30 days.

Who starts it

From the webGET /issues/:issue_id

What it is given

Issue IDtext, needed

What it does

  • input.issue_idwhat the caller gives
  • issuea value it works out
  • Issuea record type
  • Issuean attribute of a record or an answer
  • falsea fixed text or number
  • caller.idwhat it knows without being told
  • refusewhere it says no

Find the Issue whose id is the given issue id.When none matches, refuse with "No such issue".

issue is

Find the first Issue where

id is input.issue_id

when none refuse "No such issue"

When that Issue is not paid, answer with its title, body and sent_at.

When issue.paid is false

Answer

body is issue.body

sent_at is issue.sent_at

title is issue.title

Find the latest Membership whose person_id is the signed-in person.When its change is started, answer with that Issue's title, body and sent_at.

membership is

Find the first Membership where

person_id is caller.id

sorted by created_at, highest first

when none carry on without one

When membership holds a value

and membership.change is "started"

Answer

body is issue.body

sent_at is issue.sent_at

title is issue.title

When that Issue's sent_at is more than 30 days ago, answer with its title, body and sent_at.

Paying readers read first, and after 30 days the issue helps bring in new readers.

When days since issue.sent_at is more than 30

Answer

body is issue.body

sent_at is issue.sent_at

title is issue.title

Otherwise refuse with "This issue is for paying readers for its first 30 days".

Refuse "This issue is for paying readers for its first 30 days"

Who signs in and what they may read

People sign up and sign in with an email address and a password, or with a provider you add, such as Google.

  • Reader: A reader sees only their own subscription.
  • Membership: Only the payment events write memberships. Your key and the readers read them through the actions.
  • Issue: Only your key sends issues. Who may read one is the rule in Read an Issue.

Money

  • Monthly, EUR 5.00, every month. The paid tier: EUR 5 every month. Paid on a Stripe page, on your own Stripe account.
  • When a subscription starts, Start Paid Reading runs.
  • When a subscription ends, End Paid Reading runs.

Web endpoints

What it doesWho may callAddress
A signed-in reader subscribes.a signed-in personPOST /subscribe
A reader upgrades to paid.a signed-in personPOST /upgrade
You send an issue, with your key.you, with your keyPOST /issues
A signed-in reader opens an issue.a signed-in personGET /issues/:issue_id

What it cannot do yet

  • No comments on issues.
  • Each reader's email is its own background task, so a large list takes a while to go out.

Build the pages with your tool

Typestate is the backend. Paste this brief into Lovable, v0, Claude Code or the tool that builds your pages.

Show the brief
Build the pages for "Newsletter with paid tiers". The backend runs on Typestate and answers these web endpoints: POST /subscribe: A signed-in reader subscribes. Who may call: a signed-in person. POST /upgrade: A reader upgrades to paid. Who may call: a signed-in person. POST /issues: You send an issue, with your key. Who may call: you, with your key. GET /issues/:issue_id: A signed-in reader opens an issue. Who may call: a signed-in person. People sign up at POST /auth/signup and sign in at POST /auth/login, which answers a token. Send it as Authorization: Bearer <token>. A refused call answers a sentence that can be shown to the person as it is.

Start it

  1. Tell your agent the sentence on the right.
  2. It asks you for the https address of the newsletter's website, such as https://letters.example.com. Payments come back to it.
  3. It asks you for the address emails come from, on a domain you verified at Resend, such as "Riverside Physio <hello@riverside.example>".
  4. It tells you what only you can do, such as pasting your Stripe key in the cockpit. A key never goes through the agent.
  5. Open your Sandbox address and try it yourself.

Starting from this blueprint does not count against the action changes of your plan. The Free plan runs it on Sandbox. Taking real money on Live needs a paid plan.

Start the newsletter with paid tiers blueprint on TypestateChoose a plan

Related blueprints