QalamList QalamList

QalamList · Manual

Deploy once. Own the list.

Everything you need to run QalamList on your own Cloudflare account — from ./deploy.sh to your first waitlist, the API, and wiring signups into the tools you already use.

Last updated · June 30, 2026 · 15 sections

I have 5 minutes.

5-Minute Quick Start

You don't need to read the full manual to get started — six steps from clone to live. The rest of this page is here when you want the details.

  1. Clone or download the repo from your GitHub invite — git clone <repo> or grab the ZIP.
  2. Run the deployer./deploy.sh logs into Cloudflare, creates the D1 database, runs migrations, and deploys.
  3. Create your admin — open your Worker URL at /setup and set the first account.
  4. Create a waitlist — Dashboard → New Waitlist.
  5. Copy the widget — grab the embed snippet (or the hosted link) from the waitlist page.
  6. Done — you're collecting signups.

Need more details? Continue below.

Contents
01

The problem

Author writes this

Why QalamList exists

Your voice, your story. The pain of renting your own waitlist — monthly fees for a list of emails that lives on someone else's infrastructure. This is the opening; make it yours.

02

What QalamList is

Author writes this

One paragraph, your words

Self-hosted waitlist software on Cloudflare's edge. Pay once, run it forever, keep your data. Frame it the way only you can.

03

How it's different

Author writes this

The comparison, your call

Where QalamList wins against hosted services like Waitlister and GetWaitlist — pricing model, data ownership, edge runtime. Tell it straight, without leaning on overstated numbers.

04

Prerequisites

There is almost nothing to install by hand. The deployment script pulls the rest for you.

  • Node.js — the one thing you install yourself. It brings npm and npx, which everything else hangs off. Any current Node.js LTS release is supported.
  • A Cloudflare account — the free tier is enough (100k requests/day, 5M rows read). QalamList runs on Workers + D1.
  • A GitHub account (ideal) — after purchase you get an invite to the source repo, so you can pull updates.

What ./deploy.sh installs automatically when you run it:

  • Project dependencies + Wrangler via npm install — the Cloudflare CLI comes with the repo.
  • Your JWT secret is generated with openssl (already on macOS and Linux, no action from you).
One system binary The deployment script verifies that jq is installed before continuing. If you don't have it, install it once: brew install jq (macOS) or apt install jq (Linux). That's the only manual step beyond Node.
Ask any coding agent
Use the qalamlist-ops-companion skill in .agents/skills/ to run its prerequisites workflow — check what I have installed and list exactly what's missing before I deploy.

The companion ships in the repo at .agents/skills/qalamlist-ops-companion/. Coding agents that support skills (Claude Code, opencode, and similar) load it from there when you ask; it handles installs, read-only health checks, and deployment without taking production actions until you say so.

05

Install & deploy

Clone the repo, then run one command:

# from the project root
./deploy.sh

That single script walks the whole path for you, in order:

  • checks prerequisites (npx, jq, Wrangler);
  • logs you into Cloudflare in the browser (or reuses an existing session);
  • creates the D1 database (qalamlist-db) if it doesn't exist, and writes its id into wrangler.production.jsonc;
  • applies the schema baseline + migrations;
  • generates JWT_SECRET with openssl;
  • prompts for your Turnstile site & secret keys;
  • runs wrangler deploy.
Turnstile keys Bot protection needs a (free) Cloudflare Turnstile widget. Create one, add your domain to its hostname allowlist, and keep the site + secret keys ready — the script asks for them. You can manage them later as secrets.
Ask any coding agent
Use the qalamlist-ops-companion skill to run the doctor workflow, then guide me through ./deploy.sh one step at a time. Ask for approval before running anything that touches Cloudflare or production.
06

First login

After deployment, open your Worker URL to create your administrator account. On a fresh database the app sends you to /setup — email, password, confirm. That screen appears exactly once; once the first user exists, every later visit lands on /login.

The first-run setup screen
First run only: /setup creates the admin account.
The QalamList login screen
Every visit after: /login.
07

Create a waitlist

From the dashboard, open Waitlists → New. Give it a name, an optional description, and your privacy-policy and terms URLs if you have them. Leave them blank and the signup form's consent checkbox falls back to QalamList's built-in /privacy page — so you're covered out of the box, and you can point at your own policy later.

The waitlists index
Your waitlists, each with a live signup count and open/paused state.
The new waitlist form
Name, description, and legal URLs. Toggles come next, on the edit page.
08

Fields & toggles

Open a waitlist and hit Edit. This is where you decide what the form collects and how it behaves. Each switch earns its keep:

  • Open for signups — pause without deleting anything. New signups are refused while off; your list stays intact.
  • Bot protection (Turnstile) — on by default, and highly recommended to prevent spam and abuse. Turn it off only for low-friction contexts like an internal beta, where you trust the audience.
  • Collect name — adds an optional name field. Skip it for a single-tap email capture; enable it when you want to address people like humans.
  • Collect UTM attribution — captures first- and last-touch UTM parameters so you know where signups came from. Disable it for privacy-sensitive lists; doing so also empties the source/campaign panels in analytics.
The edit waitlist page with its toggles
Every field and behavior is one checkbox away.
09

Share the form

Two ways to collect, same waitlist behind both.

The hosted page. Every waitlist gets a public link at /w/<id>. Copy it into launch emails, teaser pages, social posts — no build needed.

