Production Checklist
Before you publish, run through this list. Each item is what separates a demo from a bot people actually rely on.
Handle empty states
Every list view should say something useful when there's nothing to show.
when util.len(user.tasks) == 0 {
send.text("📭 No tasks yet — add one with /add!")
} else {
foreach t in user.tasks { send.text(`• ${t.title}`) }
}
Handle ask timeout and cancel
Every blocking ask.* should route "timeout" (and ideally "error"):
branch ask.text("Your answer?", timeout: "120s") {
"timeout" { send.text("No response — say /start to retry.") stop }
default { /* use answer */ }
}
Provide /help and navigation
- Every bot has a
/helpflow explaining its commands. - Multi-step menus offer Back and Home buttons so users never get stuck.
flow help on command "/help" {
send.text("/start — menu\n/play — start a round\n/help — this message")
}
Keep secrets in global.*
API keys, IPN secrets, admin ids — all in global.*, set in the Variables panel. Never hardcoded.
global api_key: String = ""
global admin_id: Number = 0
Retry external calls
Wrap mission-critical HTTP in on error, or use the http.retry recipe for flaky upstreams.
res = http.get(url: flow.url) on error { send.text("Service unavailable.") stop }
Paginate long lists
Anything over ~10 items should page. Use the flow.paginate recipe or manual prefix-callback paging — see Pagination & limits.
Guard admin actions
Restrict destructive or privileged commands with guard.adminOnly() (or an explicit when user.id != global.admin_id check).
flow reset on command "/reset" {
guard.adminOnly() {
body { set { shared.scores = [] } send.text("Reset done.") }
onDeny { send.text("🚫 Admins only.") }
}
}
Rate-limit abusable actions
Throttle claims, submissions, and anything spammy with flow.rateLimit or a cooldown ledger. See Security & validation.
Make webhooks idempotent
Payment and callback webhooks can fire more than once. Deduplicate before crediting:
set {
shared.paid = array.reject(shared.paid, "uid", flow.uid)
shared.paid << [{ uid: flow.uid }]
}
And always verify, then re-check against the source API before delivering goods. See Payments & webhooks.
Use the right scope
user.*for per-user state.shared.*for anything cross-user (and anything a schedule/webhook must read).flow.*only for scratch within a single run — never long-lived state.
Final pass
| Check | Done? |
|---|---|
| Empty states handled | ☐ |
ask.* timeouts handled | ☐ |
/help flow exists | ☐ |
Secrets in global.* only | ☐ |
HTTP calls have on error / retry | ☐ |
| Long lists paginate | ☐ |
| Admin commands guarded | ☐ |
| Webhooks verified + idempotent | ☐ |
| Every callback button has a handler | ☐ |
| No editor errors before publish | ☐ |