Private preview · invitation only

You write the playbook.
zenn.ing runs it.
Declare the domain.
zenn derives the rest.

Your records, rules and workflows, in a playbook your whole team can read back as sentences. zenn.ing runs it as a hosted app. You review the process, not the code.One EDN model compiles to IR once. zenn serves HTTP and GraphQL, emits typed clients and migrations, and commits writes with their effects on Cloudflare Workers.

Request accessWatch the film
  • ✓ Hosted for you
  • ✓ Forms work without JavaScript
  • ✓ Every release reviewed
Playbook · enquiry

What an enquiry can do

  1. 01
    Anyone can send an enquiry from the website, without signing in.
    :send {:type :create … :allow true}
  2. 02
    Only staff read enquiries.
    :where [:check :staff]
  3. 03
    Staff reply once, and the reply is mailed to the sender.
    :reply {… :where [:= [:field :state] :new] …}
  4. 04
    Accepting an enquiry closes it and opens a job.
    :emit [{:effect :enquiry/accepted …}]
Start from a working example
EnquiriesJobs & invoicesBookingsCalloutsShopChatBlogKanbanWikiStorageURL shortener

Write. Run. Review.Model. Invoke. Release.

Three verbs. The first and the last are yours; the middle one is ours.Compile once, invoke IR, ship immutable releases. The kernel is the same on every host.

01 Write

Write the playbook. Your team reviews it as sentences.One EDN model: resources, actions, rules, effects and flows.

Records, who may do what, and what happens next, written as a model that zenn.ing reads back to you as plain sentences. If a rule is wrong, you can see that it is wrong, before it reaches a customer.The compiler checks every reference and expression. :allow decides whether a caller may act, :where what they can see, and :validate names its own error codes.

Playbook · invoiceexamples/apps/jobs · :add-line
  • Only staff add lines to an invoice.
  • A line is charged to the penny, a half penny up.
  • A unit price is in whole pence.
  • Sending the invoice mails the customer.
:add-line
{:type :update :by [:id] … :allow [:check :staff]
 :validate
 [{:assert [:= [:decimal/compare
                [:decimal/round [:input :unit-price] 2]
                [:input :unit-price]] 0]
   :code :invoice/unit-price-pence}]
 :set {:total [:decimal/add [:field :total] …]}}
02 Run

zenn.ing runs it, everywhere your business happens.Compiled once; transports, clients and storage derived.

The website form, the staff inbox and the mail that goes out all follow the same playbook. zenn.ing hosts it, so there is nothing for you to install or keep running.Apps hand work to each other through release-level subscriptions. Writes and declared effects commit together through a transactional outbox, and each event opens one job however often it arrives.

Today · Tom's Plumbingexamples/apps/jobs/with-enquiries
  • ✓ Enquiry from Sam Ortiz received
  • ✓ Accepted by Tom
  • ✓ Job #118 opened in the office
  • ✓ Invoice INV-0241 mailed · £97.01
:subscriptions
{:open-jobs
 {:effect :enquiries.enquiry/accepted
  :invoke :jobs.job/open-for-enquiry
  :input  {:enquiry  [:event :enquiry]
           :customer [:event :name]
           :email    [:event :email] …}}}
03 Review

Review the process, not the code.Immutable releases, a fork per member, a reviewed way back.

Every change is a release you read before you ship it: what changes, what happens to the data, what it needs. Who shipped what is always visible, and you can go back to an earlier release through the same review, unless a storage change since then rules it out.A member forks production; the draft has one revision-checked writer path. Shipping builds the saved draft into a release with its migrations, and deployment selects a release.

Shipped to Production · by Priyaenquiries/schedules/close-stale.edn
  • + Answered enquiries close after 30 days
  • ~ 142 enquiries kept as they are
  • ← Go back to the 1 Oct version
;; Every hour, close the answered enquiries
;; whose reply is 30 days old.
{:schedule :close-stale :every [1 :hours]
 :invoke :enquiry/close-stale :input {}
 :ctx {:tidy true}}

From a website form to a mailed invoice, with nothing lost in between.Two apps, one release-level subscription, one outbox.

