self-hosted-funnel-launch

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Self-Hosted Funnel Launch

自托管式漏斗启动

Take a funnel from nothing to published on infrastructure the operator controls, using Autonnel (Apache-2.0). This skill is the build step; design the funnel first with
sales-funnel-blueprint
, and confirm self-hosting is the right call with
funnel-platform-picker
if that is still open.
使用Autonnel(Apache-2.0许可证),在运营者可控的基础设施上完成漏斗从无到有的发布流程。本技能为构建步骤;请先使用
sales-funnel-blueprint
设计漏斗,若仍未确定方案,可通过
funnel-platform-picker
确认自托管是否为合适选择。

Step 1: choose how it runs

步骤1:选择运行方式

PathCost at low volumeOps burdenUse it for
Cloudflare WorkersEffectively $0 plus a PostgresNo servers, no patchingDefault for production. Funnel pages are mostly static assets, which Workers serves free and unmetered
DockerCost of one VPS or container hostYours: upgrades, backups, uptimeLocal evaluation in two minutes, or a server you already run and want the data on
Source checkout (Node)Cost of one VPS or container hostYoursModifying Autonnel itself, or running the Node build directly
Recommend Workers unless the operator has a specific reason not to: the pricing model fits funnels almost exactly, and it removes the entire class of work that makes people avoid self-hosting.
Use Docker for the first look regardless. It is the fastest way to see the product, and nothing you build locally is wasted: the same schema and the same admin UI back both paths.
路径低流量成本运维负担使用场景
Cloudflare Workers实际成本接近0,仅需额外支付Postgres费用无需服务器,无需补丁生产环境默认选择。漏斗页面多为静态资源,Workers可免费无限量提供服务
Docker一台VPS或容器主机的成本需自行负责:升级、备份、可用性两分钟内完成本地评估,或部署至已有服务器并保留数据
源码检出(Node)一台VPS或容器主机的成本需自行负责修改Autonnel本身,或直接运行Node构建版本
除非运营者有特定理由,否则推荐使用Workers:其定价模型完全适配漏斗场景,且消除了人们回避自托管的所有运维工作。
无论最终选择哪种路径,首次试用建议使用Docker。这是最快体验产品的方式,且本地构建的内容不会浪费:两种路径使用相同的数据库 schema 和管理UI。

Step 2a: Cloudflare Workers (the near-free production path)

步骤2a:Cloudflare Workers(近乎免费的生产路径)

Why the cost is close to zero

成本接近0的原因

Funnel traffic is overwhelmingly requests for pages, images and scripts. On Workers those are static asset requests, which are free and unlimited, with no storage cost - only requests that invoke the Worker (server-rendered pages, checkout, API) are billed. A funnel's dynamic surface is small: the order form, the upsell accept, the postback queue.
Verified Cloudflare free-plan limits (checked 2026-08; confirm current numbers before you rely on them):
ResourceWorkers Free plan
Static asset requestsFree and unlimited, no storage charge
Worker invocations100,000 requests/day
Hyperdrive (Postgres pooling)Available on Free, 100,000 database queries/day
Workers KV (page cache)100,000 reads/day, 1,000 writes/day, 1 GB storage
Cron TriggersSupported (the repo ships a
scheduled
handler)
What is not free: Postgres. Hyperdrive pools connections to a database you supply, so you still need a Postgres provider. Managed providers have their own free tiers with their own limits, and that is the one line item to plan for.
The first ceiling you will actually hit is KV writes, not requests. 1,000 writes/day is generous for serving pages and thin for publishing them, because publishing invalidates and refreshes cached entries. A day of heavy editing can burn it while traffic is nowhere near any limit. If publishing starts failing before traffic does, that is this limit, not a bug.
漏斗流量绝大多数为页面、图片和脚本请求。在Workers上,这些属于静态资源请求,免费且无限制,无存储成本——仅触发Worker的请求(服务端渲染页面、结账、API)会产生费用。漏斗的动态交互面很小:订单表单、追加销售确认、回传队列。
已验证的Cloudflare免费计划限制(2026年8月核实;使用前请确认当前最新数值):
资源Workers免费计划
静态资源请求免费无限量,无存储费用
Worker调用每日100,000次请求
Hyperdrive(Postgres连接池)免费可用,每日100,000次数据库查询
Workers KV(页面缓存)每日100,000次读取,每日1,000次写入,1GB存储
Cron触发器支持(仓库提供了
scheduled
处理器)
非免费项:Postgres。Hyperdrive连接到您提供的数据库,因此您仍需Postgres服务商。托管服务商均有各自的免费层及限制,这是唯一需要规划的成本项。
实际会遇到的第一个瓶颈是KV写入次数,而非请求次数。每日1,000次写入对于页面服务来说足够,但对于发布操作则较为紧张,因为发布会失效并刷新缓存条目。大量编辑的一天可能会耗尽该限额,而此时流量远未达到任何其他限制。如果发布在流量未达限制前就开始失败,那就是这个限额导致的,而非Bug。

