Reader
2 fieldsOne subscriber, with the address issues go to.
- person
- required
- required, an email address, never twice
A reader sees only their own subscription.
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.
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.
Each step has 3 parts: who starts it, how it reaches your backend, and the action that answers.
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.
The map shows the records it keeps, the actions it runs and the services it calls.
What it keeps
What it does
What it reaches
One subscriber, with the address issues go to.
A reader sees only their own subscription.
Each start and end of a paid subscription. Only the payment events write them.
Only the payment events write memberships. Your key and the readers read them through the actions.
One issue of the newsletter.
Only your key sends issues. Who may read one is the rule in Read an Issue.
Open an action to read its steps: each sentence, with the calls that do it under it.
From the webPOST /subscribe
Emailtext, needed
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
From the webPOST /upgrade
Nothing. It needs no input.
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
IDtext, needed
Subscriptiontext, needed
Persontext, needed
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"
IDtext, needed
Subscriptiontext, needed
Persontext, needed
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
Titletext, needed
Bodytext, needed
Emailtext, needed
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
From the webPOST /issues
Titletext, needed
Bodytext, needed
Paidyes or no, needed
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
From the webGET /issues/:issue_id
Issue IDtext, needed
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"
People sign up and sign in with an email address and a password, or with a provider you add, such as Google.
| What it does | Who may call | Address |
|---|---|---|
| A signed-in reader subscribes. | a signed-in person | POST /subscribe |
| A reader upgrades to paid. | a signed-in person | POST /upgrade |
| You send an issue, with your key. | you, with your key | POST /issues |
| A signed-in reader opens an issue. | a signed-in person | GET /issues/:issue_id |
Typestate is the backend. Paste this brief into Lovable, v0, Claude Code or the tool that builds your pages.
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 planEach sale and delivery moves the stock, and the owner gets one email when a product runs low.
People join a waiting list from your page, and each friend they bring moves them up.
Invoices get numbers that are never reused, go out by email, and a reminder follows when one is late.