YAML-powered acquisition bot that never sleeps

YAML-powered acquisition bot that never sleeps

The client's growth strategy was Telegram-native — every new user entered through a bot that sent a welcome sequence, explained the community, and nudged them toward the paid tier. The problem was that every time the marketing team wanted to change the funnel — different message order, new images, adjusted delays — they needed a developer to modify code, test it, and redeploy. Funnel iterations that should have taken an hour took a week. For a team running campaigns in the fast-moving Brazilian crypto education space, that lag was killing their ability to experiment.

how we built it

YAML funnel configuration example with branching logic

We rebuilt the entire acquisition bot around a YAML-driven funnel engine. The marketing team defines message sequences in a configuration file: each step specifies the message type (text, photo, video, sticker), the content, the delay before the next step, and optional conditional branches based on user actions. Want to send a different sequence to users who joined from a specific channel? Add a condition. Want to insert a 24-hour reminder for users who didn't respond? Add a delay node. No code changes, no deploys — edit the YAML, reload the config, and the funnel is live. The bot itself runs on aiogram 3's dispatcher architecture, with each funnel step registered as an async handler that resolves the next action from the config at runtime.

Channel membership verification is baked in — the bot checks whether a user has joined required channels before advancing them through the funnel, and sends reminders if they haven't. For re-engagement, APScheduler runs a reminder system that triggers messages to users who stalled at specific funnel stages. The scheduler persists its job state to PostgreSQL, which matters more than it sounds — without persistence, every bot restart would lose track of thousands of scheduled reminders.

edge cases and state

funnel analytics dashboard showing per-step drop-off

What made this deceptively complex was the edge cases. YAML-driven funnels look elegant on paper, but the moment you have real users interacting with them, things get messy. A user responds with a random message mid-sequence — does the funnel advance, reset, or ignore it? The bot restarts during a deploy — which users were mid-funnel and where exactly were they? We ended up building a state machine that tracks each user's position in the funnel and timestamps every transition, stored in PostgreSQL. On restart, the bot reconstructs pending actions from the database state rather than relying on in-memory scheduler jobs alone.

analytics and polish

Funnel analytics turned out to be the feature the client valued most, and we almost didn't build it. The original YAML schema had no concept of step identifiers or tracking hooks — it was designed for content delivery, not measurement. When the client asked "where are users dropping off?", we had to retrofit step IDs, add event logging for every transition, and build a drop-off report that shows conversion rates between each funnel stage. One detail that caused real headaches: Portuguese content with heavy emoji usage occasionally broke message parsing due to encoding edge cases in aiogram's markdown formatter. We switched to HTML parse mode across all funnel messages, which resolved it cleanly.

results

The bot now processes several hundred new users daily without manual intervention. The marketing team has iterated through over twenty funnel variations in the months since launch — something that would have been impossible with the old code-dependent approach. Re-engagement reminders alone recovered an estimated 15-20% of users who would have otherwise gone silent after the first message. The takeaway here is that the real product wasn't the bot — it was the configuration layer. Making the funnel editable by non-developers turned a static tool into a living system that improves on its own schedule, not ours.

Stack

Runtime: Python 3.11, aiogram 3, APScheduler

Config: YAML funnel definitions

Database: PostgreSQL (async via asyncpg)

Infrastructure: Docker

2026, «VOSGLOS». All rights reserved.