Deploy

部署

The repository ships the whole Workers toolchain: worker entry with the cron
scheduled
handler (
src/cf-worker.ts
),
wrangler.toml
generation, KV cache wiring and Hyperdrive for Postgres.
bash
npx wrangler login
npx wrangler kv namespace create CACHE_KV
npx wrangler hyperdrive create autonnel-db --connection-string="postgresql://user:pass@host:5432/autonnel"
Each command prints an id. Put them in
.env
next to the project:
bash
CF_WORKER_NAME=my-funnels
CF_KV_NAMESPACE_ID=<id from kv namespace create>
CF_HYPERDRIVE_CONFIG_ID=<id from hyperdrive create>
Then set the secrets and deploy:
bash
npx wrangler secret put DATABASE_URL
npx wrangler secret put AUTH_SESSION_SECRET            # openssl rand -hex 32
npx wrangler secret put CREDENTIALS_ENCRYPTION_KEY     # openssl rand -base64 32
npm run deploy:cf
deploy:cf
builds and generates
wrangler.toml
from the template first, so there is no separate generate step. Cron expressions are read from the app's cron registry rather than hand-written into the config - do not edit the generated
wrangler.toml
by hand, it is overwritten on every build.
If a
CF_*
variable is missing, generation fails and names the variable. That is the intended behaviour; there are no silent defaults for these.
Apply the database schema once against the same Postgres before the first visit, then open the Worker URL and complete the
/setup
wizard to create the admin account.
Also available:
npm run dev:cf
(dev server on the Workers runtime) and
npm run preview:cf
(local preview via
wrangler dev
). Prefer these over plain
astro dev
when the target is Workers, because the runtime differs.
仓库提供了完整的Workers工具链:包含Cron
scheduled
处理器的Worker入口(
src/cf-worker.ts
)、
wrangler.toml
生成、KV缓存配置和用于Postgres的Hyperdrive。
bash
npx wrangler login
npx wrangler kv namespace create CACHE_KV
npx wrangler hyperdrive create autonnel-db --connection-string="postgresql://user:pass@host:5432/autonnel"
每个命令都会输出一个ID。将这些ID放入项目旁的
.env
文件中:
bash
CF_WORKER_NAME=my-funnels
CF_KV_NAMESPACE_ID=<kv namespace create输出的ID>
CF_HYPERDRIVE_CONFIG_ID=<hyperdrive create输出的ID>
然后设置密钥并部署:
bash
npx wrangler secret put DATABASE_URL
npx wrangler secret put AUTH_SESSION_SECRET            # openssl rand -hex 32
npx wrangler secret put CREDENTIALS_ENCRYPTION_KEY     # openssl rand -base64 32
npm run deploy:cf
deploy:cf
会先构建并从模板生成
wrangler.toml
,因此无需单独的生成步骤。Cron表达式从应用的Cron注册表读取,而非手动写入配置——请勿手动编辑生成的
wrangler.toml
,每次构建都会覆盖它。
如果缺少任何
CF_*
变量,生成过程会失败并提示缺失的变量名。这是预期行为;这些变量没有静默默认值。
首次访问前,先对同一个Postgres应用数据库schema,然后打开Worker URL并完成
/setup
向导以创建管理员账户。
还提供:
npm run dev:cf
(Workers运行时上的开发服务器)和
npm run preview:cf
(通过
wrangler dev
进行本地预览)。当目标环境为Workers时,优先使用这些命令而非普通的
astro dev
,因为运行时存在差异。

Operating it afterwards

后续运维