The hosted public signup page
The hosted page. Email, consent, Turnstile — handled.

The embeddable widget. Drop a snippet into any HTML page and the form renders inline. It themes with three CSS variables, and the rest derive from them:

<!-- size: sm | md | lg -->
<div class="ql-widget"
     data-size="md"
     data-turnstile-size="normal"
     data-input-class=""
     data-btn-class="">
</div>
<script async src="https://your-host/widget/w/WAITLIST_ID.js"></script>

Theming. Override three CSS variables on .ql-widget and the rest of the widget inherits:

.ql-widget {
  --ql-primary: #ff953d; /* brand / buttons */
  --ql-fg:      #0F0F0F; /* text */
  --ql-bg:      #FAF8F5; /* background */
}
  • Dark mode: add a .dark class to any parent.
  • UTM capture: if attribution is on, first- and last-touch UTMs are read from the page URL automatically — add ?utm_source=… to any link pointing at the page.
Turnstile — configure it, then prove it Keep bot protection on and make sure your Turnstile keys are set correctly (they're required at deploy time). To be 100% sure it's working, load the widget on your actual site and submit the form once — if the challenge renders and the signup lands, you're covered.
The waitlist detail page with hosted link and embed panels
The distribution panel: copy the hosted link, or grab the embed snippet at any size.
10

The widget, in action

That's not a mock — it's the live embed rendered inside a real page. The form, the consent line, and the Turnstile challenge all inherit the surrounding site. Override three CSS variables and it matches your brand down to the button.

The QalamList widget embedded in a website — a clean form blending into the page
The embeddable widget, rendered inline on a host page.
11

Managing signups

Inside a waitlist, the table is your working surface. Search by email, or filter by UTM — switching between first touch and last touch, and drilling into source, medium, or campaign. Pagination keeps the query light.

  • Remove a signup inline (confirmed, irreversible).
  • Export CSV for the whole list, or import an existing list back in. The exported unsubscribe URLs are real routes.
  • Per-signup detail — click any row for the full record, including the complete UTM breakdown.
A single signup's detail view
One signup, full attribution — first and last touch, term and content included.
12

Analytics

Two levels. The overview rolls up every waitlist you own: a 30-day signup velocity chart, top sources (last touch), and top countries. The per-waitlist view adds quick stats — total, last 7 days, consent rate, top source — plus a 7-day sparkbar, and a dedicated analytics page for deeper cuts.

The dashboard overview with analytics
Overview: velocity across all lists, with source and country breakdowns.
The per-waitlist analytics dashboard
The per-waitlist analytics page — where the attribution story gets specific.
Gating Analytics only show what you collected. Turn Collect UTM attribution off and the source/medium/campaign panels stay empty by design — privacy is a toggle, not an afterthought.
13

Team members

You don't have to run it alone. Under Users, add a teammate by email and QalamList creates the account with a random temporary password — no activation email, no reset link. The password is shown to you once, right there in the confirmation; share it with them over whatever channel you trust.

The Users page with several team members listed
Add a teammate by email; their temp password is shown once for you to share.

On first sign-in they're sent straight to /reset-password and have to set their own before doing anything else. Need to let someone back in? Reset Password hands you a fresh temp password and flags the account for a forced change again. You can't delete your own account, and every add, reset, and delete is written to the audit log.

Why no email? This is the Metabase pattern on purpose: you vouch for the person and hand them the one-time password yourself. It keeps QalamList dependency-free — no SMTP provider required just to add teammates.
14

The REST API

Want to wire QalamList into your app or other tools? Covered. QalamList ships with a REST API you authenticate to with API keys — scope a key to a single waitlist, or leave it open to all of them. Create one under API Keys; the full key is shown once at creation, so copy it then (or rotate it later).

The API keys management page
Name a key, scope it, copy it immediately. The prefix is stored for later recognition.

The API is OpenAPI-first — every endpoint is documented and can be imported into Postman, Bruno, or Insomnia, or generated into client SDKs. Browse and try them live in the interactive API docs UI. A typical signup call looks like:

curl -X POST https://your-host/api/w/WAITLIST_ID/signups \
  -H "Authorization: Bearer QL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"email":"friend@example.com","name":"Friend"}'

Two things worth knowing: the API path skips Turnstile — your key is the auth, so the challenge only guards the public form — and requests are rate-limited per IP to keep abuse bounded.

15

Webhooks & integrations

Don't poll. Create a webhook under Webhooks and QalamList POSTs you a signed event every time someone signs up. Each delivery carries an X-QalamList-Signature header — an HMAC-SHA256 hex digest of the raw body — so you can prove the request came from you.

The webhooks management page
Register a URL, get a signing secret, verify every delivery.

Verify on your end before trusting the payload:

import crypto from 'node:crypto';

export default function handler(req, res) {
  const sig = req.headers['x-qalamlist-signature'];
  const expected = crypto
    .createHmac('sha256', process.env.QL_WEBHOOK_SECRET)
    .update(req.rawBody)            // verify against the raw body
    .digest('hex');
  if (!safeEqual(sig, expected)) return res.status(401).end();
  // …do your thing with req.body
  res.status(200).end();
}
Wire it anywhere Anything that accepts an HTTP POST works. Point the webhook at Pipedream to fan out to Slack, Discord, email, a sheet, or a CRM — no custom server required. Zapier and n8n take the same input.