Telegram Bot Conversation Designer
Designs complete Telegram bot conversations: onboarding, scenario flows built on inline keyboards, real message copy, error recovery paths, and a command menu.
Example run
Generated by Claude Opus 5.5 on September 23, 2026
Inputs used
- Bot purpose
- Help members and walk-ins of Chalkline Bouldering Gym book classes, buy day passes, and check live gym capacity — all without calling or visiting the front desk
- Target audience
- Boulder, Colorado climbers aged 18–40: roughly 1,200 existing monthly members plus prospective walk-ins who found the gym via Instagram; most already use Telegram for local climbing group chats
- Key user scenarios
- 1. Book a class: user wants to reserve a spot in an upcoming class (e.g., "Intro to Bouldering," "Women's Climbing Night," "Youth Team Tryout"). Success = user holds a confirmed spot, receives a reminder 2 hours before, and the gym's Mindbody schedule reflects the booking so front-desk staff sees it too. 2. Buy a day pass: a non-member or visiting climber wants single-day gym access. Success = user pays via Stripe inside Telegram, receives a QR code / pass code to show at the front desk, and the transaction is logged for accounting. 3. Check live capacity: a member wants to know if the gym is crowded right now before driving over. Success = bot returns current headcount vs. capacity (e.g., "62/120 climbers") pulled from the gym's door-counter API, updated within the last 10 minutes. 4. Cancel or reschedule a class booking: user changes plans and needs to cancel or move to another time slot. Success = the original slot is released back to the class roster, user is rebooked or refunded per the gym's 4-hour cancellation policy, and staff sees the updated roster. 5. Membership renewal reminder: 5 days before a member's monthly membership expires, the bot proactively reaches out to offer one-tap renewal via Stripe. Success = renewal completes without the member needing to visit the desk, or the member explicitly snoozes/declines. 6. Gear rental add-on: while booking a class or day pass, a user without their own shoes/chalk bag wants to add rental gear to their visit. Success = rental item is added to the same transaction and flagged for the front desk to have ready.
- Bot personality
- Upbeat, encouraging climbing buddy — casual, uses light chalk/climbing puns sparingly, never sounds corporate or stiff, keeps messages short and high-energy
- Constraints
- Must read/write bookings through the gym's existing Mindbody API; payments via Stripe with no card numbers stored in Telegram; live capacity depends on a door-counter API with ~10-minute refresh lag; all messages must fit Telegram's 4096-character limit; proactive messages (renewal reminders, capacity alerts) limited to opt-in users with a clear /optout command per GDPR
Chalkline Bouldering Gym: Telegram Bot Conversation Design
Prices, times, phone numbers and names in the copy are sample values. They show the tone and the length limits. At runtime, pull real values from Mindbody or config. Everything else is final copy.
0. What each scenario must achieve
| # | Scenario | What the user must do | User success | Business success | Systems it depends on |
|---|---|---|---|---|---|
| 1 | Book a class | Pick a class and a session, pay or use membership | Confirmed spot, reminder 2 h before | Booking in the Mindbody roster, visible to staff | Mindbody classes/clients, Stripe |
| 2 | Day pass | Pick a date, identify themselves, pay | QR or pass code on their phone | Stripe charge logged, sale recorded for accounting, waiver on file | Stripe, Mindbody sales, waiver system |
| 3 | Live capacity | One tap | "62/120, updated 4 min ago" | Fewer crowded peak hours, fewer desk calls | Door-counter API |
| 4 | Cancel / reschedule | Pick a booking, choose cancel or move | Spot released; refund or rebooking follows the 4-hour rule | Roster accurate, spot resold, policy applied the same way every time | Mindbody, Stripe refunds |
| 5 | Renewal reminder | One tap: renew, snooze or decline | Membership renewed without visiting the desk | Less churn, fewer desk transactions, explicit consent logged | Mindbody contracts, Stripe saved payment methods |
| 6 | Gear rental | Choose items and a size during checkout | Gear ready at the desk | Rental revenue on the same transaction, desk prep list | Mindbody products, possibly inventory |
1. Global design rules
- One idea per message, at most 4 lines. A long sequence edits a single message in place (
editMessageText) so the chat doesn't fill up. Confirmations, receipts and QR codes are sent as new messages so they stay in the history. - Buttons before typing. The user types in only four places: name, email, date of birth (new profiles only), and optionally a custom capacity threshold.
- Every step in a flow has a bottom row:
[ "← Back" ] [ "✖ Cancel" ]. They are left out of the first step and of final confirmations. - Time format: always Mountain Time, e.g. "Fri, Sep 25 · 6:00 PM".
- Lists show at most 5 items, then
[ "More times ›" ]. - Voice: casual, short, encouraging.
- At most one climbing pun per flow.
- No puns in errors, money messages or policy messages.
- Emoji only as signposts, at most one per message.
- Callback data carries a flow ID and a timestamp. Taps on buttons older than 15 minutes, or from an abandoned flow, get the stale-button handling described in section 9.
2. Account linking (shared step, triggered only when needed)
Checking capacity needs no account. Booking, day passes, bookings management and membership do. The bot asks for identity the first time one of those is needed, then returns the user to where they were.
Flow
- The bot asks the user to share their phone number (reply keyboard with
request_contact). - The bot checks that
contact.user_id == from.id. This proves the number belongs to the person tapping. - The bot looks up the phone number in Mindbody.
- One match: link the accounts and go to step 4.
- Several matches: send a 6-digit code to the email on file, then the user enters the code.
- No match: create a new profile (name, email, date of birth), then send the waiver.
- Save the mapping between Telegram ID and Mindbody client ID, then resume the original flow.
Copy
L1: Ask
Quick thing first: I need to match you to your Chalkline account. Tap below to share your number. It's only used to find your profile.
Reply keyboard: [ "📱 Share my phone number" ] [ "✖ Cancel" ]
L2a: Match found
Found you, Maya! 👋 You're linked.
(The original flow continues immediately.)
L2b: Several matches
A few profiles share that number. I just emailed a 6-digit code to m•••@gmail.com. Type it here.
Buttons: [ "Resend code" ] [ "That's not my email" ]
-
Wrong code:
That code doesn't match. Double-check and try again?
-
"That's not my email":
No worries, the front desk can sort that out in a minute: (303) 555-0142.
Buttons:
[ "Main menu" ]
L2c: No profile yet
Looks like you're new here, welcome! 🎉 Let's set you up in three quick steps. What's your full name?
-
Next:
Nice to meet you, Jordan. What email should receipts go to?
-
Invalid email:
Hmm, that doesn't look like an email. Try again?
-
Next:
Last one: your date of birth (MM/DD/YYYY)? We need it for the waiver.
-
Under 18:
Thanks! Climbers under 18 need a parent or guardian to sign up, so ask them to message me or stop by the desk.
Buttons:
[ "Main menu" ]
L3: Waiver
Every climber needs a signed waiver before touching the wall. It takes about 2 minutes.
Buttons: [ "✍️ Sign the waiver" ] (opens a URL or Web App) [ "✖ Cancel" ]
-
After signing (callback or webhook from the waiver system):
Waiver done ✅ You're all set.
-
Waiver not detected after the user returns:
I don't see the signed waiver yet. It can take a minute to sync.
Buttons:
[ "🔄 Check again" ][ "Open waiver" ]
3. Onboarding: /start
Flow
/startfor a new Telegram user: show a welcome with 4 actions. Capacity works with one tap and no account.- After the user's first completed action (not before), ask once for notification consent. This avoids front-loading.
- A returning user gets a short greeting and the main menu.
Copy
O1: New user
Hey, welcome to Chalkline! 🧗 I can book classes, sell you a day pass, and tell you how packed the gym is right now. No calls, no desk line.
Where to first?
Buttons:
[ "📊 How busy is it now?" ]
[ "📅 Book a class" ] [ "🎟 Buy a day pass" ]
[ "🙋 I'm already a member" ]
-
"I'm already a member": go to L1, then:
You're linked! Here's everything I can do for you:
Then the main menu.
O2: Notification consent (sent once, after the first completed action)
Want a heads-up 2 hours before your classes, and when your membership is about to run out? You can change this anytime in /settings or stop everything with /optout.
Buttons:
[ "✅ Yes, keep me posted" ]
[ "Class reminders only" ]
[ "No thanks" ]
- Yes:
Done. I'll only ping you when it matters. 🙌
- Class reminders only:
Got it: class reminders on, membership reminders off.
- No thanks:
All good. I'll stay quiet unless you message me.
Store the consent timestamp, the scope of the consent, and the copy version.
O3: Returning user
Welcome back, Maya! What's the plan?
Main menu (used throughout):
[ "📊 Gym capacity" ] [ "📅 Book a class" ]
[ "🎟 Day pass" ] [ "🗓 My bookings" ]
[ "💳 Membership" ] [ "⚙️ Settings" ]
4. Scenario 1: Book a class
Flow outline
- Entry:
/book, the menu button, or a deep link (t.me/ChalklineBot?start=class_intro). - Pick a class type. The list comes from Mindbody class descriptions that have sessions in the next 14 days. The user can also browse by date.
- Pick a session. Show up to 5 upcoming sessions with spots left.
- Full session: offer the waitlist (step 3a).
- Account check. If the user isn't linked, run section 2, then return here.
- Youth Team Tryout only: ask "Who's climbing?" and list the user's dependents from Mindbody.
- Class card. Show time, instructor, length and price, or "included with your membership".
- Gear question (section 9, Scenario 6). Skip it if the user saved "I have my own gear" earlier. They can still add gear from the summary.
- Payment branch:
- 8a. Included in membership, or class credit available: one-tap confirm, then book in Mindbody.
- 8b. Paid: send a Telegram invoice (Stripe provider) with line items for the class and any rentals.
- On
pre_checkout_query, check availability again. If the spot is gone, reject with a message. - On
successful_payment, book in Mindbody. - If the Mindbody booking fails, refund automatically and alert staff.
- On
- Confirmation as a new message. Schedule the reminder for T−2h if consent allows.
- Reminder at T−2h.
Copy
S1.1: Pick a class
Let's get you on the wall. What are you in the mood for?
Buttons (one per row, from Mindbody):
[ "Intro to Bouldering" ]
[ "Women's Climbing Night" ]
[ "Technique Clinic" ]
[ "Youth Team Tryout" ]
[ "📆 Browse by date instead" ]
S1.1b: Browse by date
Which day works?
Buttons: [ "Today" ] [ "Tomorrow" ] then [ "Fri 9/25" ] [ "Sat 9/26" ] [ "Sun 9/27" ] then [ "← Back" ] [ "✖ Cancel" ]
The next screen lists all classes that day, e.g. [ "6:00 PM · Intro to Bouldering · 4 left" ].
S1.2: Pick a time
Intro to Bouldering: pick a time 👇
Buttons:
[ "Thu 9/24 · 6:00 PM · 4 spots" ]
[ "Fri 9/25 · 6:00 PM · 1 spot" ]
[ "Sat 9/26 · 10:00 AM · Full, join waitlist" ]
[ "More times ›" ]
[ "← Back" ] [ "✖ Cancel" ]
-
No sessions in the next 14 days:
No Intro to Bouldering sessions on the calendar for the next two weeks. New dates usually drop on Mondays.
Buttons:
[ "See other classes" ] [ "Main menu" ]
S1.3a: Waitlist
Saturday 10 AM is full, but spots open up a lot. Want me to put you on the waitlist?
Buttons: [ "Join waitlist" ] [ "Pick another time" ]
-
Joined:
You're #3 on the waitlist. If a spot opens, I'll message you right away. It's first come, first served.
Buttons:
[ "Main menu" ]
S1.4: Who's climbing? (Youth Team Tryout only)
Youth Team Tryout is for climbers 10–17. Who are you signing up?
Buttons (dependents from Mindbody): [ "Sam (14)" ] [ "Riley (11)" ] then [ "Add another kid" ] then [ "← Back" ] [ "✖ Cancel" ]
-
"Add another kid" (v1 hands off):
Adding a new kid needs a guardian waiver, which the desk handles in about 2 minutes. Stop by or call (303) 555-0142, then come back and book here.
Buttons:
[ "Main menu" ] -
User has no dependents and is 18 or older:
This one's for climbers 10–17. If you're booking for your kid, the desk can add them to your account first: (303) 555-0142.
Buttons:
[ "See adult classes" ] [ "Main menu" ]
S1.5: Class card
Intro to Bouldering Thu, Sep 24 · 6:00–7:30 PM · with Priya Falling safely, footwork, and your first V0s. No experience needed. $35 · shoes not included
For members with the class included, the last line reads: "Included with your membership 🙌"
Buttons: [ "Book this spot" ] then [ "← Back" ] [ "✖ Cancel" ]
S1.6: Gear question. See section 9 (Scenario 6).
S1.7a: Confirm (included or credit)
Booking Intro to Bouldering, Thu 9/24 at 6:00 PM, using your membership. Sound good?
Buttons: [ "✅ Confirm" ] then [ "← Back" ] [ "✖ Cancel" ]
S1.7b: Paid. First send a lead-in message:
Almost there, just the payment. Tap Pay below. Your card details go straight to Stripe, never to me.
Then send the Telegram invoice:
- Title: Intro to Bouldering
- Description: Thu, Sep 24 · 6:00 PM · Chalkline Boulder
- Line items: "Class — $35.00", "Shoe rental (US M 9) — $6.00"
- Pay button: Telegram's native "Pay $41.00"
Also send [ "✖ Cancel" ] under the lead-in message. Tapping it invalidates the invoice payload.
S1.8: Confirmation (new message)
You're in! 🙌 Intro to Bouldering · Thu, Sep 24 · 6:00 PM Shoes (US M 9) will be waiting at the desk. Free cancellation until 2:00 PM that day.
Buttons: [ "📅 Add to calendar" ] (URL to an .ics file) [ "🗓 My bookings" ] then [ "Main menu" ]
Add one more line depending on consent:
- Opted in: "I'll ping you 2 hours before."
- Not opted in: "Want a reminder 2h before?" with
[ "Yes, remind me" ]. Tapping it records consent scoped to class reminders.
S1.9: Reminder at T−2h (proactive; consent required)
Chalk up! 🧗 Intro to Bouldering starts at 6:00 PM, 2 hours from now. Right now the gym's at 48/120.
Buttons: [ "Get directions" ] (Maps URL) [ "Can't make it" ]
- "Can't make it" goes to S4.3 with this booking preselected. At T−2h the booking is inside the late window, so the late-cancel copy applies.
S1.10: Waitlist spot opened (proactive, transactional, tied to the user's own request)
Good news, a spot opened in Intro to Bouldering, Sat 9/26 at 10:00 AM! It's first come, first served.
Buttons: [ "Grab it" ] [ "Leave the waitlist" ]
- Already taken when tapped:
Argh, someone beat you to it. You're still on the waitlist.
5. Scenario 2: Buy a day pass
Flow outline
- Entry:
/daypass, the menu, O1, or the capacity result. - Pick a date: Today, Tomorrow, or another day within 7 days. For today, show live capacity inline.
- Pick a pass type. Prices come from Mindbody pricing options.
- Account and waiver check (section 2). The waiver is mandatory; there is no path to paying without one.
- Gear question (section 9).
- Invoice. The pre-checkout step checks the waiver and that the gym is open that day.
- On success:
- Create the pass code.
- Record the sale in Mindbody or accounting.
- Send the QR as a photo with the code as text.
- Offer to share the bot with a friend. Each friend buys their own pass because each needs their own waiver.
Copy
S2.1: Pick a day
Day pass it is! When are you coming in? (Right now: 🟢 62/120 climbers)
Buttons: [ "Today" ] [ "Tomorrow" ] then [ "Another day" ] then [ "✖ Cancel" ]
-
Another day: show 7 date buttons, e.g.
[ "Fri 9/25" ] … -
Today, after closing time:
We've closed for tonight. Doors open at 6:00 AM tomorrow. Want a pass for tomorrow instead?
Buttons:
[ "Yes, tomorrow" ] [ "Pick another day" ] -
Today, within 1 hour of closing:
Heads up: we close at 10:00 PM, so you'd get under an hour. Still want today?
Buttons:
[ "Yep, today" ] [ "Make it tomorrow" ]
S2.2: Pass type
Which pass fits?
Buttons:
[ "Adult · $26" ]
[ "Student / Military · $21" ]
[ "Climbed here before? Try a 10-punch ›" ] (URL or product flow; optional)
[ "← Back" ] [ "✖ Cancel" ]
-
Student / Military:
Nice. Just bring a valid ID, and the desk will check it at check-in.
(Continues automatically.)
S2.3: Account and waiver. Run section 2. For a new person, L2c and then L3 are mandatory.
S2.4: Gear question. See section 9.
S2.5: Invoice lead-in
Here's your total. Tap Pay and you're good to go. Card details go straight to Stripe.
Invoice:
- Title: Day Pass, Adult
- Description: Valid Wed, Sep 23 · Chalkline Boulder
- Line items: "Day pass — $26.00", "Chalk bag rental — $3.00"
S2.6: Pass delivered. Send a photo (the QR) with this caption:
Your pass is ready! 🎟 Wed, Sep 23 · Adult day pass · code CHK-7F3Q-92 Show this QR at the desk. Your chalk bag will be ready there. Go send something!
Buttons: [ "📊 How busy is it?" ] [ "Main menu" ]
Then send one more message:
Climbing with a friend? Send them this bot. They'll need their own pass and waiver.
Buttons: [ "Share Chalkline bot" ] (a switch_inline_query or share-URL button)
S2.7: "Where's my pass?" (from the /mybookings Passes section)
Here's your pass for today 👇
Resend the QR photo.
6. Scenario 3: Check live capacity
Flow outline
- Entry:
/capacityor the menu. No account needed. - Check freshness (
now − last_reading_at):- ≤ 12 min: normal result.
- 12–30 min: result plus a staleness warning.
- Over 30 min, or API error: the "counter's offline" message.
- Outside opening hours: the "closed" message.
- Color band:
- 🟢 below 50%: "plenty of wall"
- 🟡 50–80%: "getting busy"
- 🔴 above 80%: "packed"
- Actions: Refresh, set a quiet alert, buy a day pass.
- Refresh is rate-limited to once per 60 s per user.
- If the reading hasn't changed, say so.
- Quiet alert (opt-in):
- Pick a threshold, today only.
- It fires once, then expires.
- It never fires during quiet hours.
Copy
S3.1: Normal (🟢)
🟢 62 / 120 climbers right now ▓▓▓▓▓░░░░░ plenty of wall Updated 4 min ago (4:52 PM). The counter refreshes about every 10 min.
Buttons: [ "🔄 Refresh" ] [ "🔔 Tell me when it's quieter" ] then [ "🎟 Buy a day pass" ] [ "Main menu" ]
S3.1: 🟡 variant
🟡 84 / 120 climbers right now ▓▓▓▓▓▓▓░░░ getting busy Updated 6 min ago (5:40 PM).
S3.1: 🔴 variant
🔴 109 / 120 climbers, it's packed ▓▓▓▓▓▓▓▓▓░ expect lines on the popular problems Updated 3 min ago (6:12 PM).
S3.2: Refresh with no new data
Shown as a toast via answerCallbackQuery:
Still the latest reading. The counter updates about every 10 min.
S3.2b: Refresh tapped again within 60 s Also a toast:
Just checked! Give it a minute.
S3.3: Stale reading (12–30 min)
🟡 84 / 120 climbers as of 5:31 PM Heads up: the door counter hasn't reported in 22 min, so this may be a bit off.
Buttons: same as S3.1.
S3.4: Counter offline
Can't reach the door counter right now 😕 Last reading I trust: 71/120 at 4:10 PM. The desk always knows: (303) 555-0142.
Buttons: [ "🔄 Try again" ] [ "Main menu" ]
If there is no reading at all today, drop the "last reading" sentence.
S3.5: Closed
We're closed right now. Doors open at 6:00 AM (Thu). Early mornings are usually the quietest. 😉
Buttons: [ "🎟 Buy tomorrow's pass" ] [ "Main menu" ]
S3.6: Quiet alert, pick a threshold If the user hasn't consented to alerts, first send:
This means I'd message you later today. Okay to do that?
Buttons: [ "Yes, that's fine" ] [ "Never mind" ]
Then:
I'll ping you once when it drops below…
Buttons: [ "🟢 Under 60 climbers" ] [ "🟡 Under 90 climbers" ] then [ "✖ Cancel" ]
-
Set:
Got it. I'll message you once today when it's under 60. Tap /capacity anytime to check yourself.
-
Alert fires (proactive):
🟢 It's quieter now: 54 / 120 climbers (as of 7:48 PM). Perfect time to head over!
Buttons:
[ "🎟 Buy a day pass" ] [ "Refresh" ] -
Expired, never triggered: send nothing. Silence is correct here.
-
Threshold already met when set:
Good news: it's already at 54, under your 60. No need to wait!
7. Scenario 4: Cancel or reschedule a booking
Flow outline
- Entry:
/mybookings, the menu, or the reminder's "Can't make it" button. - List upcoming bookings (max 5 plus More), then day passes.
- Booking detail: Reschedule / Cancel / Add gear / Back.
- Policy check:
hours_until_start ≥ 4means the free window; otherwise the late window. - Cancel:
- Free window: confirm, then remove from the class in Mindbody (not a late cancel). Then refund through Stripe for a card payment (class plus rentals), or return the credit for a member.
- Late window: warn, then "Cancel anyway" does a Mindbody late cancel with no refund. The spot is still released.
- Reschedule:
- Free window: pick a new session of the same class.
- Book the new spot first, then remove the old one. This ensures the user never ends up with neither.
- If the new session is full, offer its waitlist and keep the original booking.
- If the prices differ, charge or refund the difference. (The gym's policy on this is an open question.)
- Late window: a free move isn't allowed (open question for the gym). Offer "Keep" or "Late-cancel + book a new one at full price".
- Free window: pick a new session of the same class.
- Confirmation covering the refund or credit and what happens next. Cancel the old reminder and schedule a new one.
Copy
S4.1: My bookings
Here's what's coming up 👇
Buttons:
[ "Thu 9/24 · 6 PM · Intro to Bouldering" ]
[ "Tue 9/29 · 7 PM · Women's Climbing Night" ]
[ "🎟 Day pass · Wed 9/23" ]
[ "Main menu" ]
-
Empty:
Nothing booked yet. Let's fix that?
Buttons:
[ "📅 Book a class" ] [ "🎟 Day pass" ]
S4.2: Booking detail
Intro to Bouldering · Thu, Sep 24 · 6:00 PM Paid $41 (class + shoes) Free cancellation until 2:00 PM Thu.
Buttons: [ "🔁 Reschedule" ] [ "❌ Cancel booking" ] then [ "👟 Add rental gear" ] then [ "← Back" ]
(In the late window, the third line reads: "The free-cancel window closed at 2:00 PM.")
S4.3a: Cancel, free window
Cancel Intro to Bouldering on Thu 9/24? You'll get $41 back to your card (usually 5–10 business days).
For a member, the second sentence reads: "Your class credit goes right back to your account."
Buttons: [ "Yes, cancel it" ] [ "Keep my spot" ]
-
Done:
Cancelled ✅ Your $41 refund is on its way, and your spot's back up for grabs. See you on the wall another time!
Buttons:
[ "📅 Book another class" ] [ "Main menu" ]
S4.3b: Cancel, late window
Heads up: class starts in under 4 hours, so this counts as a late cancel. No refund this time (for members, the class credit is used), but your spot goes to someone on the waitlist.
Buttons: [ "Cancel anyway" ] [ "Keep my spot" ]
-
Done:
Cancelled. Thanks for freeing up the spot. 🙏 Rental gear fees are refunded since the gear wasn't used.
(Include the gear sentence only if gear refunds apply; see open questions.)
S4.4: Reschedule, pick a new time
No problem, pick a new Intro to Bouldering time:
Buttons: session list as in S1.2, excluding the current slot, then [ "← Back" ] [ "✖ Cancel" ]
S4.4b: Confirm the move
Move from Thu 9/24 6 PM to Fri 9/25 6 PM? Same price, nothing to pay.
For a price difference, the second sentence reads either "That one's $5 more, and I'll send you an invoice." or "That one's $5 less, and I'll refund the difference."
Buttons: [ "✅ Move it" ] [ "← Pick another time" ]
-
Done:
Moved! ✅ You're in Intro to Bouldering, Fri 9/25 at 6:00 PM. Your old spot's released. Free cancellation until 2:00 PM Fri.
Buttons:
[ "📅 Add to calendar" ] [ "Main menu" ] -
New slot filled during the move:
That time just filled up. You're still booked for Thu 9/24, nothing changed.
Buttons:
[ "Pick another time" ] [ "Join that waitlist" ] [ "Keep Thursday" ]
S4.4c: Reschedule, late window
It's under 4 hours to class, so I can't move it for free. You can keep your spot, or late-cancel and book a new time (full price).
Buttons: [ "Keep my spot" ] [ "Late-cancel & rebook" ]
S4.5: Class cancelled by the gym (proactive, transactional)
Sorry! Women's Climbing Night on Tue 9/29 was cancelled (instructor out sick). Your $25 is being refunded automatically. Want to grab another night?
Buttons: [ "See other dates" ] [ "Main menu" ]
S4.6: Add gear later (from S4.2) Runs section 9 as its own small invoice. Allowed until class start.
8. Scenario 5: Membership renewal reminder
Flow outline
- Nightly job finds members who meet all of these:
- Their monthly membership ends in 5 days.
- Auto-renew is off.
- They are opted in to membership reminders.
- They haven't snoozed or declined this cycle.
- Send at 10:00 AM MT (never during quiet hours, 9 PM–8 AM).
- Branches:
- Renew with a saved card: confirm the card's last 4 digits, charge off-session through Stripe, then record the renewal in Mindbody.
- If 3-D Secure is needed: send a Stripe-hosted confirmation link.
- Renew without a saved card: send a Telegram invoice.
- Snooze: send one more reminder at T−2 days. No snooze is offered after that.
- Not this month: acknowledge, offer an optional one-tap reason, then stay silent for the rest of the cycle.
- Stop these reminders: turn off the membership reminder scope only.
- Renew with a saved card: confirm the card's last 4 digits, charge off-session through Stripe, then record the renewal in Mindbody.
- No further nudges after expiry. At most 2 renewal messages per cycle, total.
- Manual path:
/membershipshows status and a Renew button at any time.
Copy
S5.1: Reminder at T−5
Hey Maya! Your Chalkline membership runs out Mon, Sep 28. Want to renew for another month ($79)? One tap and you're set. 💪
Buttons: [ "✅ Renew · $79" ] then [ "⏰ Remind me in 3 days" ] [ "Not this month" ] then [ "🔕 Stop these reminders" ]
S5.2a: Confirm the saved card
Charge $79 to Visa •• 4242 for Sep 28–Oct 27?
Buttons: [ "Confirm" ] [ "Use a different card" ] then [ "✖ Cancel" ]
-
Success:
Renewed ✅ You're good through Oct 27. Receipt sent to m•••@gmail.com.
Buttons:
[ "📅 Book a class" ] [ "Main menu" ] -
"Use a different card": send an invoice with the lead-in:
No problem. Tap Pay below, and Stripe handles the card.
-
Bank needs extra verification (3-D Secure):
Your bank wants a quick confirmation. Tap below to finish securely with Stripe.
Buttons:
[ "Confirm with my bank" ](Stripe URL). When the webhook confirms, send the success message.
S5.2b: Card declined
That charge didn't go through (your bank declined it). Nothing was charged.
Buttons: [ "Try a different card" ] [ "I'll handle it at the desk" ]
- "At the desk":
Cool, the desk can sort it anytime before Sep 28. I'll leave you be.
S5.3: Snooze
Got it, I'll check back Saturday. 👍
S5.3b: Second reminder at T−2
Quick nudge: your membership ends Mon, Sep 28, two days from now. Renew for $79?
Buttons: [ "✅ Renew · $79" ] [ "Not this month" ] then [ "🔕 Stop these reminders" ]
S5.4: Not this month
No worries! Day passes are always here when you want back on the wall. Mind sharing why? Totally optional.
Buttons: [ "Too pricey" ] [ "Taking a break" ] then [ "Moving away" ] [ "Skip" ]
- After any choice:
Thanks, that helps. Take care! 🧗
S5.5: Stop these reminders
Done, no more renewal reminders. Class reminders stay on unless you change them in /settings.
S5.6: /membership screen
Monthly Unlimited · active until Mon, Sep 28 Auto-renew: off
Buttons: [ "Renew now" ] [ "Main menu" ]
For auto-renew members, the second line reads "Auto-renew: on (Visa •• 4242)" and there is no Renew button.
9. Scenario 6: Gear rental add-on
Flow outline
- Offered inside S1 (before payment), S2 (before invoice), or S4.6.
- "Need gear?" The choice "I've got my own" can be remembered to skip this question next time.
- Multi-select of Shoes and Chalk bag. The message is edited in place with ✓ marks.
- If shoes are selected: pick the fit (Women's or Men's US), then the size, or "Size me at the desk".
- If a size is unavailable: suggest the nearest size, or desk fitting. (Depends on inventory; see open questions.)
- Summary, then back to the parent flow. The items are added to the same invoice or the same membership confirmation.
- After payment: create a desk flag (a Mindbody sale note or appointment note plus a daily prep list), and add the gear line to the QR caption or booking confirmation.
Copy
S6.1: Ask
Need rental gear for this one?
Buttons: [ "👟 Rent gear" ] [ "I've got my own" ] then [ "← Back" ]
-
"I've got my own" (first time only):
Nice. Want me to skip this question next time?
Buttons:
[ "Yes, skip it" ] [ "Keep asking" ]The setting can be changed in /settings.
S6.2: Pick items (tapping toggles ✓)
What do you need? Tap to select, then Done.
Buttons: [ "👟 Shoes · $6" ] [ "Chalk bag · $3" ] then [ "Done" ] then [ "← Back" ]
- After a tap, labels change to
[ "✓ 👟 Shoes · $6" ]. - "Done" with nothing selected: a toast saying "Pick something, or tap Back if you've got your own."
- Members with free rentals: prices show as "free with your membership".
S6.3: Shoe fit
Which fit?
Buttons: [ "Women's US" ] [ "Men's US" ] then [ "Not sure, size me at the desk" ]
S6.4: Shoe size
What size? (Most people go their street size or half a size down.)
Buttons, 4 per row:
[ "7" ] [ "7.5" ] [ "8" ] [ "8.5" ][ "9" ] [ "9.5" ] [ "10" ] [ "10.5" ][ "11" ] [ "11.5" ] [ "12" ] [ "13" ][ "← Back" ]
Show Women's 5–11 or Men's 7–13, depending on the fit chosen.
-
Size unavailable:
We're out of Men's 9s for that time 😕 Want a 9.5, or let the desk fit you in something close?
Buttons:
[ "9.5 works" ] [ "Size me at the desk" ]
S6.5: Summary
Added: Shoes (US M 9) + chalk bag · $9. It'll be ready at the desk when you arrive.
Buttons: [ "Continue" ] [ "Change gear" ]
10. Unhappy paths (global)
| Situation | Handling | Copy |
|---|---|---|
| Unrecognized free text, no match | Try a keyword match first: busy/crowd/packed → S3; pass → S2; book/class → S1; cancel/move → S4; renew/membership → S5. Otherwise send the fallback. | "Ha, I'm better with buttons than small talk 😅 Here's what I can do:" + main menu |
| Unrecognized text twice in a row | Offer a human. | "Stuck? The desk is in 6 AM–10 PM: (303) 555-0142. Or tap /help." Buttons: [ "❓ Help" ] [ "Main menu" ] |
| Text typed when buttons were expected | Re-show the step without resetting the flow. | "Tap one of the buttons above 👆, or /cancel to start over." |
/cancel or "✖ Cancel" mid-flow |
Clear the state and invalidate any open invoice. | "Scrapped it, nothing was booked or charged. What next?" + main menu |
| New command mid-flow | The new flow wins; the old one expires quietly. | (none) |
| "← Back" | Return to the previous step with selections kept. | (none) |
| Stale or expired button | answerCallbackQuery alert, then send a fresh screen. |
Toast: "That menu's out of date, here's a fresh one." |
| 15 min of inactivity, then a tap | Restart the flow at step 1. | "That got a bit stale, so I cleared it. Let's start fresh:" |
| Mindbody unreachable | Retry once with backoff, then show this message. Capacity still works. | "Our booking system isn't answering right now 😕 Nothing's been booked or charged. Try again in a few minutes?" Buttons: [ "🔄 Try again" ] [ "📊 Check capacity" ] [ "Main menu" ] |
| Stripe or invoice creation fails | "Payments are having a moment, so I couldn't start checkout. Nothing was charged." Buttons: [ "🔄 Try again" ] [ "Main menu" ] |
|
| Spot gone at pre-checkout | Answer pre_checkout_query with ok=false and the error text below, then send a follow-up. |
Error: "That spot just got taken, so you weren't charged." Follow-up: "Want another time?" Buttons: [ "Pick another time" ] [ "Join waitlist" ] |
| Paid, but the Mindbody write failed | Auto-refund, alert staff, log the incident. | "Your payment went through but the booking didn't save. That's on us. I've refunded $41 (5–10 business days). Want to try again?" Buttons: [ "Try again" ] [ "Main menu" ] |
Duplicate successful_payment, or double tap on Confirm |
Idempotency key per flow; ignore repeats. | (none) |
| Waiver missing at checkout | Block payment and send L3. | "One thing before you pay: we need your signed waiver." |
| User types a card number | Delete the message immediately if the bot has permission, never log it, and warn. | "Whoa, please don't send card numbers here! I deleted it. Payments go through the secure Pay button." |
| Bot blocked by the user | Mark unreachable and stop scheduled sends. Telegram returns a 403. | (none) |
11. Command menu and proactive messaging
Command summary
| Command | What it does | Needs a linked account? |
|---|---|---|
/start |
Welcome (new users) or main menu (returning users) | No |
/capacity |
Live headcount vs. capacity, with freshness | No |
/book |
Book a class (S1) | Yes (prompted inline) |
/daypass |
Buy a day pass (S2) | Yes, plus waiver |
/mybookings |
Upcoming classes and passes; cancel, reschedule, add gear (S4) | Yes |
/membership |
Status, renew, auto-renew info (S5) | Yes |
/settings |
Notification toggles, gear preference, unlink, delete data | No |
/help |
What the bot does, the 4-hour policy, desk contact | No |
/optout |
Instantly stops all proactive messages | No |
/cancel |
Abandon the current flow | No |
Set the commands with setMyCommands. Show only the first 7 in the menu button; /optout and /cancel still work, and /optout is shown in every proactive message.
/help copy
Here's what I can do: 🧗 📊 /capacity · 📅 /book · 🎟 /daypass · 🗓 /mybookings · 💳 /membership Classes: free cancellation up to 4 hours before start. Need a human? Desk: (303) 555-0142, 6 AM–10 PM.
Buttons: [ "Main menu" ] [ "⚙️ Settings" ]
/settings copy
Your settings: tap to toggle.
Buttons (labels show the current state):
[ "🔔 Class reminders: ON" ][ "💳 Renewal reminders: ON" ][ "📊 Capacity alerts: allowed" ][ "👟 Ask about gear: ON" ][ "🔗 Unlink my account" ] [ "🗑 Delete my data" ][ "Main menu" ]
Delete my data:
This removes your Telegram link and all notification settings here. Your Chalkline account, bookings and payment history stay with the gym. Sure?
Buttons: [ "Yes, delete" ] [ "Never mind" ]
/optout copy
Done. No more reminders or alerts from me, class reminders included. 🔕 You'll still get receipts for anything you buy. Changed your mind? /settings.
Every proactive message ends with the small footer line "Stop these: /optout".
Proactive message policy
| Message | Trigger | Consent scope | Frequency cap |
|---|---|---|---|
| Class reminder (S1.9) | T−2h before a booked class | Class reminders (asked at O2 or S1.8) | 1 per booking |
| Waitlist spot open (S1.10) | Mindbody waitlist opening | Implied by joining the waitlist; still suppressed by /optout | 1 per opening |
| Class cancelled by gym (S4.5) | Mindbody cancellation | Transactional; always sent (it concerns their money) | Per event |
| Renewal reminder (S5) | T−5 days, snooze to T−2 | Renewal reminders | 2 per billing cycle, maximum |
| Capacity alert (S3.6) | Threshold crossed | Explicit, per request | 1 per request, today only |
Rules:
- Quiet hours are 9 PM–8 AM MT. Class reminders for early classes are sent at 8:00 AM, or the night before at 8:45 PM if the class starts before 10 AM.
- No marketing broadcasts in v1.
12. Open questions for the developer
- Mindbody identity. Is phone matching reliable enough, or do many members share or lack numbers? Can we send an email one-time code through Mindbody or our own mailer?
- Waivers. Where do waivers live (Mindbody, Waiver Sign, Smartwaiver)? Is there a webhook or API to check signed status per client? Minors need guardian waivers.
- Telegram Payments policy. Confirm that gym access and classes count as physical services, which Telegram allows through third-party providers like Stripe. Digital goods would require Telegram Stars. Get legal or compliance sign-off.
- Mindbody and Stripe reconciliation. Does Chalkline process payments in Mindbody? If so, how do Stripe sales get recorded there (custom payment method,
CheckoutShoppingCartwith an external tender)? Which ledger is the source of truth for accounting? - Saved cards for one-tap renewal. Do members already have Stripe customer records with saved payment methods and off-session consent? Or does "one tap" mean one invoice?
- Auto-renew members. Should S5 skip them entirely, or send a courtesy "you'll be charged" notice?
- Cancellation and reschedule policy details:
- Is a reschedule inside 4 hours allowed?
- Are refunds sent to the card or issued as account credit?
- Are gear fees refunded on a late cancel?
- How are price differences handled?
- Is a day pass refundable if unused?
- Waitlist behavior. Does Mindbody auto-promote from the waitlist (which would book and charge automatically) or notify only? The S1.10 copy assumes "first come, first served".
- Youth Team Tryout. Can the bot read dependents through Mindbody relationships and book on their behalf? What are the tryout's own eligibility or fee rules?
- Door-counter API:
- Response format and timestamp field
- Auth, rate limits, uptime
- Whether it goes negative or drifts overnight (reset time)
- Whether historical data exists for a "usually quiet at…" hint (future)
- Capacity number. Is 120 the fire code limit or a comfort target? Who updates it for events or closures?
- Gear inventory. Is there per-size inventory data, or should the bot assume availability and fall back to "desk will fit you"? How does the desk see the prep list (Mindbody sale notes, a staff Telegram group, a printed daily list)?
- QR and pass validation. What does the desk scan with, and does the code map to a Mindbody check-in? Do codes expire at closing time on the valid date? Can a pass be used by someone else (name check at the desk)?
- Hours, prices, pass types, desk phone. Confirm that all of these come from config or Mindbody, not hardcoded copy.
- GDPR data processing. GDPR applies (for EU visitors). Needed: a privacy notice link, a data retention period for Telegram IDs and consent logs, the delete-data flow, and whether the chat with the Telegram ID counts as personal data shared with Mindbody.
- Staff alerts. Where do failures like "paid but not booked" go (email, Slack, staff Telegram group)? Who owns them?
- Class reminders and consent. Legal needs to confirm that booking-tied reminders may default on, or must stay opt-in as designed here.
13. Edge cases to handle before launch
- Race on the last spot. Two users pay for the same final spot. Re-check availability at
pre_checkout_queryand book with an idempotency key. The loser is auto-refunded with the S4 copy. - Timezones and DST. All scheduling runs in America/Denver. Test reminders across the November 1, 2026 DST change. Traveling users still see gym-local times.
- The 4-hour boundary. Compute it server-side at the moment the confirm button is tapped, not when the screen was shown. A user who opens S4.2 at 4h01m and taps at 3h59m gets the late-cancel copy.
- Class cancelled or edited by staff after booking. Poll or webhook Mindbody, re-schedule or cancel reminders, and trigger S4.5.
- Membership changes mid-flow. Membership expires or renews between the class card and the confirm step. Re-check price eligibility at confirm.
- Stale capacity readings. Counter frozen, negative counts, or counts above capacity. Clamp for display, flag readings over capacity to staff, and treat a frozen counter over 30 min old as offline.
- Duplicate webhooks and retries from Telegram and Stripe. Every write is idempotent per flow ID.
- Deleted or edited bot messages.
editMessageTexton a deleted message fails; fall back to sending a new message. - Account already linked to a different Telegram ID (new phone, shared account). Require email one-time-code re-verification and notify the old chat.
- Minors reaching the adult flow. DOB under 18 at L2c, or a non-guardian booking Youth Team Tryout. Block with the handoff copy.
- Refund failures. Card expired or bank rejects the refund. Alert staff and tell the user the desk will reach out; never claim a refund that didn't succeed.
- Message length. Long class descriptions or many bookings must be truncated or paginated to stay well under 4096 characters. Callback data must stay under Telegram's 64-byte limit.
- Group chats. If the bot is added to a climbing group, reply only to
/capacitythere, and redirect everything else to a DM (payments and personal data never go in groups). - Rate limits. Telegram limits broadcast bursts (~30 messages/s). Spread renewal reminders for up to 1,200 members across the send window.
/optoutduring a pending event. A user opts out while a reminder or capacity alert is already queued. Check consent at send time, not at schedule time.- Card numbers or other sensitive data typed into chat. Delete the message, never log it, and warn (section 10).
Related prompts
Telegram Channel Content Plan Builder
Builds a 30-day Telegram channel content plan with pillars, post formats, opening hooks, interactive posts, and CTAs designed around forwards and notifications.
YouTube Video Title & Description Optimizer
Generates click-worthy, keyword-optimized YouTube titles, descriptions, tags, and thumbnail ideas to maximize search reach and CTR.
Instagram Story Engagement Booster
Generates five interactive Instagram Story concepts engineered to maximize taps, replies, and shares.
LinkedIn Professional Post Creator
Writes a polished, engagement-optimized LinkedIn thought-leadership post that builds your professional credibility.