The CLI commands below need database access, not a container. From a checkout with
DATABASE_URL
pointing at the same Postgres:
bash
npx autonnel admin:create you@example.com 'a-strong-password'
npx autonnel password:reset you@example.com
以下CLI命令需要数据库访问,无需容器。在检出的代码中,将
DATABASE_URL
指向同一个Postgres:
bash
npx autonnel admin:create you@example.com 'a-strong-password'
npx autonnel password:reset you@example.com

Step 2b: Docker (local evaluation, or your own server)

步骤2b:Docker(本地评估或自有服务器)

bash
curl -O https://raw.githubusercontent.com/autonnel/autonnel/master/docker-compose.yml
docker compose up
Open http://localhost:4321 and complete
/setup
. The compose file starts Postgres, applies the schema, and runs the app. Nothing else is needed to boot: store, payments, media storage, email and AI are configured later in the admin UI, and only for the features actually used.
Before exposing it on a public host, put real secrets in a
.env
next to
docker-compose.yml
- the shipped defaults are insecure development values, and they exist only so the first local run needs zero configuration:
bash
AUTH_SESSION_SECRET=$(openssl rand -hex 32)
CREDENTIALS_ENCRYPTION_KEY=$(openssl rand -base64 32)
ADMIN_DOMAIN=admin.example.com
Generate each value once and keep it stable. Rotating
AUTH_SESSION_SECRET
invalidates sessions; rotating
CREDENTIALS_ENCRYPTION_KEY
makes stored provider credentials unreadable, which means re-entering every payment and platform credential.
Multi-arch images are published to GHCR for a plain
docker run
against an existing database:
bash
docker run -p 4321:4321 \
  -e DATABASE_URL="postgresql://user:pass@host:5432/autonnel" \
  -e ADMIN_DOMAIN="admin.example.com" \
  -e AUTH_SESSION_SECRET="$(openssl rand -hex 32)" \
  -e CREDENTIALS_ENCRYPTION_KEY="$(openssl rand -base64 32)" \
  ghcr.io/autonnel/autonnel:latest
Pin an exact tag in production rather than
:latest
, and re-apply the schema after pulling a newer tag - the compose file's one-shot schema service re-runs on every
docker compose up
. Health endpoint for orchestrators:
/api/health
(covers database and cache connectivity).
Admin CLI inside a container:
bash
docker compose exec app node dist/cli/index.js admin:create you@example.com 'a-strong-password'
bash
curl -O https://raw.githubusercontent.com/autonnel/autonnel/master/docker-compose.yml
docker compose up
打开http://localhost:4321并完成
/setup
。Compose文件会启动Postgres、应用schema并运行应用。启动无需其他操作:存储、支付、媒体存储、邮件和AI可稍后在管理UI中配置,且仅针对实际使用的功能。
在公网主机上暴露服务前,请在
docker-compose.yml
旁的
.env
文件中设置真实密钥——默认提供的是不安全的开发环境值,仅为了首次本地运行无需任何配置:
bash
AUTH_SESSION_SECRET=$(openssl rand -hex 32)
CREDENTIALS_ENCRYPTION_KEY=$(openssl rand -base64 32)
ADMIN_DOMAIN=admin.example.com
每个值仅需生成一次并保持稳定。轮换
AUTH_SESSION_SECRET
会使会话失效;轮换
CREDENTIALS_ENCRYPTION_KEY
会使存储的服务商密钥不可读,这意味着需要重新输入所有支付和平台凭证。
多架构镜像已发布至GHCR,可针对现有数据库使用普通
docker run
命令:
bash
docker run -p 4321:4321 \
  -e DATABASE_URL="postgresql://user:pass@host:5432/autonnel" \
  -e ADMIN_DOMAIN="admin.example.com" \
  -e AUTH_SESSION_SECRET="$(openssl rand -hex 32)" \
  -e CREDENTIALS_ENCRYPTION_KEY="$(openssl rand -base64 32)" \
  ghcr.io/autonnel/autonnel:latest
生产环境请固定具体标签而非使用
:latest
,拉取新版本标签后需重新应用schema——Compose文件的一次性schema服务会在每次
docker compose up
时重新运行。编排器的健康检查端点:
/api/health
(涵盖数据库和缓存连接)。
容器内的管理CLI:
bash
docker compose exec app node dist/cli/index.js admin:create you@example.com 'a-strong-password'

