Somewhere in your support inbox is a question you have answered sixty times this month. “Do you deliver to 90210?” “Can I change my order after it ships?” “What time do you close on Sunday?” Write those down. That list is the entire project brief.
Most Rasa tutorials start with the framework and work backwards, which is why so many first attempts end as an impressive demo nobody uses. We will do it the other way, in eight steps, using a running example: a small bakery with a two-person team and about 40 identical DMs a week. Follow along with your own inbox and you will finish with something you can put in front of customers this week. If you want the conceptual ground first, how intent classification and dialogue management divide the labour, read this primer on what Rasa is and how the framework builds smarter AI assistants, then come back.
Step 1: Turn 100 real messages into an intent list
Export the last 100 customer messages from your help desk, Instagram, or wherever people actually reach you. Label each one with a short, verb-first name. Ignore the urge to be clever with naming, because you will type these dozens of times.
- ask_delivery_area — “do you deliver to 90210”, “is Culver City in your zone”
- change_order — “can I add a second bag”, “I ordered the wrong size”
- check_hours — “what time do you close today”, “are you open Monday”
- talk_to_human — “can I speak to someone”, “this isn’t helping”
Four intents is a good first release. Fifteen is not. The bakery bot that handles delivery zones and opening hours perfectly beats the one that half-answers everything.
Expect a third of the messages to fit nowhere
Roughly 30 of your 100 messages will be jokes, wrong numbers, or long stories with no question in them. Create a single out_of_scope intent, dump them all there, and move on. You will never get a tidy taxonomy on the first pass, and you do not need one.
Step 2: Get the project running in ten minutes
Rasa ships as a Python package, so a virtual environment keeps things clean:
python -m venv .venv && source .venv/bin/activatepip install rasarasa init --no-prompt
That scaffold gives you five files worth knowing: config.yml (which NLU and dialogue policies run), domain.yml (intents, entities, slots, and responses), data/nlu.yml (training examples), data/stories.yml and data/rules.yml (conversation flow), and actions/ (custom Python). Run rasa train, then rasa shell and type at your bot. It will talk about restaurant moods, because that is the default example, and that is fine. You have a working training loop in about ten minutes, which is the part most people stall on.
Step 3: Write NLU data that survives real users
Aim for 8 to 15 examples per intent on the first pass. The trap is writing them the way you would type, in tidy sentence case with punctuation. Customers write “do u deliver 2 90210” and “hours??”. Train on their version. Paste in the real messages from Step 1 verbatim, then add a few variations you expect but have not seen yet.
Entities and synonyms
Entities are the parts you want to pull out and reuse, like a postcode or a product name. In YAML they live inside square brackets, followed by the entity type in parentheses: do you deliver to [90210](zipcode). Synonyms let one entity name absorb several phrasings, so “cardamom bun”, “bun”, and “the cardamom thing” all resolve to the same product. For long lists such as neighbourhood names, a lookup table beats writing 40 separate examples.
Keep entity names identical in nlu.yml and domain.yml. Rasa will refuse to train if they drift apart, and the error message is not especially friendly about it.
Step 4: Decide what is a rule and what is a story
This is the design decision people get wrong most often, and it is simpler than it looks.
Rules for the straight lines
If an intent always produces the same response, write a rule. Opening hours, delivery zones, and handing off to a human are all rules: one trigger, one reply. Rules are cheap to maintain and easy to read six months later.
Stories for anything with a branch
Changing an order has steps: ask for the order number, check it exists, then either confirm the change or apologise. Write two or three short stories covering each ending, because the dialogue policies generalise from examples but cannot invent a path you never demonstrated. Name stories after the user goal (change_order_success, change_order_not_found) rather than the internals.
Step 5: Add custom actions for anything real
A bot gets useful the moment it can look something up. In actions/actions.py, subclass Action and give it a name such as action_check_order_status. Register that name in the actions list in domain.yml, point endpoints.yml at http://localhost:5055/webhook, and run rasa run actions in a second terminal while your main process runs.
Wrap every API call in a try/except and dispatch a written fallback response when it fails. An action that throws an exception returns nothing at all, and from the customer’s side that looks like your bot went silent mid-sentence.
Step 6: Test before you demo
rasa test holds back part of your training data and produces an intent report plus a confusion matrix. Read the confusion matrix properly: if check_hours and ask_delivery_area are being mixed up, your examples are too similar and no amount of extra training will fix it. Before any demo, write 20 fresh messages per intent that the model has never seen and check how many land right. Ninety percent on the easy intents and 55% on change_order tells you exactly where to spend the next hour.
Then test the ugly paths. Type gibberish. Say “stop” halfway through a form. Ask the same question three times. Set your fallback threshold deliberately, around 0.7 with the two-stage fallback classifier, and check that the fallback reply is genuinely helpful instead of a shrug.
Step 7: Give the bot a voice before you scale it
Two response variations per intent is the cheapest quality upgrade available. “We close at 6 today” and “Doors are open until 6, so there is time” cost you five minutes and stop the bot sounding like a parking meter. If you want to test phrasings quickly before committing them to a file, a personal AI workbench built in Poe is a good place to paste your response set and ask for ten alternatives in your own tone.
Tone is not decoration. Compare how the bakery sounds when it sells out: “item unavailable” versus “the cardamom buns went fast today, they are back at 7am”. Personality is what makes a message quotable, the same instinct that turned a band’s offhand remark about splurging on good yogurt into something fans repeated for weeks. Your bot is a brand surface, so decide what it sounds like before you write 60 responses you will have to edit later.
Step 8: Ship it and actually read the transcripts
Connect a channel through credentials.yml and run with rasa run --credentials credentials.yml. A web widget, Slack, or Telegram are all a few lines of config. Containerise it once and future deploys become boring, which is what you want.
Then build one habit: every Monday, open last week’s transcripts and look at two numbers, fallback rate and human handoff rate. Every fallback that fired more than five times is a missing intent, and adding one is a 30-minute job now that the loop exists. If handoffs jumped after a release, something you changed broke a path, and the transcripts will show you which one.
What to build second
Once those four bakery intents are holding steady, the natural next moves are a form that collects an order number before running an action, a scheduled job that pushes status updates for in-transit orders, and a small knowledge base behind your top 20 FAQ answers so the bot can respond to paraphrases you never trained on. Add one of those at a time and re-run rasa test after each. The projects that fail are the ones that bolt on five new abilities in a single weekend and then cannot work out which change broke the conversation.