This is the Jobs example. A customer enquires, staff accept, a job opens and an invoice goes out, each step recorded with the one before it.Enquiries emits :enquiry/accepted; the :open-jobs subscription runs Jobs' open-for-enquiry once per enquiry; sending the invoice emits mail/send through the outbox.

EnquiriesWebsite
Sam sends an enquiryTom accepts it
emits :enquiry/accepted
JobsOffice
Job #118 openedTwo lines of work added
:open-jobs → job/open-for-enquiry
InvoiceMail
INV-0241 · £97.01Mailed to Sam
invoice/send → mail/send

If an email address is not written like one, the form says so before the enquiry is recorded.The :email field's :pattern refuses a malformed address before the write. It checks the address's form, not whether the mailbox exists.

Meet zenn

Forms that just workServer-rendered sites

Website forms post without JavaScript, from any browser.Static output and JSX pages; forms post to the domain API.

A staff inboxRules on reads

Signed-in staff see what customers sent and act on it.:where hides what a caller may not see; it behaves as missing.

Mail with the changeTransactional outbox

A message is recorded with the change that caused it.Effects commit with the write; delivery is tracked.

Apps that hand work onSubscriptions

An accepted enquiry opens a job in another app.An effect in one domain runs an action in another. A redelivered event is recognised while its inbox record is kept; Jobs also finds the job it already opened.

Who shipped whatRecorded requester

Every release and change carries the person who asked.Deployments and variable writes record who requested them.

Go backImmutable releases

Releases are frozen. Going back to one is a reviewed ship, unless storage has moved on.Deployment selects a release; storage only moves forward, so a release older than an applied migration is refused.

Magic-link sign-inSign-in links

No passwords for your team to forget.Indistinguishable responses, per SPEC §22.

Start from a templateTemplates

Every example app is a starting point. Enquiries first.New apps start from the checked-in example projects.

Clear refusalsStable error codes

When something is not allowed, the page says why.Kinds map to 400, 403, 404, 409 and 422; the codes stay stable.

Hosted for youOne kernel

zenn.ing runs it on a global network. Nothing to install.A ClojureScript engine; the same fixtures pass on Node, Bun and workerd.

We don't hand you a blank page.

Organisations join by invitation. We set yours up from the example closest to your business, then you take it from there.

  1. 01

    Tell us about the work

    30 minutes

    A short call. We find the process that takes most of your week and the example closest to it.

  2. 02

    We set up your organisation

    By invitation

    You get an invitation link, a working app from that template and its playbook to read.

  3. 03

    You take it from there

    Your team

    Change the rules, ship a release, go back if you need to. We stay a message away.

Flat plans. No surprise bills.

Monthly allowances, pooled across your apps. A year in advance costs eleven months. Compare plans

Free$0

Model an app and put it in front of people.

Small business$99 /mo

A business running real work on the platform.

Large business$999 /mo

Many apps across teams and environments.

EnterpriseCustom

Your identity provider, your terms.

Questions

Do we need engineers?

Not to run it. The playbook reads back as sentences, and the app's pages are where your team works. Engineers are welcome: everything is also an API with typed clients.Not to operate it. Engineers get HTTP and GraphQL, generated TypeScript clients, reviewed migrations and the SPEC; the model stays the one source.

Is this vibe coding?

No. The playbook is what runs. zenn.ing checks it before every release and enforces it on every call, so what you review is what happens.No. Business rules are not generated code to audit: the model compiles to IR once, and every action runs against it.

Can we go back after a change?

Usually. Releases are frozen snapshots. Choosing an earlier one opens the same Ship review as any change, and zenn.ing refuses it if a storage change has been applied since that release.Usually. Releases are immutable snapshots, and deployment selects one. Going back ships an earlier release through the Ship review; storage only moves forward, so a release without an applied migration is refused.

How do we start?

By invitation for now. Tell us about the work and we set up your organisation from the closest example.By invitation: organisations are created by us. Request access and bring your hardest domain.

Private preview

Let's write it down.Stop re-implementing the rules in five places.

Tell us about the work your team does. We will set up your organisation from the example closest to it.One model, enforced on every call, documented on every release. Bring your hardest domain.

Request access