Step 2c: source checkout (Node)

步骤2c:源码检出(Node)

For modifying Autonnel itself, or running the Node build on a host you already own. Requires Node 22+ and a Postgres:
bash
npm create autonnel@latest my-funnel
cd my-funnel
cp .env.example .env   # set DATABASE_URL and ADMIN_DOMAIN
pnpm install           # pnpm 10+; the repo pins overrides npm would ignore
npm run db:push
npm run dev            # or: npm run build && npm run start
This clones the repository and drops its git history. The schema lives at
prisma/schema.prisma
in the checkout;
db:push
syncs it. Admin CLI:
npx autonnel admin:create you@example.com 'a-strong-password'
from the project directory.
适用于修改Autonnel本身,或在已有主机上运行Node构建版本。需要Node 22+和Postgres:
bash
npm create autonnel@latest my-funnel
cd my-funnel
cp .env.example .env   # 设置DATABASE_URL和ADMIN_DOMAIN
pnpm install           # 需要pnpm 10+;仓库固定了npm会忽略的依赖覆盖项
npm run db:push
npm run dev            # 或:npm run build && npm run start
这会克隆仓库并清除其Git历史。schema位于检出代码的
prisma/schema.prisma
db:push
会同步schema。管理CLI:在项目目录中运行
npx autonnel admin:create you@example.com 'a-strong-password'

Step 3: configure only what the funnel needs

步骤3:仅配置漏斗所需的功能

In the admin UI under Settings:
SettingNeeded forOptions
EcommerceProduct and order dataShopify, WooCommerce, Picocart
PaymentsTaking moneyStripe, PayPal
StorageImage/video uploadsAny S3-compatible bucket (R2, S3, MinIO)
EmailReceipts, recall campaignsSMTP, Resend, AWS SES
LLMAI page generationAny OpenAI-compatible endpoint
Ad platformsServer-side conversionsFacebook, TikTok, Google Ads, Bing
Order of operations that avoids rework: catalog first (it constrains what the checkout can sell), then payments, then storage, then email, then ad platforms last - tracking is verified against real orders, so it needs the rest working first.
On Workers, R2 is the obvious storage choice: it is S3-compatible and keeps media egress inside Cloudflare.
在管理UI的设置中:
设置项用途可选方案
电商产品和订单数据Shopify、WooCommerce、Picocart
支付收款Stripe、PayPal
存储图片/视频上传任何兼容S3的存储桶(R2、S3、MinIO)
邮件收据、召回营销SMTP、Resend、AWS SES
LLMAI页面生成任何兼容OpenAI的端点
广告平台服务端转化跟踪Facebook、TikTok、Google Ads、Bing
避免返工的操作顺序:先配置商品目录(它会约束结账页可售卖的内容),然后是支付,接着是存储,再是邮件,最后是广告平台——跟踪需要基于真实订单验证,因此需要其他功能先正常工作。
在Workers上,R2是显而易见的存储选择:它兼容S3,且媒体流量可在Cloudflare内部流转。

Step 4: build the pages

步骤4:构建页面

Funnel roles map onto the funnel spec directly:
Role in funnelPurposeMultiple per funnel?
LANDING
Entry page(s), one per traffic angleYes
CHECKOUT
Order formNo - one per funnel
UPSELL
Post-purchase offers, ordered into a chainYes
THANKYOU
ConfirmationNo
ERROR
Payment failure / fallbackNo
These are the roles a page takes inside a funnel. Over the API a page's own type is
CHECKOUT | THANKYOU | ERROR | UPSELL | CUSTOM
- landing pages are created as
CUSTOM
and become landing pages by being bound into a funnel as
LANDING
.
Two editors are available: a component-based visual editor whose output is diffable JSON, and raw HTML for imported pages. Prefer the component editor for anything that will be A/B tested or edited by an agent later - JSON diffs review cleanly, HTML blobs do not.
Build the checkout before the landing page. The checkout determines what can actually be sold and at what price, and a landing page written first will promise something the checkout cannot deliver.
漏斗角色与漏斗规范直接对应:
漏斗中的角色用途每个漏斗可包含多个?
LANDING
入口页面,每个流量渠道对应一个
CHECKOUT
订单表单否——每个漏斗一个
UPSELL
售后优惠,按顺序组成链条
THANKYOU
确认页
ERROR
支付失败/回退页
这些是页面在漏斗中的角色。通过API创建时,页面自身的类型为
CHECKOUT | THANKYOU | ERROR | UPSELL | CUSTOM
——着陆页创建为
CUSTOM
类型,通过绑定到漏斗并设置为
LANDING
角色成为着陆页。
提供两种编辑器:基于组件的可视化编辑器,输出可对比差异的JSON;以及用于导入页面的原始HTML。对于需要A/B测试或后续由Agent编辑的内容,优先使用组件编辑器——JSON差异易于审核,而HTML blob则不然。
先构建结账页,再构建着陆页。结账页决定了实际可售卖的商品及价格,若先写着陆页可能会承诺结账页无法实现的功能。

