Workflow Recipes
Six complete workflow examples you can use as starting points. Each includes the full JSON graph, a flow diagram, and a walkthrough of how it works.
Recipe 1: Customer Support with Escalation
Section titled “Recipe 1: Customer Support with Escalation”A support bot that classifies intent, looks up relevant data, and escalates to a human when necessary.
Nodes: trigger, llm_call, logic (switch), mcp_tool, action (human_handoff), send_message
Workflow JSON
Section titled “Workflow JSON”{ "nodes": [ { "id": "trigger", "type": "trigger", "data": { "triggerType": "message_received", "label": "On Message" }, "position": { "x": 0, "y": 200 } }, { "id": "classify", "type": "llm_call", "data": { "prompt": "Classify this customer message into exactly one category: billing, refund, other.\n\nMessage: {{message}}\n\nRespond with only the category name, nothing else.", "outputVariable": "intent", "label": "Classify Intent" }, "position": { "x": 250, "y": 200 } }, { "id": "route", "type": "logic", "data": { "logicType": "switch", "variable": "intent.text", "cases": ["billing", "refund"], "label": "Route by Intent" }, "position": { "x": 500, "y": 200 } }, { "id": "lookup_account", "type": "mcp_tool", "data": { "toolName": "database__query", "arguments": "{\"sql\": \"SELECT * FROM accounts WHERE user_id = '{{user_id}}' LIMIT 1\"}", "outputVariable": "account", "label": "Look Up Account" }, "position": { "x": 750, "y": 50 } }, { "id": "billing_response", "type": "llm_call", "data": { "prompt": "The customer asked about billing. Their account info: {{account}}. Their message: {{message}}. Provide a helpful response.", "outputVariable": "ai_response", "label": "Generate Billing Response" }, "position": { "x": 1000, "y": 50 } }, { "id": "send_billing", "type": "send_message", "data": { "message": "{{ai_response.text}}", "label": "Send Billing Info" }, "position": { "x": 1250, "y": 50 } }, { "id": "check_order", "type": "mcp_tool", "data": { "toolName": "orders__get_latest_order", "arguments": "{\"user_id\": \"{{user_id}}\"}", "outputVariable": "order", "label": "Check Order" }, "position": { "x": 750, "y": 250 } }, { "id": "check_amount", "type": "logic", "data": { "logicType": "if_else", "condition": "{{order.total}} > 100", "label": "Amount > $100?" }, "position": { "x": 1000, "y": 250 } }, { "id": "escalate", "type": "action", "data": { "actionType": "human_handoff", "reason": "Refund request over $100 for order {{order.id}}", "label": "Escalate" }, "position": { "x": 1250, "y": 200 } }, { "id": "process_refund", "type": "mcp_tool", "data": { "toolName": "stripe__create_refund", "arguments": "{\"payment_id\": \"{{order.payment_id}}\"}", "outputVariable": "refund", "label": "Process Refund" }, "position": { "x": 1250, "y": 350 } }, { "id": "send_refund_confirm", "type": "send_message", "data": { "message": "Your refund for order #{{order.id}} has been processed. You should see the credit within 5-10 business days.", "label": "Confirm Refund" }, "position": { "x": 1500, "y": 350 } }, { "id": "general_response", "type": "llm_call", "data": { "prompt": "The customer sent: {{message}}. Provide a helpful general response.", "outputVariable": "ai_response", "label": "General Response" }, "position": { "x": 750, "y": 450 } }, { "id": "send_general", "type": "send_message", "data": { "message": "{{ai_response.text}}", "label": "Send Response" }, "position": { "x": 1000, "y": 450 } } ], "edges": [ { "id": "e1", "source": "trigger", "target": "classify" }, { "id": "e2", "source": "classify", "target": "route" }, { "id": "e3", "source": "route", "target": "lookup_account", "sourceHandle": "case_billing" }, { "id": "e4", "source": "lookup_account", "target": "billing_response" }, { "id": "e5", "source": "billing_response", "target": "send_billing" }, { "id": "e6", "source": "route", "target": "check_order", "sourceHandle": "case_refund" }, { "id": "e7", "source": "check_order", "target": "check_amount" }, { "id": "e8", "source": "check_amount", "target": "escalate", "sourceHandle": "true" }, { "id": "e9", "source": "check_amount", "target": "process_refund", "sourceHandle": "false" }, { "id": "e10", "source": "process_refund", "target": "send_refund_confirm" }, { "id": "e11", "source": "route", "target": "general_response", "sourceHandle": "case_default" }, { "id": "e12", "source": "general_response", "target": "send_general" } ]}Recipe 2: Order Status with ID Extraction
Section titled “Recipe 2: Order Status with ID Extraction”A focused workflow that extracts an order number from the message, looks it up, and responds with the status.
Nodes: trigger, llm_call, logic (if_else), mcp_tool, send_message
Workflow JSON
Section titled “Workflow JSON”{ "nodes": [ { "id": "trigger", "type": "trigger", "data": { "triggerType": "message_received", "label": "On Message" }, "position": { "x": 0, "y": 100 } }, { "id": "extract", "type": "llm_call", "data": { "prompt": "Extract the order number from this message. If no order number is found, respond with 'NONE'.\n\nMessage: {{message}}\n\nRespond with only the order number or 'NONE'.", "outputVariable": "order_id_result", "label": "Extract Order ID" }, "position": { "x": 250, "y": 100 } }, { "id": "has_order_id", "type": "logic", "data": { "logicType": "if_else", "condition": "{{order_id_result.text}} != NONE", "label": "Order ID Found?" }, "position": { "x": 500, "y": 100 } }, { "id": "lookup", "type": "mcp_tool", "data": { "toolName": "orders__get_order", "arguments": "{\"order_id\": \"{{order_id_result.text}}\"}", "outputVariable": "order", "label": "Look Up Order" }, "position": { "x": 750, "y": 50 } }, { "id": "format_response", "type": "llm_call", "data": { "prompt": "Format this order data into a friendly status message:\n\n{{order}}\n\nInclude: order ID, current status, tracking info, and estimated delivery if available.", "outputVariable": "status_msg", "label": "Format Status" }, "position": { "x": 1000, "y": 50 } }, { "id": "send_status", "type": "send_message", "data": { "message": "{{status_msg.text}}", "label": "Send Status" }, "position": { "x": 1250, "y": 50 } }, { "id": "ask_order", "type": "send_message", "data": { "message": "I can help you check your order status. Please share your order number (e.g., #12345) and I will look it up for you.", "label": "Ask for Order Number" }, "position": { "x": 750, "y": 200 } } ], "edges": [ { "id": "e1", "source": "trigger", "target": "extract" }, { "id": "e2", "source": "extract", "target": "has_order_id" }, { "id": "e3", "source": "has_order_id", "target": "lookup", "sourceHandle": "true" }, { "id": "e4", "source": "lookup", "target": "format_response" }, { "id": "e5", "source": "format_response", "target": "send_status" }, { "id": "e6", "source": "has_order_id", "target": "ask_order", "sourceHandle": "false" } ]}Recipe 3: Multi-Language FAQ
Section titled “Recipe 3: Multi-Language FAQ”A bot that detects the user’s language, searches a knowledge base, and responds in the user’s language.
Nodes: trigger, llm_call, logic (if_else), mcp_tool, send_message
Workflow JSON
Section titled “Workflow JSON”{ "nodes": [ { "id": "trigger", "type": "trigger", "data": { "triggerType": "message_received", "label": "On Message" }, "position": { "x": 0, "y": 150 } }, { "id": "detect_lang", "type": "llm_call", "data": { "prompt": "Detect the language of this text. Respond with only the ISO 639-1 code (e.g., en, es, fr, de, ja, zh).\n\nText: {{message}}", "outputVariable": "lang", "label": "Detect Language" }, "position": { "x": 250, "y": 150 } }, { "id": "is_english", "type": "logic", "data": { "logicType": "if_else", "condition": "{{lang.text}} == en", "label": "Is English?" }, "position": { "x": 500, "y": 150 } }, { "id": "search_faq_en", "type": "mcp_tool", "data": { "toolName": "knowledge__search", "arguments": "{\"query\": \"{{message}}\", \"limit\": 3}", "outputVariable": "faq_results", "label": "Search FAQ" }, "position": { "x": 750, "y": 50 } }, { "id": "answer_en", "type": "llm_call", "data": { "prompt": "Answer this question using the FAQ results below. If the FAQ does not cover it, say so.\n\nQuestion: {{message}}\n\nFAQ Results:\n{{faq_results}}", "outputVariable": "answer", "label": "Generate Answer" }, "position": { "x": 1000, "y": 50 } }, { "id": "send_en", "type": "send_message", "data": { "message": "{{answer.text}}", "label": "Send (English)" }, "position": { "x": 1250, "y": 50 } }, { "id": "translate_to_en", "type": "llm_call", "data": { "prompt": "Translate the following text to English. Respond with only the translation.\n\nText: {{message}}", "outputVariable": "translated_query", "label": "Translate to English" }, "position": { "x": 750, "y": 300 } }, { "id": "search_faq_translated", "type": "mcp_tool", "data": { "toolName": "knowledge__search", "arguments": "{\"query\": \"{{translated_query.text}}\", \"limit\": 3}", "outputVariable": "faq_results", "label": "Search FAQ" }, "position": { "x": 1000, "y": 300 } }, { "id": "answer_translated", "type": "llm_call", "data": { "prompt": "Answer this question using the FAQ results below.\n\nQuestion: {{translated_query.text}}\n\nFAQ Results:\n{{faq_results}}", "outputVariable": "answer_en", "label": "Answer (EN)" }, "position": { "x": 1250, "y": 300 } }, { "id": "translate_back", "type": "llm_call", "data": { "prompt": "Translate the following text to the language with ISO code '{{lang.text}}'. Respond with only the translation.\n\nText: {{answer_en.text}}", "outputVariable": "final_answer", "label": "Translate Back" }, "position": { "x": 1500, "y": 300 } }, { "id": "send_translated", "type": "send_message", "data": { "message": "{{final_answer.text}}", "label": "Send (Translated)" }, "position": { "x": 1750, "y": 300 } } ], "edges": [ { "id": "e1", "source": "trigger", "target": "detect_lang" }, { "id": "e2", "source": "detect_lang", "target": "is_english" }, { "id": "e3", "source": "is_english", "target": "search_faq_en", "sourceHandle": "true" }, { "id": "e4", "source": "search_faq_en", "target": "answer_en" }, { "id": "e5", "source": "answer_en", "target": "send_en" }, { "id": "e6", "source": "is_english", "target": "translate_to_en", "sourceHandle": "false" }, { "id": "e7", "source": "translate_to_en", "target": "search_faq_translated" }, { "id": "e8", "source": "search_faq_translated", "target": "answer_translated" }, { "id": "e9", "source": "answer_translated", "target": "translate_back" }, { "id": "e10", "source": "translate_back", "target": "send_translated" } ]}Recipe 4: Identity-Verified Refund
Section titled “Recipe 4: Identity-Verified Refund”A refund bot that verifies the customer’s identity via email OTP before accessing their account or processing any refund. Uses auth_gate and human_handoff.
Nodes: trigger, auth_gate, mcp_tool, logic (if_else), llm_call, action (human_handoff), send_message
Workflow JSON
Section titled “Workflow JSON”{ "nodes": [ { "id": "trigger", "type": "trigger", "data": { "triggerType": "message_received", "label": "On Message" }, "position": { "x": 0, "y": 150 } }, { "id": "verify", "type": "auth_gate", "data": { "provider": "resend", "contact_type": "email", "outputVariable": "verified_email", "label": "Verify Email" }, "position": { "x": 250, "y": 150 } }, { "id": "auth_failed", "type": "send_message", "data": { "message": "We were unable to verify your identity. Please try again or contact support directly.", "label": "Auth Failed" }, "position": { "x": 500, "y": 300 } }, { "id": "lookup_orders", "type": "mcp_tool", "data": { "toolName": "database__query", "arguments": "{\"sql\": \"SELECT * FROM orders WHERE customer_email = '{{verified_email}}' ORDER BY created_at DESC LIMIT 5\"}", "outputVariable": "orders", "label": "Look Up Orders" }, "position": { "x": 500, "y": 50 } }, { "id": "extract_order", "type": "llm_call", "data": { "prompt": "The customer wants a refund. Their message: {{message}}\n\nTheir recent orders:\n{{orders}}\n\nIdentify which order they want to refund. Respond with JSON: {\"order_id\": \"...\", \"amount\": ...}", "outputVariable": "refund_target", "label": "Identify Order" }, "position": { "x": 750, "y": 50 } }, { "id": "parse_amount", "type": "code_block", "data": { "code": "try { const parsed = JSON.parse(ctx.variables.refund_target.text); ctx.variables.refund_order_id = parsed.order_id; ctx.variables.refund_amount = parsed.amount || 0; } catch(e) { ctx.variables.refund_amount = 0; }", "label": "Parse Amount" }, "position": { "x": 1000, "y": 50 } }, { "id": "check_amount", "type": "logic", "data": { "logicType": "if_else", "condition": "{{refund_amount}} > 500", "label": "Amount > $500?" }, "position": { "x": 1250, "y": 50 } }, { "id": "escalate", "type": "action", "data": { "actionType": "human_handoff", "reason": "Refund of ${{refund_amount}} for order {{refund_order_id}} (customer: {{verified_email}})", "label": "Finance Approval" }, "position": { "x": 1500, "y": 0 } }, { "id": "process_refund", "type": "mcp_tool", "data": { "toolName": "stripe__create_refund", "arguments": "{\"order_id\": \"{{refund_order_id}}\"}", "outputVariable": "refund_result", "label": "Process Refund" }, "position": { "x": 1500, "y": 150 } }, { "id": "confirm_refund", "type": "send_message", "data": { "message": "Your refund of ${{refund_amount}} for order #{{refund_order_id}} has been processed. You will see the credit in 5-10 business days.", "label": "Confirm Refund" }, "position": { "x": 1750, "y": 150 } } ], "edges": [ { "id": "e1", "source": "trigger", "target": "verify" }, { "id": "e2", "source": "verify", "target": "lookup_orders", "sourceHandle": "verified" }, { "id": "e3", "source": "verify", "target": "auth_failed", "sourceHandle": "failed" }, { "id": "e4", "source": "lookup_orders", "target": "extract_order" }, { "id": "e5", "source": "extract_order", "target": "parse_amount" }, { "id": "e6", "source": "parse_amount", "target": "check_amount" }, { "id": "e7", "source": "check_amount", "target": "escalate", "sourceHandle": "true" }, { "id": "e8", "source": "check_amount", "target": "process_refund", "sourceHandle": "false" }, { "id": "e9", "source": "process_refund", "target": "confirm_refund" } ]}Recipe 5: Appointment Scheduler with Reminders
Section titled “Recipe 5: Appointment Scheduler with Reminders”A WhatsApp bot that checks availability, books appointments, and sends automated reminders.
Nodes: trigger, llm_call, mcp_tool, logic, schedule_message, send_message
Workflow JSON
Section titled “Workflow JSON”{ "nodes": [ { "id": "trigger", "type": "trigger", "data": { "triggerType": "message_received", "label": "On Message" }, "position": { "x": 0, "y": 150 } }, { "id": "extract_pref", "type": "llm_call", "data": { "prompt": "Extract the appointment date and time preference from this message. Respond with JSON: {\"date\": \"YYYY-MM-DD\", \"time\": \"HH:MM\", \"service\": \"...\"}\nIf no clear preference, use the next available business day at 10:00.\n\nMessage: {{message}}", "outputVariable": "preference", "label": "Extract Preference" }, "position": { "x": 250, "y": 150 } }, { "id": "parse_pref", "type": "code_block", "data": { "code": "try { const p = JSON.parse(ctx.variables.preference.text); ctx.variables.pref_date = p.date; ctx.variables.pref_time = p.time; ctx.variables.pref_service = p.service || 'general'; } catch(e) { ctx.variables.pref_date = ''; ctx.variables.pref_time = '10:00'; }", "label": "Parse" }, "position": { "x": 500, "y": 150 } }, { "id": "check_avail", "type": "mcp_tool", "data": { "toolName": "calendar__check_availability", "arguments": "{\"date\": \"{{pref_date}}\", \"time\": \"{{pref_time}}\"}", "outputVariable": "availability", "label": "Check Availability" }, "position": { "x": 750, "y": 150 } }, { "id": "is_available", "type": "logic", "data": { "logicType": "if_else", "condition": "{{availability.available}} == true", "label": "Slot Available?" }, "position": { "x": 1000, "y": 150 } }, { "id": "book", "type": "mcp_tool", "data": { "toolName": "calendar__create_appointment", "arguments": "{\"date\": \"{{pref_date}}\", \"time\": \"{{pref_time}}\", \"service\": \"{{pref_service}}\", \"customer_name\": \"{{user_name}}\", \"customer_id\": \"{{user_id}}\"}", "outputVariable": "booking", "label": "Book Appointment" }, "position": { "x": 1250, "y": 50 } }, { "id": "confirm", "type": "send_message", "data": { "message": "Your appointment is confirmed for {{pref_date}} at {{pref_time}}. Booking reference: {{booking.reference}}. You will receive reminders before your appointment.", "label": "Confirm" }, "position": { "x": 1500, "y": 0 } }, { "id": "reminder_24h", "type": "schedule_message", "data": { "message": "Reminder: Your appointment is tomorrow at {{pref_time}}. Reply CANCEL to cancel or RESCHEDULE to change.", "delay_minutes": 1380, "label": "24h Reminder" }, "position": { "x": 1500, "y": 100 } }, { "id": "reminder_1h", "type": "schedule_message", "data": { "message": "Your appointment is in 1 hour at {{pref_time}}. See you soon!", "delay_minutes": 1440, "label": "1h Reminder" }, "position": { "x": 1500, "y": 200 } }, { "id": "suggest", "type": "llm_call", "data": { "prompt": "The requested time slot ({{pref_date}} at {{pref_time}}) is not available. Available alternatives: {{availability.alternatives}}. Suggest 2-3 alternatives in a friendly message.", "outputVariable": "suggestion", "label": "Suggest Alternatives" }, "position": { "x": 1250, "y": 300 } }, { "id": "send_suggest", "type": "send_message", "data": { "message": "{{suggestion.text}}", "label": "Send Alternatives" }, "position": { "x": 1500, "y": 300 } } ], "edges": [ { "id": "e1", "source": "trigger", "target": "extract_pref" }, { "id": "e2", "source": "extract_pref", "target": "parse_pref" }, { "id": "e3", "source": "parse_pref", "target": "check_avail" }, { "id": "e4", "source": "check_avail", "target": "is_available" }, { "id": "e5", "source": "is_available", "target": "book", "sourceHandle": "true" }, { "id": "e6", "source": "book", "target": "confirm" }, { "id": "e7", "source": "book", "target": "reminder_24h" }, { "id": "e8", "source": "book", "target": "reminder_1h" }, { "id": "e9", "source": "is_available", "target": "suggest", "sourceHandle": "false" }, { "id": "e10", "source": "suggest", "target": "send_suggest" } ]}Recipe 6: Incident Triage with Bot Delegation
Section titled “Recipe 6: Incident Triage with Bot Delegation”A monitoring bot that receives alerts, parses severity, and delegates to specialist bots. Uses delegate_to_bot and send_proactive.
Nodes: trigger, llm_call, code_block, logic (switch), delegate_to_bot, send_proactive, action (human_handoff), send_message
Workflow JSON
Section titled “Workflow JSON”{ "nodes": [ { "id": "trigger", "type": "trigger", "data": { "triggerType": "message_received", "label": "Alert Received" }, "position": { "x": 0, "y": 200 } }, { "id": "parse", "type": "llm_call", "data": { "prompt": "Parse this monitoring alert. Extract severity (P1, P2, or P3), affected service, and a one-line summary.\n\nAlert: {{message}}\n\nRespond with JSON: {\"severity\": \"P1|P2|P3\", \"service\": \"...\", \"summary\": \"...\"}", "outputVariable": "alert_parsed", "label": "Parse Alert" }, "position": { "x": 250, "y": 200 } }, { "id": "extract", "type": "code_block", "data": { "code": "try { const a = JSON.parse(ctx.variables.alert_parsed.text); ctx.variables.severity = a.severity || 'P3'; ctx.variables.service = a.service || 'unknown'; ctx.variables.summary = a.summary || ctx.variables.message; } catch(e) { ctx.variables.severity = 'P3'; ctx.variables.summary = ctx.variables.message; }", "label": "Extract Fields" }, "position": { "x": 500, "y": 200 } }, { "id": "route", "type": "logic", "data": { "logicType": "switch", "variable": "severity", "cases": ["P1", "P2"], "label": "Route by Severity" }, "position": { "x": 750, "y": 200 } }, { "id": "delegate_p1", "type": "delegate_to_bot", "data": { "target_bot_id": "bt_oncall_bot", "outputVariable": "oncall_response", "context": "{\"severity\": \"P1\", \"service\": \"{{service}}\", \"summary\": \"{{summary}}\"}", "label": "Oncall Bot (P1)" }, "position": { "x": 1000, "y": 50 } }, { "id": "notify_ops_p1", "type": "send_proactive", "data": { "message": "P1 INCIDENT: {{service}} - {{summary}}\nOncall response: {{oncall_response}}", "target_channel_id": "ops-alerts", "target_platform": "slack", "label": "Notify Ops (P1)" }, "position": { "x": 1250, "y": 0 } }, { "id": "escalate_manager", "type": "action", "data": { "actionType": "human_handoff", "reason": "P1 incident on {{service}}: {{summary}}", "label": "Escalate to Eng Manager" }, "position": { "x": 1250, "y": 120 } }, { "id": "delegate_p2", "type": "delegate_to_bot", "data": { "target_bot_id": "bt_oncall_bot", "outputVariable": "oncall_response", "context": "{\"severity\": \"P2\", \"service\": \"{{service}}\", \"summary\": \"{{summary}}\"}", "label": "Oncall Bot (P2)" }, "position": { "x": 1000, "y": 250 } }, { "id": "notify_ops_p2", "type": "send_proactive", "data": { "message": "P2 Incident: {{service}} - {{summary}}\nOncall acknowledged.", "target_channel_id": "ops-alerts", "target_platform": "slack", "label": "Notify Ops (P2)" }, "position": { "x": 1250, "y": 250 } }, { "id": "create_ticket", "type": "mcp_tool", "data": { "toolName": "jira__create_issue", "arguments": "{\"project\": \"OPS\", \"type\": \"Bug\", \"summary\": \"[P3] {{service}}: {{summary}}\", \"priority\": \"Low\"}", "outputVariable": "ticket", "label": "Create Ticket (P3)" }, "position": { "x": 1000, "y": 400 } }, { "id": "ack_p3", "type": "send_message", "data": { "message": "P3 alert logged. Ticket created: {{ticket.key}}. Service: {{service}}.", "label": "Acknowledge" }, "position": { "x": 1250, "y": 400 } } ], "edges": [ { "id": "e1", "source": "trigger", "target": "parse" }, { "id": "e2", "source": "parse", "target": "extract" }, { "id": "e3", "source": "extract", "target": "route" }, { "id": "e4", "source": "route", "target": "delegate_p1", "sourceHandle": "case_P1" }, { "id": "e5", "source": "delegate_p1", "target": "notify_ops_p1" }, { "id": "e6", "source": "delegate_p1", "target": "escalate_manager" }, { "id": "e7", "source": "route", "target": "delegate_p2", "sourceHandle": "case_P2" }, { "id": "e8", "source": "delegate_p2", "target": "notify_ops_p2" }, { "id": "e9", "source": "route", "target": "create_ticket", "sourceHandle": "case_default" }, { "id": "e10", "source": "create_ticket", "target": "ack_p3" } ]}Importing a Recipe
Section titled “Importing a Recipe”To use any of these recipes, copy the workflow JSON and set it on your bot:
Via Dashboard
Section titled “Via Dashboard”- Open your bot’s detail page
- Go to the Workflow tab
- Paste the JSON into the workflow editor
- Toggle Enable Workflow
Via API
Section titled “Via API”curl -X PATCH https://api.aerostack.dev/api/bots/YOUR_BOT_ID \ -H "Authorization: Bearer YOUR_JWT_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "workflow_json": "{...paste the JSON here...}", "workflow_enabled": 1 }'