What keeps Mooflair running in production, and the checklist to go through before launch. In short: two Docker Compose projects (mooflair and engine), neither backed up, a monitor service emails administrators when an engine goes down or errors pile up, /api/health answers uptime monitors, and /status shows visitors what works right now.
Checklist before launch
| # | Item | Where | Done by |
|---|---|---|---|
| 1 | Tests pass: npm run test:billing, test:permission, test:table, test:walker | Tests | code |
| 2 | An uptime monitor on /api/health and /status | Health and status | you (UptimeRobot, Better Stack…) |
| 3 | Administrators have a verified email (and WhatsApp) for alerts | Alerts | you |
| 4 | Email domain records: SPF, DKIM (from Mailgun), DMARC | Email domain | you, at your DNS host |
| 5 | Encryption keys (ENCRYPTION_KEY_*, and secret.jwt in configuration.json) stored offline | Secrets | you |
| 6 | Business details filled in (name, address, email, tax ID, PSE number) | Admin › Configuration › Site | you |
| 7 | Legal texts checked by a lawyer (Terms, Privacy, Refund) | content/page/<locale>/*.mdx | you |
| 8 | PSE registration (Indonesian electronic system operator) done, number added | PSE | you |
| 9 | PayPal sandbox checklist passed, then live credentials | PayPal | you |
| 10 | Engine capacity in Admin › Engine is about how many browsers fit each engine's host (its RAM less resource.engine.reserve, over the browser RAM in Admin › Configuration › Resources) | you | |
| 11 | Every engine has MOOFLAIR_KEY (mooflair's public key) and MOOFLAIR_URL, and reaches the other engines' ENGINE_PUBLIC_URL (Drive copies) | you |
Tests
Scripts in scripts/, run with the dev database in .env (they leave nothing behind):
| Command | Checks | Needs |
|---|---|---|
npm run test:billing | Quotes (flat, per seat, seat limits), coupons (percent, fixed per currency, wrong plan, expired, used up, case), whole-rupiah rounding, a missing price, changes (prorated upgrade, monthly → yearly, downgrade at renewal, the same plan, IDR shown and USD charged at the checkout rate) | MongoDB with the seeded plans |
npm run test:permission | The permission table (owner, admin, member, guest) and who may use a dataset | nothing |
npm run test:table | Table queries, claims, test rows, per-account tables | MongoDB |
npm run test:walker | Runs through the real engine: called flows, error flow, pause, queue, priority, loops, cache, lanes, files, webhooks, forms, chat (and its streaming), the AI agent, rebuilt nodes (Switch, Edit fields, spreadsheets, images, Python) | the engine and the dev server |
npm run test:billing (more) | Add-ons (quote lines, a prorated change, raised limits), packs (balances, use past the limit, soonest first), trial traces, and PayPal's order, capture and revise against a stand-in of its API | MongoDB |
npm run test:knowledge | Chunking, search, filters, replace, remove: in mooflair, in a real Qdrant and in Pinecone Local (embeddings from a stand-in) | MongoDB, Qdrant (docker run -p 6333:6333 qdrant/qdrant), Pinecone Local (docker run -p 5080-5090:5080-5090 -e PORT=5080 ghcr.io/pinecone-io/pinecone-local; index zz-index, 64 dimensions) |
npm run test:poll | Every polling trigger through a real poll pass (first check only learns, new records start one run, nothing new starts nothing), IMAP with an attachment, and the Telegram trigger (webhook set on publish, signed updates, removed) | the engine, the dev server, GreenMail (docker run -p 3025:3025 -p 3143:3143 -e GREENMAIL_OPTS="-Dgreenmail.setup.test.all -Dgreenmail.hostname=0.0.0.0 -Dgreenmail.auth.disabled" greenmail/standalone) |
npm run test:secret | External secrets resolved for real: Vault KV v2, AWS Secrets Manager (LocalStack), Google (a stand-in checking the signed JWT); the plan gate | Vault dev (docker run -p 8200:8200 -e VAULT_DEV_ROOT_TOKEN_ID=zz-root hashicorp/vault), LocalStack (docker run -p 4566:4566 -e SERVICES=secretsmanager localstack/localstack) |
npm run test:oauth | OAuth2 Connect over HTTP: start (state, PKCE), a local OAuth server, the callback's token exchange, using and refreshing the token, a refusal, a forged state |
Each prints ✅/❌ per case and exits non-zero on a failure.
Backups
There are none: neither Compose project has a backup service. Losing a server's disk loses what it holds. mooflair: its database and library/ (configuration.json with its secrets, the Drive's records, exports). Engine: browser profiles (accounts sign in again), its Drive file bytes and its run history. Use your host's volume snapshots if you want a safety net.
Secrets
configuration.json holds provider keys sealed or plain; ENCRYPTION_KEY_* decrypt what's sealed (datasets, 2FA secrets) and JWT_SECRET signs sessions. Keep a copy of .env in a password manager or offline: without the encryption keys, a restored database can't open its secrets.
PSE registration
A private electronic system operator (PSE Lingkup Privat) serving users in Indonesia registers through OSS (oss.go.id) and the Kominfo/Komdigi PSE portal. Add the registration number in Admin › Configuration › Site › Business details: it's shown in the footer and on the Terms and Privacy pages.
PayPal checklist
In the sandbox, with webhooks pointed at /api/billing/webhook/paypal:
- Checkout a plan (approve, return, invoice issued and paid).
- Renewal (sandbox time or a short plan): a second invoice.
- A failed payment: past due, the dunning emails, then unpaid.
- Suspend and reactivate at PayPal: the status follows.
- Change plan (revise, prorated charge) and seats.
- Cancel at period end and now.
- Refund from Admin › Billing › Customers.
- A dispute opened and closed.
Then switch billing.gateway.paypal.environment to live with live credentials.