Step 5: wire the funnel

步骤5:配置漏斗

  • Create the funnel, then attach pages with their role and order.
  • A
    LANDING
    page can belong to only one funnel. Attempting to reuse one across funnels is rejected - clone it instead.
  • THANKYOU
    and
    ERROR
    are auto-bound from existing pages at creation if any exist; if the tenant has none, create them before going live. A funnel with no error page fails silently on declined payments.
  • The funnel's promotional URL (the first landing page) is what goes into ad campaigns. Steps are reachable by short slugs so the buyer's path across steps survives cross-domain hops.
  • Publish is explicit and versioned per page and per funnel - publishing a page does not publish the funnel binding.
  • 创建漏斗,然后附加页面并指定其角色和顺序。
  • 一个
    LANDING
    页面只能属于一个漏斗。尝试跨漏斗复用会被拒绝——请克隆页面。
  • 创建漏斗时,若已有
    THANKYOU
    ERROR
    页面,会自动绑定;若租户没有这些页面,请在上线前创建。没有错误页的漏斗在支付失败时会静默出错。
  • 漏斗的推广URL(第一个着陆页)用于广告投放。各步骤可通过短slug访问,因此买家的跨步骤路径可跨域保持。
  • 发布操作是显式的,且每个页面和漏斗都有版本控制——发布页面不会发布漏斗绑定关系。

Step 6: instrument before sending traffic

步骤6:引流前配置监控

Non-negotiable pre-launch checks:
  1. One real end-to-end purchase, on a real payment provider, including: base order, one accepted upsell, one declined upsell, the thank-you page, the receipt email, and the order appearing in the connected store.
  2. One refund on that order, to confirm the refund path works per charge.
  3. Click id coverage - confirm
    fbclid
    /
    ttclid
    /
    gclid
    /
    msclkid
    reach the order record. See
    server-side-conversion-tracking
    .
  4. Server-side conversion arriving in each ad platform's event debugger, with the click id attached and no duplicate against the browser event.
  5. Error page reachable by forcing a declined card.
On Workers, add one more: confirm the cron
scheduled
handler is firing (queued postbacks and recall campaigns depend on it). A deploy that silently lost its cron triggers looks healthy while background work quietly stops.
Only then increase spend. A funnel that has not had a real transaction pushed through it has an unknown, not a low, failure rate.
上线前必须完成的检查:
  1. 一次完整的真实购买流程,使用真实支付服务商,包括:基础订单、一次接受的追加销售、一次拒绝的追加销售、感谢页、收据邮件,以及订单出现在关联的商店中。
  2. 一次退款操作,确认针对该订单的退款流程正常。
  3. 点击ID覆盖——确认
    fbclid
    /
    ttclid
    /
    gclid
    /
    msclkid
    被记录到订单中。详见
    server-side-conversion-tracking
  4. 服务端转化数据到达每个广告平台的事件调试器,附带点击ID且不会与浏览器事件重复。
  5. 错误页可访问——通过使用无效卡片触发支付失败来验证。
在Workers上,需额外检查:确认Cron
scheduled
处理器在运行(排队的回传和召回营销依赖它)。如果部署时静默丢失了Cron触发器,系统看起来正常但后台工作会悄悄停止。
完成以上检查后再增加投放预算。未经过真实交易测试的漏斗,其失败率是未知的,而非低失败率。

Step 7: operate it from an agent over MCP

步骤7:通过MCP由Agent运维

