self-hosted-funnel-launch

Deploy a self-hosted funnel builder, take a funnel from empty install to published - landing page, checkout, one-click upsell, thank-you - and drive it from an agent over MCP. Covers deploying to Cloudflare Workers inside the free tier or to Docker, wiring payments, catalog and conversion tracking,

By autonnel · 48,587 installs

npx skills add autonnel/autonnel-skills --skill self-hosted-funnel-launch

Source repository · Upstream listing

Self Hosted Funnel Launch Take a funnel from nothing to published on infrastructure the operator controls, using [Autonnel](https://github.com/autonnel/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. Step 1: choose how it runs Path Cost at low volume Ops burden Use it for Cloudflare Workers Effectively $0 plus a Postgres No servers, no patching Default for production. Funnel pages are mostly static assets, which Workers serves free and unmetered Docker Cost of one VPS or container host Yours: upgrades, backups, uptime Local evaluation in two minutes, or a server you already run and want the data on Source checkout (Node) Cost of one VPS or container host Yours Modifying 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. Step 2a: Cloudflare Workers (the near free production path) Why the cost is close to zero 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): Resource Workers Free plan Static asset requests Free and unlimited, no storage charge Worker invocations 100,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 Triggers Supported (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. 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. Each command prints an id. Put them in .env next to the project: Then set the secrets and deploy: 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. Operating it afterwards The CLI commands below need database access, not a container. From a checkout with DATABASE URL pointing at the same Postgres: Step 2b: Docker (local evaluation, or your own server) Get the repository from <https://github.com/autonnel/autonnel (Apache 2.0), check out a release tag, and read its docker compose.yml it declares the images and ports that will run. From that checkout: 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: 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: Keep an exact tag pinned 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: Step 2c: source checkout (Node) For modifying Autonnel itself, or running the Node build on a host you already own. Requires Node 22+ and a Postgres: 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. Step 3: configure only what the funnel needs In the admin UI under Settings : Setting Needed for Options Ecommerce Product and order data Shopify, WooCommerce, Picocart Payments Taking money Stripe, PayPal Storage Image/video uploads Any S3 compatible bucket (R2, S3, MinIO) Email Receipts, recall campaigns SMTP, Resend, AWS SES LLM AI page generation Any OpenAI compatible endpoint Ad platforms Server side conversions Facebook, 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. Step 4: build the pages Funnel roles map onto the funnel spec directly: Role in funnel Purpose Multiple per funnel? LANDING Entry page(s), one per traffic angle Yes CHECKOUT Order form No one per funnel UPSELL Post purchase offers, ordered into a chain Yes THANKYOU Confirmation No ERROR Payment failure / fallback No 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. Step 5: wire the funnel Create the funnel, then attach pages with their role and order. The same page may be referenced by several funnels. The only uniqueness rule the domain enforces is that a stepSlug is unique within one funnel (and a page appears at most once per funnel) there is no cross funnel rejection to design around. Clone a landing page only when you want the two funnels to diverge, because an edit to a shared page lands on every funnel referencing it. 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 URL that goes into ad campaigns is the landing page's own slug on a storefront host https://shop.example.com/{page.slug} never a /n/ step URL. /n/{funnelId}/{stepSlug} redirects forward to the next step, so it belongs in a page's CTA link, not in an ad. Writing the CTA that way keeps the buyer's path stable when a step is swapped for another page. Publish is explicit and versioned per page and per funnel publishing a page does not publish the funnel binding. Step 6: instrument before sending traffic 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. Step 7: operate it from an agent over MCP The instance exposes its admin API as MCP tools at /api/mcp , so an agent can build and change funnels without the UI. Calling /api/mcp with raw HTTP rather than an MCP client has two transport requirements the SDK normally hides. The endpoint speaks Streamable HTTP: the request must carry Accept: application/json, text/event stream anything less is rejected with 406 before the tool layer is reached and a success comes back as an SSE frame ( event: message / data: {...} ), so the JSON RPC envelope has to be unwrapped from data: before parsing. One more trap in front of the app entirely: an instance behind Cloudflare with the default managed bot rules rejects the Python standard library's user agent . A urllib client sends Python urllib/3.x and gets 403 with the plain text body error code: 1010 on every path including /api/health , which reads exactly like an auth or permission failure and is not JSON, so a client that parses the body throws before it can see the status. Only that agent string is blocked curl , python requests , httpx , axios , node and an empty UA all pass. If you script this in Python, set an explicit User Agent or use requests / httpx ; if every call including unauthenticated ones returns 403 , this is why, and no API key will fix it. .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, descript