Skip to content

Documentation

Starters

A starter is a named setup in one file: the lists people can join with the sentence they agree to, the emails, the automations, the saved views and the charts. Installing one is what you do in your first ten minutes instead of reading seven settings tabs — and everything it writes is paused, so nothing reaches anybody before you have read it.

Michael, 2026-09-08: "Automation, we just need to have some opinionated simple automations."

A blueprint is a named setup, in one JSON file: the lists people can join with the sentence they agree to, the emails, the automations written as sentences, the saved views and the charts. Installing one is what a founder does in their first ten minutes instead of reading seven settings tabs — and everything it writes is paused, so nothing reaches anybody until a person has read it and switched it on.

The files live in blueprints/, one per starter, imported statically by blueprints/index.ts (never read off disk at run time). The installer is src/lib/data/blueprints.ts.

Where a starter is offered

  • The dashboard's first screen, while the workspace has neither a list nor an email: Install the SaaS starter, the starter's own paragraph, and one button — the page's only accent fill.
  • Settings → Setup, always, as a Starters frame listing all three with their state (Installed / Partly installed / Not installed), how much it would be trusted with once it is in (#213: drafts only, proposes and you approve, runs alone), and one button each. It sits above the go-live checks while the workspace is empty and under them once something is there.
  • Claude, as install_blueprint (docs/mcp.md). describe_workspace says which are in.
  • A terminal, as node scripts/workspace-setup.mts --slug <slug> --apply blueprints/saas.json, which is how a blueprint is applied to production. That script is a mirror of the installer, not the installer itself — the data layer's @/ imports do not resolve under plain node, which is the whole reason it talks to the schema — so when the shape grows, both move. It keeps the same rules: nothing twice, and an automation the file says may not belong here (needs) is held back with that sentence rather than written. What it does not do is compose the sentences again: it writes the text and refs the file carries.

The four starters

SaaS starter (saas)

For a web app people sign up for. It makes a Product updates list, five emails to rewrite in your own words, and five automations: welcome somebody when they sign up, check in when they signed up and never got started, say thank you when they pay, ask why when they cancel, and put a task on your list when somebody goes quiet.

Automation What it does
Welcome on signup A signup event arrives → send Welcome once. Transactional: no list, because the person just signed up.
Signed up but never activated Somebody enters Account → wait 3 days, unless they move on or unsubscribe → if they are still at Account, send Still getting started? once.
Paid: say thank you A purchase event arrives → send Thanks for subscribing once.
Cancelled: ask why A subscription_cancelled event arrives → send Sorry to see you go once.
Went quiet for two weeks Somebody enters Subscriber → wait 14 days, unless they move on, reply, or unsubscribe → create a task for the owner. Never an email: going quiet is not something the person did, so there is nothing to answer.

Also the view Signed up, not activated (people sitting at Account) and the chart Signups by week. The fifth email, Checking in, is the wording to start from when the quiet-for-two-weeks task lands on somebody; nothing sends it.

Two things about it are worth saying out loud. Signed up but never activated reads the default lifecycle, where Account comes before Activated; on a downloadable app's order Activated comes first, so read that one before switching it on. And Cancelled: ask why can only be written once this workspace has actually seen a subscription_cancelled event — Stripe's own, or one your servers post — because an automation may not wait for an event Kicklace has never heard of. Until then the starter says "Connect Stripe first" and installs everything else; install it again after the first cancellation and the automation lands.

Waitlist starter (waitlist)

For a product people are waiting for. It makes a Waitlist list, the welcome that goes out when somebody joins, and a check-in two weeks later for anyone still waiting. Kicklace runs its own waitlist on these two. It does not make a pipeline: where people stand on your waitlist is yours to arrange, under Settings → Pipelines.

Two automations, both list sends against Waitlist — the welcome on joining, and two weeks without a word, which waits 14 days and cancels if they move stage, reply or unsubscribe. Plus the view On the waitlist and the chart New on the waitlist, 7 days.

A blueprint does not make a pipeline. Kicklace's own waitlist is worked on a Waitlist pipeline (Joined → Contacted → Invited → Live → Not now), and that is ensureHomeWorkspace's doing, not a starter's: stages are how a team decides to work, and a starter arriving with five of them and moving people between them would be a bigger thing than one click should be. Settings → Pipelines is where they are added.

Release notes (release-notes)

For telling people what you shipped. It makes a Product updates list — the same one the SaaS starter uses, so installing both makes one list — and a Release notes email, and when Vercel says a production deployment succeeded it writes that email as a draft and tells you. Nothing is ever sent by itself: you open the draft, write what changed, and press send. The automation needs the Releases object, which Settings → Integrations makes when you connect Vercel.

One automation: a Release entering Deployeddraft_broadcast to Product updates from the Release notes template. Without the Releases object the starter installs the list and the email and says "Connect Vercel first".

Reply triage (reply-triage)

For a founder who answers the email themselves. It adds an Intent field to a person, four emails to rewrite in your own words, and six automations: when somebody writes in, the model reads what they wrote once and sets Intent — a question, a complaint, a cancellation, thanks or something else — and then the matching reply is drafted on their record for you to send. A cancellation also puts a task on your list to call them today. Nothing is ever sent by it.

Automation What it does
A reply arrives: read it email_inai_classify Intent
A support email arrives: read it supportai_classify Intent
A question: draft the answer Intent changes, is “A question” → draft_email
A complaint: draft the apology Intent changes, is “A complaint” → draft_email
A cancellation: draft it, and call them Intent changes, is “A cancellation” → draft_email + create_task due today
Thanks: draft the reply Intent changes, is “Thanks” → draft_email

Why one classifier and four drafters rather than one automation per intent with an ai_decide and an Otherwise: otherwise is one level, on purpose, so four outcomes cannot be written as one sentence without becoming a flowchart. Splitting them also means each card reads as one plain line a founder can switch on by itself, and only the first automation costs a model call — one per message, on the workspace's own allowance. The four drafters read the field and ask nothing.

Its word is drafts only: there is no send_email anywhere in it. The starter is where fields came from — an automation that has the model fill in a choice needs somewhere to put the answer.

The rules an install keeps

  1. Nothing is written twice. A list is matched by its address, an email and an automation by name, a view by its key or name, a chart by its title. Installing a starter again adds only what is missing, so the button is safe to press at any time.
  2. Nothing broken is written. The readable half of every automation — the sentences and the chips — is composed in this workspace's own words when it is installed, and the whole thing is then judged by the app's own validateSpec against this workspace's own vocabulary. If it would not pass, it is not written.
  3. What cannot land says why. An automation naming a stage, an object or an event this workspace has not got is skipped with one sentence naming the screen that would make it exist. The file may write that sentence itself (needs), which is where "Connect Vercel first" comes from.
  4. Nothing is ever switched on. Every automation is written paused, whatever the file says, and nothing an install does sends an email.
  5. Only owners and admins install one, on every path — the screens, the door, and the script.

The shape

{
  "key": "saas",                 // its name in a URL, a tool call, a file
  "name": "SaaS starter",
  "description": "One plain paragraph: who it is for and what it writes.",
  "brand": { "logoUrl": "…", "accent": "#422006", "display": "sans" },  // optional; --apply only
  "fields":    [{ "object": "people", "label": "Intent", "type": "select",
                  "options": "A question, A complaint, A cancellation" }],
  "lists":     [{ "slug": "product-updates", "name": "Product updates",
                  "consentText": "Email me when there is something new.",
                  "description": "…" }],
  "templates": [{ "name": "Welcome", "subject": "Thanks for signing up",
                  "body": "Hi {{first_name}},\n\n…", "description": "A draft. …" }],
  "automations": [{
    "name": "Welcome on signup",
    "status": "paused",
    "summary": "…",              // the app composes its own; --apply writes this one
    "note": "…",                 // one line in the automation's own conversation
    "needs": "Connect Stripe first: …",   // optional: the sentence when it cannot land
    "spec": { "when": { … }, "wait": { … }, "onlyIf": [ … ], "then": [ … ] }
  }],
  "views":  [{ "object": "people", "key": "signed-up-not-activated",
               "name": "Signed up, not activated", "filter": { "stage": "account" } }],
  "charts": [{ "key": "bp_saas_signups", "kind": "line", "title": "Signups by week",
               "prompt": "How many people signed up each week?", "span": 2, "spec": { … } }]
}

fields are written first, before anything else, because an automation that classifies something has to have somewhere to write. The key is never in the file: addField makes it from the label the way Settings → Objects and fields does, so a starter can never claim a key the app would not have given it, and a field whose key is already there is left exactly as it is — the one there is the one somebody set up.

spec is a whole AutomationSpec — the runnable half (trigger, wait, condition, action) with the text and refs a person reads. The app recomposes the text and the chips from the runnable half when it installs one, in the words this workspace uses for its own stages and lists, so what is written in the file is what --apply writes under plain node and nothing more. span is the same: --apply writes it, and inside the app a chart lands at the size the dashboard gives a chart of that kind, which is then dragged.

Exporting a workspace's own setup

Settings → Setup → Export this setup as a blueprint downloads <workspace>-blueprint-<date>.json: the same shape, holding the fields on its people and organizations, its lists with their consent sentences, its emails, its automations (all paused, with member names replaced by owner in the parts, the chips and the sentences), its saved views and its charts. Owners and admins only, because the file carries every consent sentence and every email the workspace sends.

It carries no person, no record, no subscription, no key and no secret: none of those is part of a way of working, and a file people pass around is the last place any of them belongs. The brand's logo URL is kept, because it is a public address.

The file is the one --apply installs, so a setup can be moved from one workspace to another, or shared (#221's gallery is where that goes next).

Adding a starter

  1. Write blueprints/<key>.json in the shape above.
  2. Import it in blueprints/index.ts and add it to FILES.
  3. npm test src/lib/data/__tests__/blueprints.test.ts — the first test installs every file in the folder into a bare workspace and fails on the first automation the app's own validator would show a problem for, so a mistyped stage key or a template name that does not match is caught there rather than by a customer.
  4. If it names something a workspace may not have, write the needs sentence: what to connect, and that the starter can be installed again afterwards.

Copy stays on the honesty rail: no claims, no numbers, no customer named. Every template is a draft a person edits before switching anything on, and every description says so.

This page is still written partly for the people building Kicklace, so parts of it are about work that is not yours to do. It is here because what it describes is real; it is not listed on the documentation index until it has been rewritten.