The instance exposes its admin API as MCP tools at
/api/mcp
, so an agent can build and change funnels without the UI.
json
{
  "mcpServers": {
    "autonnel": {
      "transport": "http",
      "url": "https://<your-autonnel-host>/api/mcp",
      "headers": { "Authorization": "Bearer <your-api-key>" }
    }
  }
}
.mcp.json
at the project root for Claude Code;
claude_desktop_config.json
for Claude Desktop. Generate the key in admin → Settings → API Keys. Every call is scoped to that key's tenant. Read tools work with any key; mutating tools need
writeAccess
toggled on. Treat a write key as production credentials: one per agent, read-only keys for reporting, revoke rather than share.
Tools are self-describing - the client reads names, descriptions and input schemas on connect, so this skill does not repeat them. What follows is only what introspection cannot tell you.
实例在
/api/mcp
暴露管理API作为MCP工具,因此Agent无需UI即可构建和修改漏斗。
json
{
  "mcpServers": {
    "autonnel": {
      "transport": "http",
      "url": "https://<your-autonnel-host>/api/mcp",
      "headers": { "Authorization": "Bearer <your-api-key>" }
    }
  }
}
Claude Code使用项目根目录的
.mcp.json
;Claude Desktop使用
claude_desktop_config.json
。在管理后台→设置→API密钥生成密钥。每个调用都限定在该密钥对应的租户范围内。只读工具可使用任何密钥;修改工具需要开启
writeAccess
。将写入密钥视为生产凭证:每个Agent一个,只读密钥用于报表,应撤销而非共享。
工具是自描述的——客户端在连接时会读取名称、描述和输入schema,因此本技能不再重复这些内容。以下仅介绍 introspection 无法告知的信息。

A failed call still returns HTTP 200

失败的调用仍返回HTTP 200

This is the single thing most likely to make a client read a failure as a success.
ConditionWhat you actually get
Auth failureHTTP 401, no JSON-RPC frame at all
Unknown tool nameHTTP 200 with a real JSON-RPC
error
object carrying a numeric code - the only in-band failure shaped that way, because it happens before dispatch
Everything else: missing write access, validation failure, not found, conflict, server errorHTTP 200 with
result.isError === true
and the message in
result.content[0].text
, and no JSON-RPC
error
object
So: if
error
is present it is an unknown tool; otherwise check
result.isError
before trusting
result.content
. Read the message rather than retrying blindly - a validation failure names the exact field path, and a conflict names the rule that was hit.
这是最可能导致客户端将失败误认为成功的情况。
情况实际返回内容
认证失败HTTP 401,无JSON-RPC帧
未知工具名称HTTP 200,返回真实的JSON-RPC
error
对象,包含数字代码——这是唯一的带内失败形式,因为它发生在调度之前
其他所有情况:缺少写入权限、验证失败、未找到、冲突、服务器错误HTTP 200,返回
result.isError === true
,消息在
result.content[0].text
中,JSON-RPC
error
对象
因此:如果存在
error
则为未知工具;否则需先检查
result.isError
再信任
result.content
。请阅读消息而非盲目重试——验证失败会指明确切的字段路径,冲突会指明违反的规则。

Rules that cause most failed writes

最常导致写入失败的规则

  • A funnel step is
    { stepSlug, pageId }
    and nothing else.
    Order is array order. A step's role in the flow is the referenced page's own
    type
    ; there is no
    pageType
    ,
    order
    or
    subOrder
    field.
    stepSlug
    is required on
    add_funnel_page
    , must be unique within the funnel, and forms
    /n/{funnelId}/{stepSlug}
    . The schemas are
    .strict()
    , so an extra field is rejected rather than ignored.
  • Steps are keyed by the page they reference, so a page appears at most once per funnel, but the same page may be referenced by several funnels. There is no "belongs to one funnel only" rule.
  • There is no
    LANDING
    page type.
    Page
    type
    is
    CHECKOUT | THANKYOU | UPSELL | ERROR | CUSTOM
    , stored uppercase. A landing page is a
    CUSTOM
    page that a funnel references.
  • Thank-you and error steps are live references, not snapshots.
    create_funnel
    adds steps for the tenant's existing thank-you and error pages, creating them from templates if none exist. Editing that page later changes every funnel referencing it - clone it if one funnel should diverge.
  • Draft and published are separate. Writes land in
    draftData
    ;
    publish: true
    promotes it and invalidates the render cache. Write, verify with
    get_page
    , publish in a second call.
  • draftData
    is not structurally validated.
    Nothing checks
    root
    /
    content
    /
    zones
    , required props, or component type names before saving. A malformed document is accepted and only breaks at render time.
  • Component types must come from
    get_template({ key })
    , never from a remembered list. The component set is renamed and extended over releases, and because
    draftData
    is unvalidated, a stale component name saves cleanly and renders blank. A template's JSON is the authoritative shape.
  • draftData
    and
    htmlContent
    are not checked against the page's
    editorType
    .
    create_page
    accepts only
    draftData
    ;
    update_page
    accepts both on any page and writes whichever you send to a column the other renderer never reads. Match the field to the page's real editor type yourself.
  • Media before pages.
    upload_media
    fetches a URL server-side and returns a CDN URL for component props. Binary upload is REST-only, not an MCP tool.
  • get_stats
    counts unique users, not views.
    Five visits by one visitor is 1.
  • list_orders
    cannot filter by funnel
    , and its amounts divide minor units by 100 - correct for USD/EUR, wrong for JPY or BHD.
  • Ad spend is not available here. Core ads support is token-mode conversion postback only: no campaign or spend queries. Pull spend from the ad platform directly for ROAS.
  • 漏斗步骤仅包含
    { stepSlug, pageId }
    。顺序由数组顺序决定。步骤在流程中的角色由引用页面自身的
    type
    决定;不存在
    pageType
    order
    subOrder
    字段。
    stepSlug
    add_funnel_page
    中是必填项,且在漏斗内必须唯一,构成路径
    /n/{funnelId}/{stepSlug}
    。schema是
    .strict()
    ,因此多余字段会被拒绝而非忽略。
  • 步骤以引用的页面为键,因此一个页面在每个漏斗中最多出现一次,但同一页面可被多个漏斗引用。不存在“仅属于一个漏斗”的规则。
  • 没有
    LANDING
    页面类型
    。页面
    type
    CHECKOUT | THANKYOU | UPSELL | ERROR | CUSTOM
    ,存储为大写。着陆页是被漏斗引用的
    CUSTOM
    页面。
  • 感谢页和错误页是实时引用,而非快照
    create_funnel
    会为租户现有的感谢页和错误页添加步骤,若不存在则从模板创建。后续编辑该页面会修改所有引用它的漏斗——如果需要某个漏斗使用不同的页面,请克隆它。
  • 草稿和发布版本是分离的。写入内容会存入
    draftData
    publish: true
    会将其升级为发布版本并失效渲染缓存。先写入,用
    get_page
    验证,再通过第二次调用发布。
  • draftData
    不进行结构验证
    。保存前不会检查
    root
    /
    content
    /
    zones
    、必填属性或组件类型名称。格式错误的文档会被接受,仅在渲染时出错。
  • 组件类型必须来自
    get_template({ key })
    ,绝不能来自记忆的列表。组件集在版本迭代中会重命名和扩展,且由于
    draftData
    未验证,过时的组件名称会被正常保存但渲染为空。模板的JSON是权威格式。
  • draftData
    htmlContent
    不会与页面的
    editorType
    校验
    create_page
    仅接受
    draftData
    update_page
    在任何页面上都接受两者,并将您发送的内容写入另一个渲染器永远不会读取的列。请自行确保字段与页面实际的编辑器类型匹配。
  • 先上传媒体,再创建页面
    upload_media
    会从服务端获取URL并返回CDN URL用于组件属性。二进制上传仅支持REST,不支持MCP工具。
  • get_stats
    统计唯一用户数,而非浏览量
    。同一访客访问5次计为1次。
  • list_orders
    无法按漏斗过滤
    ,且金额会将最小单位除以100——对USD/EUR正确,但对JPY或BHD错误。
  • 广告支出数据无法在此获取。核心广告支持仅为令牌模式的转化回传:无广告活动或支出查询。如需计算ROAS,请直接从广告平台拉取支出数据。

Build order

构建顺序

list_products → list_templates → get_template → upload_media
  → create_page × N → get_page → edit draftData → update_page → get_page → update_page({ publish: true })
  → create_funnel → add_funnel_page × N (pageId + unique stepSlug) → get_funnel
Do not treat
entryStepSlug
as the URL to advertise.
It is literally
steps[0]
, and
create_funnel
appends the thank-you and error steps before you add anything, so on a funnel built this way
steps[0]
is the thank-you page. Read the
steps
array from
get_funnel
, pick the step whose
page.type
is
CUSTOM
, and fetch
/n/{funnelId}/{thatStepSlug}
to confirm the landing page renders before spending money on it.
To change a live page:
get_page
→ edit only the props you mean to change →
update_page
without publish →
get_page
to diff → publish. Never regenerate a whole
draftData
blob for one headline;
update_page
replaces the document wholesale, and component JSON carries prop values you did not write and cannot reconstruct.
list_products → list_templates → get_template → upload_media
  → create_page × N → get_page → edit draftData → update_page → get_page → update_page({ publish: true })
  → create_funnel → add_funnel_page × N (pageId + 唯一stepSlug) → get_funnel
请勿将
entryStepSlug
作为推广URL
。它实际上是
steps[0]
,而
create_funnel
会在您添加任何步骤前先追加感谢页和错误页,因此通过这种方式构建的漏斗,
steps[0]
是感谢页。从
get_funnel
读取
steps
数组,选择
page.type
CUSTOM
的步骤,访问
/n/{funnelId}/{thatStepSlug}
确认着陆页可正常渲染后再投放广告。
修改已上线页面的流程:
get_page
→ 仅编辑您想要修改的属性 → 不带publish参数调用
update_page
get_page
对比差异 → 发布。切勿为修改一个标题而重新生成整个
draftData
blob;
update_page
会完全替换文档,且组件JSON包含您未编写且无法重构的属性值。

REST is not the same surface

REST与MCP界面不同

Thirteen tools are also reachable over REST through a bridge that runs the identical schema and handler, so those cannot drift. Seven are MCP-only:
list_funnels
,
get_funnel
,
list_pages
,
get_page
,
list_products
,
deliver_order
,
get_stats
. Some of those have an older, independently written REST endpoint at the same path with a different response shape or parameter name (
GET /products
takes
q
, not
search
). Do not assume a REST endpoint matches the tool of the same name.
One path trap:
/api/v1.1/templates
lists email templates for Settings, not Puck page templates. Page templates are at
/api/v1.1/page-templates
.
13个工具也可通过REST访问,通过桥接器运行相同的schema和处理器,因此不会出现差异。7个工具仅支持MCP:
list_funnels
get_funnel
list_pages
get_page
list_products
deliver_order
get_stats
。其中部分工具在相同路径下有独立编写的旧版REST端点,响应格式或参数名称不同(
GET /products
接受
q
而非
search
)。请勿假设REST端点与同名工具一致。
一个路径陷阱:
/api/v1.1/templates
列出的是邮件模板(用于设置),而非Puck页面模板。页面模板位于
/api/v1.1/page-templates

Honest operational cost

真实运维成本

Say this out loud rather than letting the user discover it after launch:
  • Workers path: no servers to patch, but you own the Postgres and its backups, and free-plan limits are daily caps that fail operations rather than billing you. Know which limit you are closest to before a traffic spike.
  • Docker path: upgrades, backups and uptime are yours. Schema changes ship with the image and must be applied on upgrade.
  • Payment configuration and its PCI scope are the operator's responsibility on every path.
  • There is no support SLA on the self-hosted build. Issues and Discussions on GitHub are the channel.
  • Budget hours, not minutes, for the first production deployment. The two-minute number is the local Docker evaluation, not a launch.
Documentation: https://autonnel.com/docs · Issues and Discussions: https://github.com/autonnel/autonnel
请明确告知用户,而非让他们上线后才发现:
  • Workers路径:无需补丁服务器,但您需负责Postgres及其备份,且免费计划的限额是每日上限,会导致操作失败而非计费。流量高峰前请了解您最接近哪个限额。
  • Docker路径:升级、备份和可用性均由您负责。schema变更随镜像发布,升级时必须应用。
  • 无论选择哪种路径,支付配置及其PCI合规范围均由运营者负责。
  • 自托管版本无服务级别协议(SLA)支持。问题和讨论请通过GitHub渠道提交。
  • 首次生产部署请按小时规划时间,而非分钟。两分钟是本地Docker评估的时间,而非上线时间。